如何在 Caliburn.Micro ViewModel 中处理 WPF MenuItem 命令?
How to handle WPF MenuItem Commands in a Caliburn.Micro ViewModel?
我的 WPF 应用程序有一个布局页面,代码如下:
/* /Views/ShellView.xaml */
<DockPanel>
<!-- Global Main menu, always visible -->
<Menu IsMainMenu="true" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="Save"/>
<MenuItem Header="_Save As..."/>
<Separator/>
<MenuItem Header="_Exit"/>
</MenuItem>
</Menu>
<!-- The window's main content. It contains forms that the user might want to save -->
<ContentControl x:Name="ActiveItem"/>
</DockPanel>
由于我使用的是Caliburn.Micro,所以我也实现了相应的视图模型:
/* /ViewModels/ShellViewModel.cs */
public class ShellViewModel : Conductor<object> {
// Control logic to manage ActiveItem
}
我的 objective 是为 Save
命令实现一个处理程序,我假设只要用户单击相应的 MenuItem
或按 CTRL + S
就会触发该处理程序。
在标准 WPF 中,我会在 ShellView.xaml
中添加一个 CommandBinding
标记(如 this tutorial 中所示)以将事件路由到我将在 ShellView.xaml.cs
中实现的处理程序.但是,为了尊重 Caliburn.Micro 的 MVVM 约定,我希望我的逻辑保留在视图模型中 class。
我查看了 Caliburn.Micro 的文档,但我发现最接近命令的是 Actions。
我该如何实现?
感谢您的宝贵时间。
解决方案并不短!!
你需要创建一个依赖(这里是GestureMenuItem)
在 xaml 文件中
xmlns:common="clr-namespace:Common.Caliburn"
:
:
<Menu IsMainMenu="true" DockPanel.Dock="Top">
<common:GestureMenuItem x:Name="Save" Key="S" Modifiers="Ctrl" Header="_Save"/>
在 ActionMessageCommand.cs 文件中
using System;
using System.Windows.Input;
using Caliburn.Micro;
namespace Common.Caliburn
{
public class ActionMessageCommand : ActionMessage, ICommand
{
static ActionMessageCommand()
{
EnforceGuardsDuringInvocation = true;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
void ICommand.Execute(object parameter)
{
}
public event EventHandler CanExecuteChanged;
}
}
在 GestureMenuItem.cs 文件中
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace Common.Caliburn
{
public class GestureMenuItem : MenuItem
{
public override void EndInit()
{
Interaction.GetTriggers(this).Add(ConstructTrigger());
if(string.IsNullOrEmpty(InputGestureText))
InputGestureText = BuildInputGestureText(Modifiers, Key);
base.EndInit();
}
private static readonly IEnumerable<ModifierKeys> ModifierKeysValues = Enum.GetValues(typeof(ModifierKeys)).Cast<ModifierKeys>().Except(new [] { ModifierKeys.None });
private static readonly IDictionary<ModifierKeys, string> Translation = new Dictionary<ModifierKeys, string>
{
{ ModifierKeys.Control, "Ctrl" }
};
private static string BuildInputGestureText(ModifierKeys modifiers, Key key)
{
var result = new StringBuilder();
foreach (var val in ModifierKeysValues)
if ((modifiers & val) == val)
result.Append((Translation.ContainsKey(val) ? Translation[val] : val.ToString()) + " + ");
result.Append(key);
return result.ToString();
}
private TriggerBase<FrameworkElement> ConstructTrigger()
{
var trigger = new InputBindingTrigger();
trigger.GlobalInputBindings.Add(new KeyBinding { Modifiers = Modifiers, Key = Key });
var command = new ActionMessageCommand { MethodName = Name };
Command = command;
trigger.Actions.Add(command);
return trigger;
}
public static readonly DependencyProperty ModifiersProperty =
DependencyProperty.Register("Modifiers", typeof(ModifierKeys), typeof(GestureMenuItem), new PropertyMetadata(default(ModifierKeys)));
public ModifierKeys Modifiers
{
get { return (ModifierKeys)GetValue(ModifiersProperty); }
set { SetValue(ModifiersProperty, value); }
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(GestureMenuItem), new PropertyMetadata(default(Key)));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
}
}
在 InputBindingTrigger.cs 文件中
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using Caliburn.Micro;
namespace Common.Caliburn
{
public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
{
public InputBindingTrigger()
{
GlobalInputBindings = new BindableCollection<InputBinding>();
LocalInputBindings = new BindableCollection<InputBinding>();
}
public static readonly DependencyProperty LocalInputBindingsProperty =
DependencyProperty.Register("LocalInputBindings", typeof(BindableCollection<InputBinding>), typeof(InputBindingTrigger), new PropertyMetadata(default(BindableCollection<InputBinding>)));
public BindableCollection<InputBinding> LocalInputBindings
{
get { return (BindableCollection<InputBinding>)GetValue(LocalInputBindingsProperty); }
set { SetValue(LocalInputBindingsProperty, value); }
}
public BindableCollection<InputBinding> GlobalInputBindings
{
get { return (BindableCollection<InputBinding>)GetValue(GlobalInputBindingProperty); }
set { SetValue(GlobalInputBindingProperty, value); }
}
public static readonly DependencyProperty GlobalInputBindingProperty =
DependencyProperty.Register("GlobalInputBinding", typeof(BindableCollection<InputBinding>), typeof(InputBindingTrigger), new UIPropertyMetadata(null));
protected override void OnAttached()
{
foreach (var binding in GlobalInputBindings.Union(LocalInputBindings))
binding.Command = this;
AssociatedObject.Loaded += delegate
{
var window = GetWindow(AssociatedObject);
foreach (var binding in GlobalInputBindings)
window.InputBindings.Add(binding);
foreach (var binding in LocalInputBindings)
AssociatedObject.InputBindings.Add(binding);
};
base.OnAttached();
}
private Window GetWindow(FrameworkElement frameworkElement)
{
if (frameworkElement is Window)
return frameworkElement as Window;
var parent = frameworkElement.Parent as FrameworkElement;
return GetWindow(parent);
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
InvokeActions(parameter);
}
}
}
并在 ShellViewModel.cs 中,使用 Caliburn
的约定名称
public void Save()
{
//some code here
}
我找到了一个更短的解决方案。在 class 继承 ViewAware
时,受保护的虚方法 onViewReady
在视图就绪时用视图对象调用。
所以在我的视图模型中,我可以像这样覆盖它:
protected override void OnViewReady(object view) {
base.OnViewReady(view);
ShellView shellView = (ShellView)view;
shellView.CommandBindings.Add(new CommandBinding(ApplicationCommands.SaveAs, SaveAsCommandHandler));
}
使用视图对象,我可以通过指定特定命令和处理程序来添加新的 CommandBinding
实例。
处理程序可以在视图模型中定义class:
private void SaveAsCommandHandler(object sender, ExecutedRoutedEventArgs e) {
this.CurrentForm.SaveAs();
}
我的 WPF 应用程序有一个布局页面,代码如下:
/* /Views/ShellView.xaml */
<DockPanel>
<!-- Global Main menu, always visible -->
<Menu IsMainMenu="true" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="Save"/>
<MenuItem Header="_Save As..."/>
<Separator/>
<MenuItem Header="_Exit"/>
</MenuItem>
</Menu>
<!-- The window's main content. It contains forms that the user might want to save -->
<ContentControl x:Name="ActiveItem"/>
</DockPanel>
由于我使用的是Caliburn.Micro,所以我也实现了相应的视图模型:
/* /ViewModels/ShellViewModel.cs */
public class ShellViewModel : Conductor<object> {
// Control logic to manage ActiveItem
}
我的 objective 是为 Save
命令实现一个处理程序,我假设只要用户单击相应的 MenuItem
或按 CTRL + S
就会触发该处理程序。
在标准 WPF 中,我会在 ShellView.xaml
中添加一个 CommandBinding
标记(如 this tutorial 中所示)以将事件路由到我将在 ShellView.xaml.cs
中实现的处理程序.但是,为了尊重 Caliburn.Micro 的 MVVM 约定,我希望我的逻辑保留在视图模型中 class。
我查看了 Caliburn.Micro 的文档,但我发现最接近命令的是 Actions。
我该如何实现?
感谢您的宝贵时间。
解决方案并不短!!
你需要创建一个依赖(这里是GestureMenuItem)
在 xaml 文件中
xmlns:common="clr-namespace:Common.Caliburn"
:
:
<Menu IsMainMenu="true" DockPanel.Dock="Top">
<common:GestureMenuItem x:Name="Save" Key="S" Modifiers="Ctrl" Header="_Save"/>
在 ActionMessageCommand.cs 文件中
using System;
using System.Windows.Input;
using Caliburn.Micro;
namespace Common.Caliburn
{
public class ActionMessageCommand : ActionMessage, ICommand
{
static ActionMessageCommand()
{
EnforceGuardsDuringInvocation = true;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
void ICommand.Execute(object parameter)
{
}
public event EventHandler CanExecuteChanged;
}
}
在 GestureMenuItem.cs 文件中
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace Common.Caliburn
{
public class GestureMenuItem : MenuItem
{
public override void EndInit()
{
Interaction.GetTriggers(this).Add(ConstructTrigger());
if(string.IsNullOrEmpty(InputGestureText))
InputGestureText = BuildInputGestureText(Modifiers, Key);
base.EndInit();
}
private static readonly IEnumerable<ModifierKeys> ModifierKeysValues = Enum.GetValues(typeof(ModifierKeys)).Cast<ModifierKeys>().Except(new [] { ModifierKeys.None });
private static readonly IDictionary<ModifierKeys, string> Translation = new Dictionary<ModifierKeys, string>
{
{ ModifierKeys.Control, "Ctrl" }
};
private static string BuildInputGestureText(ModifierKeys modifiers, Key key)
{
var result = new StringBuilder();
foreach (var val in ModifierKeysValues)
if ((modifiers & val) == val)
result.Append((Translation.ContainsKey(val) ? Translation[val] : val.ToString()) + " + ");
result.Append(key);
return result.ToString();
}
private TriggerBase<FrameworkElement> ConstructTrigger()
{
var trigger = new InputBindingTrigger();
trigger.GlobalInputBindings.Add(new KeyBinding { Modifiers = Modifiers, Key = Key });
var command = new ActionMessageCommand { MethodName = Name };
Command = command;
trigger.Actions.Add(command);
return trigger;
}
public static readonly DependencyProperty ModifiersProperty =
DependencyProperty.Register("Modifiers", typeof(ModifierKeys), typeof(GestureMenuItem), new PropertyMetadata(default(ModifierKeys)));
public ModifierKeys Modifiers
{
get { return (ModifierKeys)GetValue(ModifiersProperty); }
set { SetValue(ModifiersProperty, value); }
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(GestureMenuItem), new PropertyMetadata(default(Key)));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
}
}
在 InputBindingTrigger.cs 文件中
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using Caliburn.Micro;
namespace Common.Caliburn
{
public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
{
public InputBindingTrigger()
{
GlobalInputBindings = new BindableCollection<InputBinding>();
LocalInputBindings = new BindableCollection<InputBinding>();
}
public static readonly DependencyProperty LocalInputBindingsProperty =
DependencyProperty.Register("LocalInputBindings", typeof(BindableCollection<InputBinding>), typeof(InputBindingTrigger), new PropertyMetadata(default(BindableCollection<InputBinding>)));
public BindableCollection<InputBinding> LocalInputBindings
{
get { return (BindableCollection<InputBinding>)GetValue(LocalInputBindingsProperty); }
set { SetValue(LocalInputBindingsProperty, value); }
}
public BindableCollection<InputBinding> GlobalInputBindings
{
get { return (BindableCollection<InputBinding>)GetValue(GlobalInputBindingProperty); }
set { SetValue(GlobalInputBindingProperty, value); }
}
public static readonly DependencyProperty GlobalInputBindingProperty =
DependencyProperty.Register("GlobalInputBinding", typeof(BindableCollection<InputBinding>), typeof(InputBindingTrigger), new UIPropertyMetadata(null));
protected override void OnAttached()
{
foreach (var binding in GlobalInputBindings.Union(LocalInputBindings))
binding.Command = this;
AssociatedObject.Loaded += delegate
{
var window = GetWindow(AssociatedObject);
foreach (var binding in GlobalInputBindings)
window.InputBindings.Add(binding);
foreach (var binding in LocalInputBindings)
AssociatedObject.InputBindings.Add(binding);
};
base.OnAttached();
}
private Window GetWindow(FrameworkElement frameworkElement)
{
if (frameworkElement is Window)
return frameworkElement as Window;
var parent = frameworkElement.Parent as FrameworkElement;
return GetWindow(parent);
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
InvokeActions(parameter);
}
}
}
并在 ShellViewModel.cs 中,使用 Caliburn
的约定名称 public void Save()
{
//some code here
}
我找到了一个更短的解决方案。在 class 继承 ViewAware
时,受保护的虚方法 onViewReady
在视图就绪时用视图对象调用。
所以在我的视图模型中,我可以像这样覆盖它:
protected override void OnViewReady(object view) {
base.OnViewReady(view);
ShellView shellView = (ShellView)view;
shellView.CommandBindings.Add(new CommandBinding(ApplicationCommands.SaveAs, SaveAsCommandHandler));
}
使用视图对象,我可以通过指定特定命令和处理程序来添加新的 CommandBinding
实例。
处理程序可以在视图模型中定义class:
private void SaveAsCommandHandler(object sender, ExecutedRoutedEventArgs e) {
this.CurrentForm.SaveAs();
}