WPF Architecture Confusion RE: Routed Commands, Events, and Gestures

 ̄綄美尐妖づ 提交于 2019-12-07 22:40:17

问题


In learning WPF, I've been reading scads of books and web sites. One thing that seems to keep evading me is how we are supposed to properly wire up RoutedCommands. In one article, the author pointed out that the codebehind for your XAML files is supposed to contain nothing more than the call to InitializeComponent. I can get behind that. It makes the XAML file nothing more than a presentation document, and satisifies my unholy craving for separation of concerns.

On the other hand, every sample I've seen that addresses double-click events seems to want you to write code. It was my understanding that we wanted to get away from having repetitive code in the codebehind files (and, again, I'm all for that), so it seems to me that that's not the right way to do it. The same is true of menu commands, toolbar button clicks, and so forth.

Imagine, for instance, that I have a command to open a document. That command has to present the Open dialog, then open the document and cache it in the application state. (This application only allows you to work on one document at a time.) The user can invoke this command by either:

  • Choosing File->Open from the menu.
  • Typing Ctrl+O.
  • Clicking the Open button on the toolbar.

If I trust most of the sources on the Web, I have to write at least two Click event handlers that then invoke the command, polluting the codebehind file. That seems, to me, to defeat the purpose of having the Commands. I thought that I read somewhere that there was a way to bind the command to these things declaratively in XAML and it would do it for you, even disabling the command if it couldn't execute. But now I can't seem to find it, nor a decent example of how to do it.

Could someone please explain this to me? It's all starting to look like voodoo and shrapnel at this point.


回答1:


Commanding in WPF is quite cumbersome, but it does solve the problem of updating IsEnabled for you. Here's the canonical example. Step 1 is optional because there are a lot of built-in common commands to reduce the amount of boiler-plate.

Step 1. (Optional) Create your command in a static class

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)
            }
      );
   }
}

Step 2: Declare command bindings in the 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>

Step 3: Wire up your controls (menuitems, buttons etc)

The long binding here is to rectify the fact that Button by default won't use the command text.

  <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>

Step 4: Implement handlers for Execute and CanExecute in codebehind

Careful with CanExecute! This will be called quite often, so try not to do anything expensive here.

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

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



回答2:


The usual way to avoid codebehind with commands is to avoid RoutedCommands. In the various variations on the MVVM (Model-View-ViewModel) theme, people tend to use custom implementations of ICommand. They write a ViewModel class that is placed in the DataContext of the UI. This ViewModel exposes properties of type ICommand, and those command properties are connected to menu items, buttons and so on through data binding. (And it's usually just the one implementation of ICommand used over and over again - search the web for either RelayCommand or DelegateCommand or DelegatingCommand, and you'll see the pattern - it's basically ICommand as a wrapper around a delegate, with optional support for enabled/disabled.)

In this idiom, you almost never use the built-in commands like ApplicationCommands.Open. The only real use for those things is if you want focus-sensitive commands to be handled intrinsically by controls. E.g., the TextBox has built in command handling for Edit, Copy, Paste, and so on. This avoids the codebehind issue because it's a full custom control, and custom controls don't really have codebehind as such - they're all code. (The Xaml is actually in a completely separate object, the Template, and isn't really part of the control.) And in any case, it's not your code - you have a control that already knows how to support the command, so you can keep entirely within the Xaml here.

Command routing is interesting in that particular scenario because it lets you put one set of menu items associated with the various editing controls, and the routing system figures out which textbox (or whatever) will handle the command based on where the focus is. If that's not what you want, command routing probably isn't much use to you.

However, there's a bigger issue here of what to do when you find that you really do have to put code in the codebehind. Commands usually aren't an example of that scenario if you use custom ICommand implementations (although there's the odd exception), but the slightly more interesting user input events are. You mention double click, but also, if you're doing any kind of unusual interactivity you tend to want things like mouse up/down and so on.

In this case, the usual approach is to bite the bullet and put code in the codebehind, but you try to keep it to one line per event handler. Basically, your codebehind just calls into the relevant method on the viewmodel, and that's what really handles the event.

The lovely thing about that is it makes it really easy to write automated tests. Want to simulate the mouse entering a particular part of your UI? No need to mess around with unit test UI automation frameworks - just call the relevant method directly!



来源:https://stackoverflow.com/questions/4101729/wpf-architecture-confusion-re-routed-commands-events-and-gestures

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!