Using ApplicationCommands in WPF PRISM

2019-08-31 12:21发布

After studying several Q&A on stackoverflow, some tutorials and of course the official documentation, I trying to use ApplicationCommands in my WPF Prism MVVM application.

My current approach

After trying different solutions I found out, I ended up with following constellation:

  1. I am using the AttachCommandBindingsBehavior class mentioned in this answer, which will be used like this in the view:

    <UserControl>
        <i:Interaction.Behaviors>
            <localBehaviors:AttachCommandBindingsBehavior CommandBindings="{Binding CommandBindings}"/>
        </i:Interaction.Behaviors>
    </UserControl>
    
  2. MyViewModel contains a CommandBindingCollection property, which will be populated in the constructor:

    public CommandBindingCollection CommandBindings { get; } = new CommandBindingCollection();
    
    public MyViewModel()
    {
        this.CommandBindings.AddRange(new[]
        {
            new CommandBinding(ApplicationCommands.Save, this.Save, this.CanSave),
            new CommandBinding(ApplicationCommands.Open, this.Open)
        });
    }
    
  3. The UserControl MyView contains two buttons:

    <Button Command="ApplicationCommands.Open" Content="Open" />
    <Button Command="ApplicationCommands.Save" Content="Save" />
    

My first question at this point is: Are the Executed() and CanExecute() methods already bound to the Command-DependencyProperty of the Button? Since it does not work, what did I forgot or made wrong?

My second question is: How can I trigger the CanExecute of the Command the Button is bound to? The actual use-case: MyViewModel.CanSave() returns true, when the user successfully executed the MyViewModel.Open() method. Usually, I would call an DelegateCommand.RaiseCanExecuteChanged(), but calling ApplicationCommands.Save.RaiseCanExecuteChanged() does not execute MyViewModel.CanSave().

Feel free to ask for more information. I will really appreciate your answers. Thank you!

1条回答
欢心
2楼-- · 2019-08-31 13:03

Since it does not work, what did I forgot or made wrong?

The CommandBindings property on your behavior is an ObservableCollection<CommandBinding>, but you're binding it to a CommandBindingCollection in your view model. Change your view model's property to a ObservableCollection<CommandBinding>.

There are also some problems with the AttachCommandBindingsBehavior you linked to. I'm not sure why the answer was accepted, because it's actually quite broken. The tweaked version below should work, though.

public class AttachCommandBindingsBehavior : Behavior<FrameworkElement>
{
    public ObservableCollection<CommandBinding> CommandBindings
    {
        get => (ObservableCollection<CommandBinding>)GetValue(CommandBindingsProperty);
        set => SetValue(CommandBindingsProperty, value);
    }

    public static readonly DependencyProperty CommandBindingsProperty =
        DependencyProperty.Register(
            "CommandBindings",
            typeof(ObservableCollection<CommandBinding>),
            typeof(AttachCommandBindingsBehavior),
            new PropertyMetadata(null, OnCommandBindingsChanged));

    private static void OnCommandBindingsChanged(
        DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
    {
        var b = sender as AttachCommandBindingsBehavior;
        if (b == null)
            return;

        var oldBindings = e.OldValue as ObservableCollection<CommandBinding>;
        if (oldBindings != null)
            oldBindings.CollectionChanged -= b.OnCommandBindingsCollectionChanged;

        var newBindings = e.OldValue as ObservableCollection<CommandBinding>;
        if (newBindings != null)
            newBindings.CollectionChanged += b.OnCommandBindingsCollectionChanged;

        b.UpdateCommandBindings();
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        UpdateCommandBindings();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.CommandBindings.Clear();
    }

    private void UpdateCommandBindings()
    {
        if (AssociatedObject == null)
            return;

        AssociatedObject.CommandBindings.Clear();

        if (CommandBindings != null)
            AssociatedObject.CommandBindings.AddRange(CommandBindings);

        CommandManager.InvalidateRequerySuggested();
    }

    private void OnCommandBindingsCollectionChanged(
        object sender,
        NotifyCollectionChangedEventArgs e)
    {
        UpdateCommandBindings();
    }
}

How can I trigger the CanExecute of the Command the Button is bound to?

You can suggest that WPF's routed command system reevaluate all commands by calling CommandManager.InvalidateRequerySuggested(). The actual reevaluation will occur asynchronously at the Background dispatcher priority. This is the explicit way to do it, but you should know that this already happens implicitly upon certain actions, like focus changes and mouse/keyboard button-up events. The WPF developers tried to make automatic command requerying as seamless as possible, so it "just works" most of the time.

The actual use-case: MyViewModel.CanSave() returns true [...]

Just to be clear, as a CanExecuteRoutedEventHandler, your CanSave method should return void and set CanExecute to true on the event argument.

查看更多
登录 后发表回答