简体   繁体   English

如何在 Caliburn.Micro ViewModel 中处理 WPF MenuItem 命令?

[英]How to handle WPF MenuItem Commands in a Caliburn.Micro ViewModel?

My WPF application have a layout page with the following code:我的 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>

Since I'm using Caliburn.Micro, I've also implemented the corresponding view model:由于我使用的是Caliburn.Micro,所以我也实现了对应的视图model:

/* /ViewModels/ShellViewModel.cs */
public class ShellViewModel : Conductor<object> {
  // Control logic to manage ActiveItem
}

My objective is to implement an handler for the Save command, which I assume is triggered whenever the user click on the corresponding MenuItem or press CTRL + S .我的目标是为Save命令实现一个处理程序,我假设每当用户单击相应的MenuItem或按CTRL + S时都会触发该处理程序。

In standard WPF I would add a CommandBinding tag in ShellView.xaml (as shown in this tutorial ) to route the event to an handler I would implement within ShellView.xaml.cs .在标准 WPF 中,我将在ShellView.xaml中添加一个CommandBinding标记(如本教程所示),以将事件路由到我将在ShellView.xaml.cs中实现的处理程序。 However, for the sake of respecting Caliburn.Micro's MVVM conventions, I want my logic to stay within the view model class.但是,为了尊重 Caliburn.Micro 的 MVVM 约定,我希望我的逻辑保持在 model class 视图中。

I've looked into Caliburn.Micro's documentation but the closest thing I've found to commands were Actions .我查看了 Caliburn.Micro 的文档,但我发现最接近命令的是Actions

How can I implement that?我该如何实施?

Thanks for your time.谢谢你的时间。

The solution isnt short!!解决方案不短!!

You need to create a dependency (here is GestureMenuItem)您需要创建一个依赖项(这里是 GestureMenuItem)

in xaml file在 xaml 文件中


 xmlns:common="clr-namespace:Common.Caliburn"
 :
 :
  <Menu IsMainMenu="true" DockPanel.Dock="Top">
     <common:GestureMenuItem x:Name="Save" Key="S" Modifiers="Ctrl" Header="_Save"/>

in ActionMessageCommand.cs file在 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;
    }
}

in GestureMenuItem.cs file在 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); }
        }
    }
}

in InputBindingTrigger.cs file在 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);
        }
    }
}

and in ShellViewModel.cs, using the convention name of Caliburn在 ShellViewModel.cs 中,使用 Caliburn 的约定名称

    public void Save()
    {
        //some code here
    }

I found a shorter solution.我找到了一个更短的解决方案。 On class inheriting ViewAware , the protected virtual method onViewReady is called with the view object when the view is ready.在继承ViewAware的 class 上,当视图准备好时,使用视图 object 调用受保护的虚拟方法onViewReady

So in my view model I can just override it like so:所以在我看来 model 我可以像这样覆盖它:

protected override void OnViewReady(object view) {
    base.OnViewReady(view);

    ShellView shellView = (ShellView)view;
    shellView.CommandBindings.Add(new CommandBinding(ApplicationCommands.SaveAs, SaveAsCommandHandler));
}

With the view object, I can add a new CommandBinding instance by specifying the specific command and the handler.使用视图 object,我可以通过指定特定命令和处理程序来添加新的CommandBinding实例。

The handler can be define within the view model class:处理程序可以在视图 model class 中定义:

private void SaveAsCommandHandler(object sender, ExecutedRoutedEventArgs e) {
    this.CurrentForm.SaveAs();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM