WPF Architecture Confusion RE: маршрутизируемые команды, события и жесты - PullRequest
2 голосов
/ 05 ноября 2010

Изучая WPF, я читал множество книг и веб-сайтов. Одна вещь, которая, кажется, продолжает уклоняться от меня, это то, как мы должны правильно подключить RoutedCommands. В одной статье автор указал, что код для ваших файлов XAML должен содержать не что иное, как вызов InitializeComponent. Я могу получить это. Это делает файл XAML не более чем презентационным документом и удовлетворяет мою безобразную тягу к разделению интересов.

С другой стороны, каждый пример, который я видел, посвященный событиям двойного щелчка, хочет, чтобы вы написали код. Насколько я понимаю, мы хотели избежать повторяющегося кода в файлах кода (и опять-таки я за это), поэтому мне кажется, что это неправильный способ сделать это. То же самое относится и к командам меню, щелчкам кнопок на панели инструментов и т. Д.

Представьте, например, что у меня есть команда, чтобы открыть документ. Эта команда должна представить диалоговое окно «Открыть», затем открыть документ и кэшировать его в состоянии приложения. (Это приложение позволяет одновременно работать только с одним документом.) Пользователь может вызвать эту команду одним из следующих способов:

  • Выбор файла-> Открыть из меню.
  • Набрав Ctrl + O.
  • Нажатие кнопки Открыть на панели инструментов.

Если я доверяю большинству источников в Интернете, мне нужно написать как минимум два обработчика событий Click, которые затем вызывают команду, загрязняя файл codebehind. Мне кажется, чтобы победить цель наличия команд. Я подумал , что где-то читал, что есть способ привязать команду к этим вещам декларативно в XAML, и она сделает это за вас, даже отключив команду, если она не может быть выполнена. Но теперь я не могу найти ни приличного примера того, как это сделать.

Может кто-нибудь объяснить мне это? С этого момента все начинает выглядеть как вуду и шрапнель.

Ответы [ 2 ]

4 голосов
/ 05 ноября 2010

Обычный способ избежать кодирования позади команд - это избегать RoutedCommands. В различных вариациях темы MVVM (Model-View-ViewModel) люди склонны использовать пользовательские реализации ICommand. Они пишут класс ViewModel, который помещается в DataContext пользовательского интерфейса. Эта ViewModel предоставляет свойства типа ICommand, и эти свойства команды связаны с элементами меню, кнопками и т. Д. Через привязку данных. (И обычно это всего лишь одна реализация ICommand, используемая снова и снова - ищите в Интернете либо RelayCommand, либо DelegateCommand, либо DelegatingCommand, и вы увидите шаблон - это в основном ICommand как оболочка для делегата с необязательной поддержкой включенного /disabled.)

В этой идиоме вы почти никогда не используете встроенные команды, такие как ApplicationCommands.Open. Единственное реальное использование для этих вещей, если вы хотите, чтобы чувствительные к фокусу команды обрабатывались внутренне средствами управления. Например, TextBox имеет встроенную обработку команд для редактирования, копирования, вставки и т. Д. Это позволяет избежать проблемы codebehind, поскольку это полностью настраиваемый элемент управления, а настраиваемые элементы управления на самом деле не имеют codebehind как таковые - все они являются кодом. (На самом деле Xaml находится в совершенно отдельном объекте, шаблоне, и на самом деле не является частью элемента управления.) И в любом случае, это не ваш код - у вас есть элемент управления, который уже знает, как поддерживать команду, поэтому вы здесь можно целиком и полностью оставаться в пределах Xaml.

Командная маршрутизация интересна в этом конкретном сценарии, поскольку она позволяет вам поместить один набор элементов меню, связанных с различными элементами управления редактированием, и система маршрутизации выясняет, какое текстовое поле (или что-то еще) будет обрабатывать команду, основываясь на том, где находится фокус , Если это не то, что вам нужно, командная маршрутизация, вероятно, не слишком полезна для вас.

Однако здесь возникает большая проблема, что делать, когда вы обнаружите, что вам действительно нужно поместить код в коде позади. Команды обычно не являются примером этого сценария, если вы используете пользовательские реализации ICommand (хотя есть странное исключение), но есть несколько более интересные события пользовательского ввода. Вы упоминаете двойной щелчок, но также, если вы делаете какую-то необычную интерактивность, вам, как правило, нужны такие вещи, как мышь вверх / вниз и т.д.

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

Приятно то, что это действительно облегчает написание автоматизированных тестов. Хотите смоделировать ввод мыши в определенную часть вашего интерфейса? Нет необходимости возиться с инфраструктурой автоматизации пользовательского интерфейса модульного тестирования - просто вызовите соответствующий метод напрямую!

2 голосов
/ 05 ноября 2010

Командование в WPF довольно громоздко, но оно решает проблему обновления IsEnabled для вас.Вот канонический пример.Шаг 1 является необязательным, поскольку существует множество встроенных общих команд для уменьшения количества котельной пластины.

Шаг 1. (Необязательно) Создайте команду в статическом классе

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfApplication1
{
   public static class Commands
   {
      public static RoutedCommand WibbleCommand = new RoutedUICommand
      (
         "Wibble",
         "Wibble",
         typeof(Commands),
         new InputGestureCollection()
            {
               new KeyGesture(Key.O, ModifierKeys.Control)
            }
      );
   }
}

Шаг 2. Объявите привязки команд в xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
   <Window.CommandBindings>
      <CommandBinding
         Command="{x:Static local:Commands.WibbleCommand}"
         Executed="WibbleCommandExecuted"
         CanExecute="WibbleCommandCanExecute"
      />
   </Window.CommandBindings>

Шаг 3: Проводваши элементы управления (элементы меню, кнопки и т. д.)

Длинная привязка здесь заключается в том, чтобы исправить тот факт, что Button по умолчанию не будет использовать текст команды.

  <Button Command="{x:Static local:Commands.WibbleCommand}" Width="200" Height="80">
     <TextBlock Text="{Binding Path=Command.Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}">
     </TextBlock>
  </Button>

Шаг 4: Реализуйте обработчики для Execute и CanExecute в codebehind

Осторожнее с CanExecute!Это будет называться довольно часто, поэтому постарайтесь не делать ничего дорогого здесь.

  private void WibbleCommandExecuted(object sender, ExecutedRoutedEventArgs e)
  {
     MessageBox.Show("Wibbled!");
  }

  private void WibbleCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
     e.CanExecute = DateTime.Now.Minute % 2 == 0;
  }
...