Search code examples
wpfxamlicommand

Why is the WPF ApplicationCommands.Close command disabled on the button and in the menu item?


Question

Why is the WPF ApplicationCommands.Close command disabled on the button and in the menu item?

Code

<Window x:Class="ApplicationCloseCommand.Views.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:viewModels="clr-namespace:ApplicationCloseCommand.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="169.493" Width="319.273">
    <Window.DataContext>
        <viewModels:MainWindowViewModel />
    </Window.DataContext>
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_File">
                <MenuItem Header="_Exit" Command="Close"/>
            </MenuItem>
        </Menu>
        <Canvas>
            <Button Content="Close" Command="Close" Height="23" VerticalAlignment="Top" Canvas.Left="142" Canvas.Top="31"/>
        </Canvas>
    </DockPanel>
</Window>

Research

Some sources say to implement an ICommand yourself and do a this.Close() in the command handler. But then, I don't understand why ApplicationCommands.Close exists at all.

Some sources say that one needs to implement a CanExecute methode to return true. But again, if I have to do that myself including the CloseCommandHandler (from the linked example), what's the benefit of ApplicationCommands.Close?

Some sources mention a CommandTarget. This didn't work:

<MenuItem Header="_Exit" Command="Close" CommandTarget="{Binding ElementName=MyMainWindow}"/>

Did I just do it wrong with the CommandTarget?


Solution

  • ApplicationCommands, like NavigationCommands and ComponentCommands exposes set of predefined common UI commands. They are all implementations of RoutedCommand. RoutedCommand or RoutedUICommandimplement ICommand.

    RoutedCommand doesn't implement an Execute or CanExecute method. It just raises the corresponding RoutedEvent.

    This event will traverse the visual tree until it is handled by a CommandBinding. It's the CommandBinding that defines or executes the CanExecute and Execute methods or special event handlers. This means the actual command implementations can be defined somewhere on the tree on which the RoutedCommand was raised.

    There are a few controls that implement a default CommandBinding (or command implementation). TextBox can handle e.g. the ApplicationCommands.Copy RoutedCommand. Or the DocumentViewer can handle the NavigationCommands.NextPage RoutedCommand. ApplicationCommands.Close has no supporter or default implementation provided by any control.

    RoutedCommand is designed to be used in UI context e.g. executing behaviour of a control. This kind of logic should never be handled in the view model. Therefore ApplicationCommands.Close must be implemented by a control or an attached behaviour that hooks into the visual tree.
    For data related behaviour or logic you must provide your own ICommand implementation, which has the handlers defined in the view model.

    If you assign a RoutedCommand e.g., ApplicationCommands.Close to a command source e.g. a Button, but don't provide a handler for this RoutedCommand and therefore no CanExecute handler exists the command source e.g. Button assumes CanExecute is false and disables itself.

    This means you must provide a CommandBinding:

    MainWindow.xaml

    <Window>
      <Window.CommandBindings>
        <CommandBinding Command="{x:Static ApplicationCommands.Close}"
                        Executed="ExecutedCloseCommand"
                        CanExecute="CanExecuteCloseCommand" />
      </Window.CommandBindings>
    
      <Button Command="{x:Static ApplicationCommands.Close}"
              Content="Close Window"/>
    </Window>
    

    MainWindow.xaml.cs

    private void CanExecuteCloseCommand(object sender,
        CanExecuteRoutedEventArgs e)
    {
       e.CanExecute = true;
    }
    
    private void ExecutedCloseCommand(object sender,
        ExecutedRoutedEventArgs e)
    {
      this.Close();
    }