Search code examples
c#wpfrouted-commands

How to bind commands with a view model?


I've read a number of posts on binding and commands but I am struggling to get what I want working.

The below works fine

public partial class TailoredReading : Window
    {

        public static RoutedUICommand myRoutingCommand = new RoutedUICommand("myCommand", "myCommand", typeof(InputGestureWindow));

        public TailoredReading()
        {
            InitializeComponent();
        }

        private void SaveResource_Click(object sender, RoutedEventArgs e)
        {
            //ViewModel.SaveResource();
        }

        void myRoutingCommandExecuted(object target, ExecutedRoutedEventArgs e)
        {
            String command = ((RoutedCommand)e.Command).Name;
            MessageBox.Show("The \"" + command + "\" command has been invoked NOW. ");
        }

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

    }
<Window x:Class="ESL_Master_Suite.Components.Core.Resources.TailoredReading"
        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:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources" 
        xmlns:this1="clr-namespace:ESL_Master_Suite.Components.Controls"
        xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
        mc:Ignorable="d"
        Title="TailoredReading" WindowStartupLocation="CenterScreen" Width="1024">

    <Window.DataContext>
        <this:ViewModel />
    </Window.DataContext>

    <Window.InputBindings>
        <KeyBinding Command="{x:Static this:TailoredReading.myRoutingCommand}" Key="F1" />
    </Window.InputBindings>

    <Window.CommandBindings>
        <CommandBinding Command="{x:Static this:TailoredReading.myRoutingCommand}" CanExecute="myRoutingCommandCanExecute" Executed="myRoutingCommandExecuted"/>
    </Window.CommandBindings>

However, I would like to keep the command logic seperate, in it's own class.

public class Commands
    {
        public static readonly RoutedUICommand myRoutingCommand = new RoutedUICommand("myCommand", "myCommand", typeof(InputGestureWindow));

        void myRoutingCommandExecuted(object target, ExecutedRoutedEventArgs e)
        {
            String command = ((RoutedCommand)e.Command).Name;
            MessageBox.Show("The \"" + command + "\" command has been invoked. ");
        }

        void myRoutingCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
    }
<Window x:Class="ESL_Master_Suite.Components.Core.Resources.TailoredReading"
        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:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources" 
        xmlns:this1="clr-namespace:ESL_Master_Suite.Components.Controls"
        xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
        mc:Ignorable="d"
        Title="TailoredReading" WindowStartupLocation="CenterScreen" Width="1024">

    <Window.DataContext>
        <this:ViewModel />
    </Window.DataContext>

<Window.InputBindings>
        <KeyBinding Command="{x:Static this:Commands.myRoutingCommand}" Key="F1" />
    </Window.InputBindings>

    <Window.CommandBindings>
        <CommandBinding Command="{x:Static this:Commands.myRoutingCommand}" CanExecute="myRoutingCommandCanExecute" Executed="myRoutingCommandExecuted"/>
    </Window.CommandBindings>

When I do this, even after cleaning and rebuilding I get an error that Commands is not in the namespace. Even though it is and positioned just below the window class.

Any ideas?

Paul


Solution

  • myRoutingCommandCanExecute and myRoutingCommandExecuted are event handlers. You cannot defined these in another class.

    In fact, using a RoutedUICommand is not very useful if you want to separate your exeuction logic from the view. Please refer to this blog post for more information about this.

    What you should to is to create a custom class that implements ICommand and accepts an Action<object> and a Predicate<object>:

    public class DelegateCommand : System.Windows.Input.ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;
    
        public DelegateCommand(Action<object> execute)
            : this(execute, null)
        {
        }
    
        public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
    
        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
                return true;
    
            return _canExecute(parameter);
        }
    
        public void Execute(object parameter)
        {
            _execute(parameter);
        }
    
        public event EventHandler CanExecuteChanged;
    }
    

    You then create instances of the command in your view model where you also may define the execution logic:

    public class ViewModel
    {
        public ViewModel()
        {
            MyCommand = new DelegateCommand(MyCommandExecuted, MyCommandCanExecute);
        }
    
        public DelegateCommand MyCommand { get; }
    
        private void MyCommandExecuted(object obj)
        {
            MessageBox.Show("The command has been invoked.");
        }
    
        private bool MyCommandCanExecute(object obj)
        {
            return true;
        }
    }
    

    The view then binds to the command property of the view model:

    <Window x:Class="ESL_Master_Suite.Components.Core.Resources.TailoredReading"
            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:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources" 
            xmlns:this1="clr-namespace:ESL_Master_Suite.Components.Controls"
            xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
            mc:Ignorable="d"
            Title="TailoredReading" WindowStartupLocation="CenterScreen" Width="1024">
        <Window.DataContext>
            <this:ViewModel />
        </Window.DataContext>
    
        <Window.InputBindings>
            <KeyBinding Command="{Binding MyCommand}" Key="F1" />
        </Window.InputBindings>
    </Window>
    

    Obviously you don't have to implement the Action<object> and the Predicate<object> that you pass to the command in the view model class. You can implement them wherever you want.