如何将命令与视图模型绑定?

How to bind commands with a view model?

我已经阅读了很多关于绑定和命令的文章,但我正在努力获得我想要的东西。

以下工作正常

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>

但是,我想将命令逻辑分开,在它自己的 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>

当我执行此操作时,即使在清理和重建之后,我也会收到一条错误消息,指出 Commands 不在命名空间中。即使它位于 window class.

正下方

有什么想法吗?

保罗

绑定XAML中的命令时:我认为是因为CanExecuteExecuted绑定的方法必须是class后面代码的实例成员。


如果你想做你想做的事,你可以在代码中的静态构造函数中做:

  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)
    {

    }

  }

不知道问题解决了吗?她是我解决它的建议。如果不需要使用 RoutedUICommand,请将其更改为自己的 class,称为 RelayCommand (Link: RelayCommand implementation),它源自 ICommand.那么你的 Commands class 看起来像这样:

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;
        }
    }
}

在您的 TailoredReading Window XAML-文件中,您添加了 class 所在的命名空间。在我的示例中,它是 clr-namespace:WpfAppNet.Commands (就像代码片段的第一行)。如果您已经将它添加到 namspace-alias 中,那么您不需要这样做。

在您的代码片段中,请查看您是否添加了文件 Commands.csTailoredReading.cs到文件夹 Resources。如果不是,那可能是您出错的原因。

<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>

RelayCommand class 的好处是避免对每个对象执行 ExecuteCanExecute新命令。你只需要为他们实现方法/功能。

P.S.: 我还看到你为不同的命名空间别名添加了两次相同的路径:

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

其中一个可以删除。

  1. 您的命令逻辑与处理数据无关,因此在 ViewModel 中实现它没有意义。
    您的命令 class 不是 ViewModel,它是属于视图一部分的助手 class。

  2. 据我所知,“x: Static”标记扩展可以获取常量、枚举或STATIC字段和属性的值。
    但不是传统方法!

试试这个实现:

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>

当然,还要确保命名空间正确。

修改XAML后,可能会出现错误警告。
但这是因为 XAML 的信息不是从项目中检索的,而是从程序集中检索的。
因此,构建修改后的项目后错误应该会消失。

myRoutingCommandCanExecutemyRoutingCommandExecuted 是事件处理程序。您不能在另一个 class.

中定义这些

事实上,如果您想将执行逻辑与视图分开,使用 RoutedUICommand 并不是很有用。请参阅 this blog post 了解更多相关信息。

你应该做的是创建一个自定义 class 实现 ICommand 并接受 Action<object>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;
}

然后您可以在视图模型中创建命令的实例,您还可以在其中定义执行逻辑:

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;
    }
}

视图然后绑定到视图模型的命令属性:

<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>

显然,您不必在视图模型 class 中实现传递给命令的 Action<object>Predicate<object>。您可以在任何地方实施它们。