【问题标题】:How to bind commands with a view model?如何将命令与视图模型绑定?
【发布时间】:2020-08-02 08:22:07
【问题描述】:

我已经阅读了许多关于绑定和命令的帖子,但我很难得到我想要的工作。

下面的工作正常

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>

但是,我想将命令逻辑分开,放在它自己的类中。

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>

当我这样做时,即使在清理和重建之后,我也会收到命令不在命名空间中的错误。即使它位于窗口类的正下方。

有什么想法吗?

保罗

【问题讨论】:

  • 为窗口发布 XAML 命名空间。
  • 要么问题标题错误,要么这是一个非常奇怪的实现。这是一个单独的静态类,它不是视图模型。但是,如果您正在尝试 Henrik 的代码,它应该是 local:Commands.MyCommand 假设您的代码与他的代码匹配,那就是。除非这仅特定于该窗口,否则您可能希望 Window 而不是 TailoredReading 作为命令绑定将与之关联的类型。如果它是特定于窗口的,那么将您的事件处理程序放在一个单独的类中似乎很奇怪。
  • 对不起。我不确定它应该如何设置。我有上面的窗口。我有一个 ViewModel 类,其中包含 UI 的所有数据。最初,我想将命令逻辑添加到 View Model 类中,但后来我想为什么不将它单独放在它自己的类中。我不知道哪种方式是正确的或最好的方式。

标签: c# wpf routed-commands


【解决方案1】:

myRoutingCommandCanExecutemyRoutingCommandExecuted 是事件处理程序。您不能在另一个类中定义这些。

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

您应该创建一个实现ICommand 并接受Action&lt;object&gt;Predicate&lt;object&gt; 的自定义类:

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>

显然,您不必在视图模型类中实现传递给命令的Action&lt;object&gt;Predicate&lt;object&gt;。您可以在任何地方实现它们。

【讨论】:

  • 感谢您的回答以及非常有帮助的博文。
【解决方案2】:
  1. 命令的逻辑与处理数据无关,因此在 ViewModel 中实现它没有意义。
    您的 Commands 类不是 ViewModel,它是属于 View 的辅助类。

  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 的信息不是从项目中检索的,而是从程序集中检索的。
因此,构建修改后的项目后,错误应该会消失。

【讨论】:

  • 嘿,我尝试了这个解决方案,但遇到了异常:System.Windows.Markup.XamlParseException: ''Set property 'System.Windows.Input.CommandBinding.CanExecute' throw an exception。' ArgumentException:“System.Action`2[System.Object,System.Windows.Input.CanExecuteRoutedEventArgs]”类型的对象无法转换为“System.Windows.Input.CanExecuteRoutedEventHandler”类型。
  • 这发生在:public TailoredReading() { InitializeComponent(); }
  • 对不起!表现出不注意。我修复了代码中的错误。委托的类型应该已经明确设置,而不是通过 Action <...>.
【解决方案3】:

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


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

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

    }

  }

【讨论】:

    【解决方案4】:

    不知道问题解决了吗?她是我解决它的建议。如果不需要使用RoutedUICommand,请将其更改为一个名为RelayCommand(链接:RelayCommand implementation)的自己的类,该类派生自ICommand。然后你的Commands 类看起来像这样:

    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-File 中,您已经添加了类所在的命名空间。在我的示例中,它是clr-namespace:WpfAppNet.Commands(就像代码片段的第一行一样)。如果您已经将其添加到 namspace-alias 中,则无需这样做。

    在您的 code-sn-ps 中,请查看您是否已将文件 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 类的好处是避免为每个新命令执行 ExecuteCanExecute。您只需要为它们实现方法/功能。

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

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

    其中一个可以删除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-02
      • 2022-01-09
      • 2014-11-08
      • 2011-07-29
      • 1970-01-01
      • 2020-06-20
      • 2013-01-16
      • 1970-01-01
      相关资源
      最近更新 更多