Why WPF CommandBinding with one button is effecting the other button?

106 Views Asked by At

My following code is Implementing a custom WPF Command. I have bonded only the first button (titled Exit) with the CommandBinding so that when Exit button is clicked and e.CanExecute is true in CommandBinding_CanExecute event, the CommandBinding_Executed event closes the app. This scenario works fine with Exit button. But, when btnTest button - that is not bonded with any command - is clicked, CommandBinding_CanExecute event also gets called. This can be tested by placing a breakpoint on the btnTest_Click event and noticing that after the code exits this event the cursor goes to CommandBinding_CanExecute event.

Question: Why the btnTest button is also calling CommandBinding_CanExecute event despite that fact that CommandBinding is used only on Exit button. What I may be missing here, and how can we fix the issue?

Remarks For brevity I have simplified the issue. But in real scenario e.CanExecute value in CommandBinding_CanExecute is set to true by calling a function that performs a long complex logic that returns true or false based on certain scenario for the Exit button. And I don't want that long logic to be performed when other buttons (e.g. btnTest) is clicked.

MainWindow.Xaml:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Button Content="Exit" Command="local:CustomCommands.Exit">
                <Button.CommandBindings>
                    <CommandBinding Command="local:CustomCommands.Exit" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
                </Button.CommandBindings>
            </Button>
            <Button x:Name="btnTest" Content="Test" Click="btnTest_Click" Margin="10"/>
        </StackPanel>
    </Grid>
</Window>

MainWindow.Xaml.cs:

public partial class MainWindow : Window
{
public MainWindow()
{
    InitializeComponent();
}

private void btnTest_Click(object sender, RoutedEventArgs e)
{
    System.Diagnostics.Debug.WriteLine("Why this event is calling ExitCommand_CanExecute");
}

private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Application.Current.Shutdown();
}
}

public static class CustomCommands
{
    public static readonly RoutedUICommand Exit = new RoutedUICommand
    (
        "Exit",
        "Exit",
        typeof(CustomCommands),
        new InputGestureCollection()
        {
            new KeyGesture(Key.F4, ModifierKeys.Alt)
        }
    );
}
2

There are 2 best solutions below

5
On

What makes you think btnTest is calling CommandBinding_CanExecute? It doesn't.

The CanExecute method of the command is called by the CommandManager whenever it wants to know the current status of the command. You don't control when this happens. The framework does. It's not connected to the btnTest.

If you have some complex logic in CanExecute, you should consider creating a custom command class that implements the ICommand interface and raise the CanExecuteChanged event whenever you want the framework to refresh the status of the command by calling its CanExecute method. This way you can control when the command should be refreshed.

You could then bind the Command property of the Button to an instance of your custom command class. If you google for "DelegateCommand" or "RelayCommand", you should find a lot of examples. This blog post may be a good starting point.

0
On

Any interaction with the UI which is considered by the designers of wpf to be significant will indirectly initiate a check of all bound canexecute. The idea being you changed something, did something or other. Best check if all these commands should still be enabled.

It's actually commandmanager.requerysuggested() that is invoked. This doesn't directly invoke canexecute. What it does is tells commands they should go check whether they can still be executed. This isn't completely insane because whilst your button's command is invoking some code then there's a fair chance if the user clicks some other button then your viewmodel will be partly updated or in some indeterminate state,

You should never drive other logic using canexecute.

It is very common to add a bool IsBusy to a base viewmodel and check that to see if anything is doing stuff and you should not allow the user to do something else.

An extra check within commands on IsBusy is part of this pattern.