0
votes

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?

2
I'd break this up into two statements : get { return this.selectAllCommand ?? (this.selectAllCommand = new DelegateCommand(SelectAll, ((t) => ((ObservableCollection<ThumbnailModel>)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
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
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
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
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

2 Answers

1
votes

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));
}
0
votes

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); }));
        }
    }