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:
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>
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)
});
}
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!
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.