Сочетания клавиш MenuItem в «чистом» MVVM? - PullRequest
5 голосов
/ 23 декабря 2011

Все меню / контекстные меню / панели инструментов, которые я использую в wpf, объявлены в коде ViewModel примерно так:

MenuService.Add( new MenuItem()
  {
    Header = "DoStuff",
    Command = new relayCommand( DoStuff, () => CanDoStuffExecute() )
    // some more properties like parent item/image/...
  } );

MenuService предоставляет одну точку привязки, которая представляет собой иерархический список MenuItem и привязывается кФактический ItemSource меню в xaml.

Это работает очень хорошо, и теперь я хотел бы добавить сочетания клавиш таким же удобным способом.В идеале MenuItem получит свойство типа System.Windows.Input.KeyGesture, поэтому я могу просто написать

Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )

, что приведет к вызову команды элемента при нажатии Ctrl + D в окне, которое владеет меню, ичто также привело бы к автоматическому отображению "Ctrl + D" в меню.

Однако я здесь потерян: я хотел установить коллекцию MenuItem.InputBindings через привязку данных, но она только для получения.Как я могу получить предметы в этом в любом случае?Или есть фреймворк MVVM, который уже поддерживает что-то подобное?Большинство вопросов и ответов, которые я нашел по сочетаниям клавиш, касаются настройки сочетаний клавиш через xaml, что не поможет.

Обновление

Поиск 'relaycommand vs routeduicommand и' relaycommandkeygesture и т. д. действительно выявили достаточно информации, чтобы найти работающее, хотя и хакерское решение.Есть определенно другие и лучшие пути, но на данный момент это крайне низкий приоритет для меня и отлично справляется со своей работой.Я добавил два свойства в класс MenuItem следующим образом:

//Upon setting a Gesture, the original command is replaced with a RoutedCommand
//since that automatically gives us proper display of the keyboard shortcut.
//The RoutedCommand simply calls back into the original Command.
//It also sets the CommandBinding property, which still has to be added to the
//CommandBindingCollection of either the bound control or one of it ancestors
public InputGesture Gesture
{
  set
  {
    var origCmd = Command;
    if( origCmd == null )
      return;
    var routedCmd = new RoutedCommand( Header,
      typeof( System.Windows.Controls.MenuItem ),
      new InputGestureCollection { value } );
    CommandBinding = new CommandBinding( routedCmd,
      ( sender, args ) => origCmd.Execute( args.Parameter ),
      ( sender, args ) => { args.CanExecute = origCmd.CanExecute( args.Parameter ); } );
    Command = routedCmd;
  }
}

//CommandBinding created when setting Gesture
public CommandBinding CommandBinding { get; private set; }

Так что это дает функциональность, о которой я просил изначально (например, добавление сочетаний клавиш в код, где они легко настраиваются и т. Д.).Осталось только зарегистрировать привязки команд.На данный момент это делается путем простого добавления всех их в Application.Current.MainWindow.CommandBindings.

1 Ответ

3 голосов
/ 16 апреля 2014

Это на самом деле не считается «ответом» (я не могу явно добавить комментарий), но я бы сказал, что то, что вы делаете, не является предполагаемым методом в WPF.Вы делаете это способом Windows Forms (и, как и во многих других инструментах) - определяете свой UX в коде.Вы продвинулись так же далеко, как и раньше, но теперь вы наткнулись на кирпичную стену: ключевые жесты - чисто UX, определенно не должны быть указаны в code-behind.Внешний вид (как функция модели представления) и взаимодействие с ним пользователя (способы выполнения заданной команды) предназначены для определения XAML.

Значения свойств, а команды - для вашего представления.модель, так что вы можете повторно использовать эту модель представления для других представлений, а также легко создавать для нее модульные тесты.Как реализация сочетаний клавиш в модели представления поможет тестируемости?А для использования в других представлениях можно утверждать, что фактические ярлыки могут не относиться к новому представлению, так что они не принадлежат.Конечно, у вас могут быть и другие причины, но я бы посоветовал вам просто определить их в XAML.

-Добавлено в ответ на ваш комментарий-

Вы совершенно правы - иЯ видел несколько довольно крупных проектов WPF UX, которые изо всех сил пытались избежать какого-либо кода и оказывались излишне тупыми.Я пытаюсь использовать любой подход, который дает рабочий результат, и настолько прост, насколько я могу его получить.

Вот пример фрагмента, который просто создает MenuItem ..

<Menu x:Name="miMain" DockPanel.Dock="Top">
    <MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>

Этосоздает меню.Здесь MyGreatCommand - это ICommand, и это просто свойство модели представления.Я обычно помещаю это в DockPanel, для обработки StatusBar и т. Д.

Для назначения жеста клавиши.

<Window.InputBindings>
    <KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>

Однако, поскольку вы упомянули, что выуже искал ответы и нашел только XAML - я полагаю, вы уже пробовали этот подход.Я использовал RoutedUICommand s вместо пользовательских ICommand s, чтобы получить этот красивый выровненный по правому краю жест в тексте заголовка, но я не нашел, как сделать то и другое.Если вы настаиваете на создании команд и жестов клавиш в коде, возможно, вам придется создать RoutedUICommand s.

Почему вы хотите установить жесты клавиш, отличные от вашего XAML?

Если вы хотите, чтобы некоторые пункты меню появлялись только в том случае, если в вашей view-модели влияют определенные состояния, то вы можете привязать свойство Visibility элемента меню (которое может содержать другие пункты меню) к Collapsed или Visible.

...