-3

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)
        }
    );
}
nam
  • 21,967
  • 37
  • 158
  • 332

2 Answers2

0

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • This can be tested by placing a breakpoint on the btnTest_Click event and by pressing `F11` noticing that after the code exits `btnTest_Click` event, the cursor goes to CommandBinding_CanExecute event. – nam Dec 15 '20 at 16:18
  • Yes, because the `CommandManager` calls it then. Like I said, you don't control *when* it's called. But it will be called very frequently. – mm8 Dec 15 '20 at 16:19
  • Huuuun!!! So I probably should use some other logic because I don't want my that function (mentioned in my remarks of the post) to be called by the click event of other buttons. – nam Dec 15 '20 at 16:22
  • Did you really read my answer? Then you should provide your own custom implementation of `ICommand`. – mm8 Dec 21 '20 at 14:08
  • Sorry, I was on holiday break. I thought when we are doing resources/binding etc. we can do it on application level, windows level, and local level. E.g., for a button, using `.....`. I was curious as to why then `` is not local to this specific button. And, if it has something to do with `CommandManager` then what is the use of ``. A reasoning behind the use of `` may help. I may be missing something here. Am I? – nam Dec 31 '20 at 15:54
0

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.

Andy
  • 11,864
  • 2
  • 17
  • 20