0

I'm trying to disable a menuitem depending on objects in an ObservableCollection.

MainViewModel:

public ObservableCollection<ThumbnailModel> Thumbnails { get; set; }

public MainWindowViewModel()
{
    Thumbnails = new ObservableCollection<ThumbnailModel>();
    this.CreateMenu();
}

private void CreateMenu()
{
    //TODO: Add tooltip to menu with short description

    var items = new List<MenuItemViewModel>();

    var item = new MenuItemViewModel();
    item.MenuText = "File";

    item.MenuItems = new List<MenuItemViewModel> { 
        new MenuItemViewModel { MenuText = "Select all", MenuCommand = this.SelectAllCommand, IsEnabled = SelectAllCommand.CanExecute(Thumbnails) },
        new MenuItemViewModel { MenuText = "Unselect all", MenuCommand = this.UnselectAllCommand, IsEnabled = true },
    };

    items.Add(item);

    //And so on
    MenuItems = items;
}

public ICommand SelectAllCommand
{
    get
    {
        return this.selectAllCommand ??
            (this.selectAllCommand = new DelegateCommand(SelectAll, ((t) => ((ObservableCollection<ThumbnailModel>)t).Any(o => !o.IsChecked))));
    }
}

Xaml:

<Window.Resources>
    <!--Menu template-->
    <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}"
                              ItemsSource="{Binding Path=MenuItems}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding MenuCommand}"/>
                <Setter Property="CommandParameter" 
                        Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                <Setter Property="IsEnabled"
                        Value="{Binding IsEnabled}"/>
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding MenuIcon}" />
            <TextBlock Text="{Binding MenuText}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>


<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />

When opening the File-menu, I get an exception.

System.ArgumentNullException was unhandled HResult=-2147467261
Message=Value cannot be null. Parameter name: source
Source=System.Core
ParamName=source
StackTrace: at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2 predicate) at KoenHoefman.PhotoResizer.ViewModels.MainWindowViewModel.b__e(Object t) in d:\000 TFS Workspace\KoenHoefman.PhotoResizer\Main\KoenHoefman.PhotoResizer\ViewModels\MainWindowViewModel.cs:line 126 at KoenHoefman.PhotoResizer.ViewModels.DelegateCommand.CanExecute(Object parameter) in d:\000 TFS Workspace\KoenHoefman.PhotoResizer\Main\KoenHoefman.PhotoResizer\ViewModels\DelegateCommand.cs:line 95 at MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(ICommandSource commandSource) at System.Windows.Controls.MenuItem.UpdateCanExecute() at System.Windows.Controls.MenuItem.HookCommand(ICommand command) ...

At first I tought the reason was the fact that there are no items in MenuItems at the start. However, when I run the folowing code after my menu-creation it returns false (as expected).

var y = SelectAllCommand.CanExecute(Thumbnails);

Any idea what's going wrong here and of course how to fix it?

UPDATE Must have looked over it before but when the CanExecute-method is hit, the parameter is null, although I've specified it to be Thumbnails ?

DelegateCommand:

using System;
using System.Windows.Input;

public class DelegateCommand : ICommand
{

    private readonly Action<object> execute;
    private readonly Predicate<object> canExecute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {}

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }

        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        this.execute(parameter);
    }

    public bool CanExecute(object parameter) // parameter is null when breakpoint is hit
    {
        return this.canExecute == null || this.canExecute(parameter);
    }
}

If I understand predicates correctly (which is not sure), the method will be executed every time it's called. But what about the parameter I've put in at the time of the assignment? Is this only used one time?

Koen
  • 2,501
  • 1
  • 32
  • 43
  • I'd break this up into two statements : get { return this.selectAllCommand ?? (this.selectAllCommand = new DelegateCommand(SelectAll, ((t) => ((ObservableCollection)t).Any(o => !o.IsChecked)))); } So that when it is called you can see a bit more. Is SelectAllCommand Null or not, if it's not then does it return what you expect? – JWP Nov 11 '14 at 19:54
  • I've done as you suggested, but outcome is the same. I've put a breakpoint in CanExecute and the predicate is not null, but when step further the exception occurs. – Koen Nov 11 '14 at 20:11
  • But as mentioned at the bottom of my question: When I execute the same from my code (I've placed in the ctor of the VM) It returns false as it should because there are no items in the collection (that is initialized in the ctor). – Koen Nov 11 '14 at 20:12
  • I thought using the ICommand interface made it unecessary for us to have to set enable properites explictly as it's done by WPF/XAML system right? This HResult=-2147467261 isn't good as it appears to be possbily a corrupted pointer issue? Maybe it's an after effect because the value is null? I'm guessing here. – JWP Nov 11 '14 at 20:24
  • Also I don't care for the delegate command but am wondering where is the CanExecute option contained and what is the backing store for the method call? – JWP Nov 11 '14 at 20:26
  • Do you mean the `DelegateCommand` class? – Koen Nov 11 '14 at 20:27
  • Ya I call it the boomerang thing. Why not put all ICommand in one place? When an ICommand finishes what it's doing it can easily send an event. To me it's cleaner because VM only hooks up to the events, all the logic is in the command class. – JWP Nov 11 '14 at 23:51
  • Not a bad idea. However, I doubt that it will solve the issue here. As I've put in the update at the bottom, the parameter of the CanExecute method is ´null´ instead of the Thumbnails collection that I would like it to be. I guess I've missed something in the assignment, just can't figure out what... – Koen Nov 12 '14 at 10:22
  • CanExecute in the ICommand pattern is a method, it has nothing in it until you decide what controls that value. Typically this is done in the Execute method of the command on entry setting a private backing variable to _CanExcute=false, when the Execute method is done is sets that variable to true. The CanExecute method then just returns _CanExecute. Of course there are other ways to do this but that's by far the most simple. – JWP Nov 12 '14 at 15:07
  • I've added the complete DelegateCommand class. As you can see the predicate will be executed when CanExecute is executed. I'll have a look again at the assignment of the predicate and the IsEnabled property. Guess the problem lies somewhere in there... – Koen Nov 12 '14 at 19:49

2 Answers2

1

The definition of a Predicate is this:

public delegate bool Predicate<in T>( T obj)

All it does is some type of compare or test on the obj and returns true or false. We see this all the time in LINQ.

 var myList = getEmployees();
 var filter = myList.Where(p=>p.lastName == "Jones");

The delegate is the "p" or parameter, and the comparison part is the predicate or bool value.. Notice that the type passed in in "implied" this is because linq has a static class "Where" extension method allowing up to pass in any collection type which will take a predicate as the parm. Like this:

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, int, bool> predicate
)

Per the example at MSFT site on the Delegate command we see the new creation of one, and the second parm is passing a method (pointer) named "CanSubmit" to be called when needed.

public MyClass()
{
   this.submitCommand = new DelegateCommand<int?>(this.Submit, this.CanSubmit);
}

private bool CanSubmit(int? customerId)
{
  return (customerId.HasValue && customers.Contains(customerId.Value));
}
JWP
  • 6,672
  • 3
  • 50
  • 74
  • Got it working. Reading your answer, I noticed that I wasn't wrong about the Predicate but totally misunderstood the working of MenuItem and CanExecute. Can't mark your answer as the solution but You've got my +1 for your effort. It's highly appreciated. – Koen Nov 12 '14 at 22:00
0

Finally figured it out while going through the code, step by step and stumbling upon this question

Turns out that

By default menu items become disabled when its command cannot be executed (CanExecute = false).

(Could not find any reference to that in MSDN??)

So the solution became a lot simpler since I didn't need the IsEnabled property on my MenuItemViewModel anymore.

My XAML looks now like:

<Window.Resources>
    <!--Menu template-->
    <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}"
                              ItemsSource="{Binding Path=MenuItems}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding MenuCommand}"/>
                <Setter Property="CommandParameter" 
                        Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                <!-- No longer needed. By default menu items become disabled when its command cannot be executed (CanExecute = false).
                <Setter Property="IsEnabled"
                        Value="{Binding IsEnabled}"/>-->
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding MenuIcon}" />
            <TextBlock Text="{Binding MenuText}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>

And my commands:

    public ICommand SelectAllCommand
    {
        get
        {
            return this.selectAllCommand ?? (this.selectAllCommand = new DelegateCommand(SelectAll, delegate(object obj) { return Thumbnails.Any(t => !t.IsChecked); }));
        }
    }
Community
  • 1
  • 1
Koen
  • 2,501
  • 1
  • 32
  • 43
  • 1
    Very good! Yes indeed the CanExecute for any item automatically handles enabled properties (Wpf internals handle that and it is transparent to the programmer). – JWP Nov 13 '14 at 14:57