0

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

  • Post XAML namespaces as well for the window. – Suresh Aug 02 '20 at 09:01
  • Either the question title is wrong or this is a very strange implementation. This is a separate static class which isn't a viewmodel. But if you're trying Henrik's code it should be local:Commands.MyCommand Assuming your code matched his, that is. Unless this is only specific to that window you probably want Window rather than TailoredReading as the type the command binding will be associated with. If it's specific to the window then putting your event handlers in a separate class seems very strange. – Andy Aug 02 '20 at 14:55
  • I'm sorry. I'm not sure how it should be setup really. I've got the window as above. I have a class ViewModel with all the data for the UI. Originally, I wanted to add the command logic to the View Model class, but then I thought why not keep it seperate in it's own class. I don't know which way is correct or best way. – Paul Haydock Aug 02 '20 at 22:36

4 Answers4

2

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
1
  1. The logic of your commands has nothing to do with working with data, so there is no point in implementing it in the ViewModel.
    Your Commands class is not a ViewModel, a helper class that is part of the View.

  2. The "x: Static" markup extension, as far as I know, can get the value of constants, enumerations or STATIC fields and properties.
    But not the CONVENTIONAL METHOD!

Try this implementation:

public static class Commands
{
    public static RoutedUICommand MyRoutingCommand { get; } = new RoutedUICommand("myCommand", "myCommand", typeof(Commands));

    public static ExecutedRoutedEventHandler MyRoutingCommandExecuted { get; } 
      = myRoutingCommandExecuted;

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

    public static CanExecuteRoutedEventHandler MyRoutingCommandCanExecute { get; } 
      = myRoutingCommandCanExecute;

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

XAML:

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

<Window.CommandBindings>
    <CommandBinding Command="{x:Static this:Commands.MyRoutingCommand}"
                    CanExecute="{x:Static this:Commands.MyRoutingCommandCanExecute}"
                    Executed="{x:Static this:Commands.MyRoutingCommandExecuted}"/>
</Window.CommandBindings>

And, of course, make sure the namespaces are correct.

After making changes to XAML, there may be an error warning.
But this is due to the fact that information for XAML is retrieved not from the project, but from the assembly.
Therefore, errors should disappear after building the modified project.

EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Hey, I tried this solution but I encountered an exception:System.Windows.Markup.XamlParseException: ''Set property 'System.Windows.Input.CommandBinding.CanExecute' threw an exception.' ArgumentException: Object of type 'System.Action`2[System.Object,System.Windows.Input.CanExecuteRoutedEventArgs]' cannot be converted to type 'System.Windows.Input.CanExecuteRoutedEventHandler'. – Paul Haydock Aug 03 '20 at 12:53
  • This occured in: public TailoredReading() { InitializeComponent(); } – Paul Haydock Aug 03 '20 at 12:54
  • Sorry! Showed inattention. I fixed the error in the code. The type of delegates should have been set explicitly, and not through Action <...>. – EldHasp Aug 03 '20 at 14:55
0

When binding the commands in XAML: I think it's because the methods bound to CanExecute and Executed must be instance members of the code behind class.


If you want to do what you do, you can for instance do it in the static constructor in code:

  public class Commands
  {
    public static RoutedCommand MyCommand = new RoutedCommand("MyCommand", typeof(TailoredReading ));

    static Commands()
    {
      CommandBinding commandBinding = new CommandBinding(MyCommand, MyCommandCmd_Executed, MyCommandCmd_CanExecute);
      CommandManager.RegisterClassCommandBinding(typeof(TailoredReading ), commandBinding);
    }

    public static void MyCommandCmd_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = true;
    }

    public static void MyCommandCmd_Executed(object sender, ExecutedRoutedEventArgs e)
    {

    }

  }
0

I don't know if the problem has fixed? Her are my suggestion to solve it. If it is not nesessary to use an RoutedUICommand, change it to an own class called RelayCommand (Link: RelayCommand implementation) which derivide from ICommand. Then your Commands class looks like this:

namespace WpfAppNet.Commands
{
    public class Commands
    {
        public static ICommand MyRoutingCommand = new RelayCommand(MyRoutingCommandExecuted, (o) =>
        {
            return MyRoutingCommandCanExecute();
        });

        private static void MyRoutingCommandExecuted(object target)
        {
            MessageBox.Show("The command has been invoked. ");
        }

        private static bool MyRoutingCommandCanExecute()
        {
            return true;
        }
    }
}

In your TailoredReading Window XAML-File you had add the namespace where you located the the class. In my example it is clr-namespace:WpfAppNet.Commands (like the first row of the code-snipped). If you have already add this to the namspace-alias then you don't need to do that.

In your code-snippets, plase look if you have add your file Commands.cs and TailoredReading.cs to the folder Resources. If not that could be the reason of your error.

<Window x:Class="WpfAppNet.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:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:local="clr-namespace:WpfAppNet"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        mc:Ignorable="d"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        <!-- add an namespace alias where the file Commands.cs is located -->
        xmlns:commands="clr-namespace:WpfAppNet.Commands"
        Title="MainWindow" Height="450" Width="800">

    <Window.InputBindings>
        <!-- Bind to the command -->
        <KeyBinding Command="{x:Static commands:Commands.MyRoutingCommand}" Key="F1" />
    </Window.InputBindings>
 ...
</Window>

The benefit of the RelayCommand class are to avoid the implementation of Execute and CanExecute for every new command. You only need to implement the methods/ function for them.

P.S.: Also i have seen that you have add twice the same path for different namespace-alias:

xmlns:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources" 
xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"

One of them can remove.

kanukiesel
  • 175
  • 7