如何将命令与视图模型绑定?
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中的命令时:我认为是因为CanExecute
和Executed
绑定的方法必须是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.cs 和 TailoredReading.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 的好处是避免对每个对象执行 Execute 和 CanExecute新命令。你只需要为他们实现方法/功能。
P.S.: 我还看到你为不同的命名空间别名添加了两次相同的路径:
xmlns:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
其中一个可以删除。
您的命令逻辑与处理数据无关,因此在 ViewModel 中实现它没有意义。
您的命令 class 不是 ViewModel,它是属于视图一部分的助手 class。
据我所知,“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 的信息不是从项目中检索的,而是从程序集中检索的。
因此,构建修改后的项目后错误应该会消失。
myRoutingCommandCanExecute
和 myRoutingCommandExecuted
是事件处理程序。您不能在另一个 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>
。您可以在任何地方实施它们。
我已经阅读了很多关于绑定和命令的文章,但我正在努力获得我想要的东西。
以下工作正常
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中的命令时:我认为是因为CanExecute
和Executed
绑定的方法必须是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.cs 和 TailoredReading.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 的好处是避免对每个对象执行 Execute 和 CanExecute新命令。你只需要为他们实现方法/功能。
P.S.: 我还看到你为不同的命名空间别名添加了两次相同的路径:
xmlns:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
其中一个可以删除。
您的命令逻辑与处理数据无关,因此在 ViewModel 中实现它没有意义。
您的命令 class 不是 ViewModel,它是属于视图一部分的助手 class。据我所知,“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 的信息不是从项目中检索的,而是从程序集中检索的。
因此,构建修改后的项目后错误应该会消失。
myRoutingCommandCanExecute
和 myRoutingCommandExecuted
是事件处理程序。您不能在另一个 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>
。您可以在任何地方实施它们。