Microsoft Belgi & Luxemburg - MSDN - The Command Pattern In Windows Presentation Foundation:

来源:百度文库 编辑:神马文学网 时间:2024/04/27 12:53:54
The Command Pattern In Windows Presentation FoundationJelle DruytsApplies to:Visual Studio 2005Windows Presentation Foundation (November 2005 CTP)Summary: Windows Presentation Foundation (formerly codenamed "Avalon"), or WPF for short, is a brand new Microsoft framework for developing very rich and powerful Windows applications. It will ship as part of Windows Vista, the next major version of Windows that will be released in the coming months, but WPF will also be available on Windows XP SP2 and Windows Server 2003. There is much to be said about Windows Presentation Foundation and its numerous new and enhanced capabilities, but this article will in stead focus on an old trusted friend, who has finally been given a dedicated room in the big house of Windows User Interface development: the "Command" pattern. This design pattern basically abstracts all actions the user can perform in an application into the notion of "commands"; it has been implemented in many different ways on top of various UI frameworks, but now, it has finally made it into the gut of the system itself. Note that this article is based on a public preview of WPF, so it‘s possible that there are implementation details that will change over time as the product matures into completion.Contents: The Command Design PatternCommands In WPFExecuting CommandsHandling CommandsEnabling CommandsWrapping UpAbout the authorThe Command Design PatternAccording to its original description in the famous "Design Patterns" book by the so-called Gang of Four, the Command pattern allows you to "encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations". Because design patterns can, by definition, be applied to a whole range of different scenarios, this is a rather general description. Let‘s refine that definition in the WPF space as "an action that a user can perform through the keyboard or by interacting with a user interface element". If you look at a modern Graphical User Interface (GUI) application like Visual Studio, you can recognize "Open File", "Copy", "Paste", "Build Solution", "New Project", "Attach to Process" and many, many more as commands: they are all actions you perform by clicking a toolbar button or a menu item, or by pressing a keyboard shortcut. If you think about these commands from an application perspective, they all support a number of common behaviors: they are typically accessible through multiple user interface elements (e.g. both a toolbar button and a menu item), they are grayed out (disabled) when they‘re currently inaccessible, they support "undo" behavior (rolling back the last command), and they can be executed in batch (a macro, for example, isn‘t much more than a sequential series of commands). As soon as developers find common behaviors, reusable libraries and frameworks emerge that handle these requirements centrally. For large applications, it would of course be sub-optimal (which is a nice word for incredibly wrong) to duplicate the code for every command behind the event handler of all user interface elements that enable this command. Would you want to maintain the code for Microsoft Word 2003 if all of its more than 1500 commands would be duplicated for every button and menu item that enabled them? Looking at these commands from a developer‘s perspective, they effectively separate the execution of a command from its invoker. Any command can be implemented as a single object with an Execute method that implements its behavior (e.g. make the selected text bold). The event handlers of the UI elements don‘t perform any work themselves, they just delegate it to the command they‘re bound to when they‘re invoked (e.g. when the toolbar button is clicked). If the application supports undo behavior, the same object can define an Unexecute method to roll back the effects of the Execute method if possible (e.g. remove the bold face from the text). As an added bonus, the object can be queried to see if it‘s valid, and the appropriate UI elements can be disabled to reflect the fact that they‘re inaccessible. Commands In WPFNow, let‘s get our hands dirty and look at how commands are implemented in Windows Presentation Foundation. The ICommand InterfaceCommands in WPF are implementations of the System.Windows.Input.ICommand interface. This interface defines three members: public interface ICommand{ // Methods. void Execute(object parameter); bool CanExecute(object parameter); // Events. event EventHandler CanExecuteChanged;}The Execute method contains the logic to perform the action that makes up the command (e.g. make the selected text bold). The CanExecute method returns a value that determines if the command is currently valid (e.g. if there is any selected text). The CanExecuteChanged event is raised when the command notices that the value returned by the CanExecute method has changed (e.g. there is no more selected text). These last two members allow user interface elements to check if they should be enabled, and as we will see later on, WPF handles these automatically to gray them out. The parameter object on the two methods is just a generic way to pass any information into the command. As you can see, there is no Unexecute method or any other built-in mechanism to support undoable operations in WPF, but you can handle the predefined Undo and Redo commands for your own purposes (like the TextBox class does now to support undo and redo for text editing). The RoutedCommand ClassIn WPF, there is currently only one class that implements this interface: the System.Windows.Input.RoutedCommand class. public class RoutedCommand : ICommand{ // ICommand implementation. void ICommand.Execute(object parameter); bool ICommand.CanExecute(object parameter); public event EventHandler CanExecuteChanged; // Constructors. public RoutedCommand(); public RoutedCommand(string name, Type ownerType); public RoutedCommand(string name, Type ownerType, InputGestureCollection inputGestures); // Methods. public void Execute(object parameter, IInputElement target); public bool CanExecute(object parameter, IInputElement target); // Properties. public InputGestureCollection InputGestures { get; } public bool IsBlockedByRM { get; set; } public string Name { get; } public Type OwnerType { get; }}Apart from implementing the ICommand interface, it defines a number of constructors, methods and properties to provide the actual baseline functionality of a command. As you can see, each command has a Name (e.g. "Copy"), an OwnerType (containing the type that defines the command), an IsBlockedByRM flag (which indicates if the command is disabled due to a Digital Rights Management policy, e.g. a copy protection) and a collection of InputGestures (which specify mouse or keyboard combinations such as CTRL-C, which are one of many ways the command can be invoked). The "routed" part of the class name comes from the fact that many event-like behaviors in WPF are described as routed. With regular events in .NET, an event is ignored completely when there are no registered event handlers. This means you have to subscribe to all events of all objects you are potentially interested in. For large UI applications with lots of controls, this could mean you‘d have to subscribe to tens or hundreds of events. To make it easier to handle events centrally – even though they could have initiated anywhere in the huge visual tree that makes up a UI application – WPF introduces the notion of routed events. This means that if an event isn‘t handled on the element that defined it, it bubbles up the visual tree until it reaches the root element. (Tunneling events also exist, which travel from the root element down to the initiating element, but this is outside the scope of this article.) The same bubbling mechanism applies to the RoutedCommand: if no handler is found to deal with the command, it travels up the visual tree until it is handled. The RoutedUICommand ClassThere is one specialized subclass of this RoutedCommand class, the RoutedUICommand class, which adds nothing but a Text property to provide a localized description of the command so it can be displayed in the UI of the application (e.g. as a button‘s tooltip): public class RoutedUICommand : RoutedCommand{ // Constructors. public RoutedUICommand(); public RoutedUICommand(string text, string name, Type ownerType); public RoutedUICommand(string text, string name, Type ownerType, InputGestureCollection inputGestures); // Properties. public string Text { get; set; }}Since WPF is a UI framework, most commands will in fact be instances of this RoutedUICommand. Built-in CommandsThere are currently 156 predefined commands in WPF to support the most common scenarios. These are all RoutedUICommands, grouped as static properties on 5 different classes. ApplicationCommands: a set of standard commands for an application, e.g. Copy, Paste, Undo, Redo, Find, Open, SaveAs, PrintPreview, ... ComponentCommands: a set of commands that are frequently used by user interface components, e.g. MoveLeft, MoveToEnd, ScrollPageDown, ... MediaCommands: a set of commands used for multimedia, e.g. Play, Pause, NextTrack, IncreaseVolume, ToggleMicrophoneOnOff, ... NavigationCommands: a set of commands used for page navigation, e.g. BrowseBack, GoToPage, NextPage, Refresh, Zoom, ... EditingCommands (in the System.Windows.Documents namespace): a set of commands used for document editing, e.g. AlignCenter, ApplyFontSize, EnterPageBreak, ToggleBold, ... Since these commands are all static, they should in fact be used as singletons: there is only one globally available ApplicationCommands.Copy command for example. This will become important later on, as we will see when handling commands in a user interface. Because these commands are predefined and well-known throughout the WPF framework itself, a lot of these will be handled automatically by the controls that ship with the framework. The TextBox control, for example, knows how to handle the Cut, Copy and Paste commands. Custom CommandsOf course, while these built-in commands already provide an extensive set of commonly used functionality, you will probably want to define commands specific to your application as well. Fortunately, defining a custom command is very straightforward if you follow the pattern of the predefined commands: just create a new instance of a RoutedCommand or a RoutedUICommand (depending on whether the command will be "visible" on a UI or not) and make this available through a static property or field on the class that defines the command. Imagine, for example, a Customer window that needs to define a command to add a new customer: public partial class CustomerWindow : Window{ public readonly static RoutedUICommand AddCustomerCommand; static CustomerWindow() { AddCustomerCommand = new RoutedUICommand("Add Customer...", "AddCustomerCommand", typeof(CustomerWindow)); AddCustomerCommand.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); }}As you can see, we expose a static singleton instance of a RoutedUICommand called AddCustomerCommand, and we initialize it in the static constructor to have a UI Text "Add Customer...", a Name that‘s the same as the field name, and the OwnerType will be set to the defining type, i.e. the CustomerControl type. Furthermore, we add a KeyGesture to the command‘s InputGestures to indicate that the command can be executed by pressing the Alt-C shortcut. Executing CommandsNow that we‘ve seen which commands are available out-of-the-box and how commands are defined, let‘s see how we can execute a command. Input GesturesThere are several ways that a command can be invoked, and we‘ve already seen one of them: the InputGestures collection of a RoutedCommand defines the possible mouse or keyboard combinations that trigger the command (e.g. CTRL-C and CTRL-INS for the Copy command). These are defined by the command itself so they are inherent to its behavior. Input BindingsBut what if you want to assign a different keyboard shortcut for that command within the scope of a specific window or control? Every UI element has an InputBindings property you can use to assign new input gestures to existing commands for the scope of that UIElement. Within our Customer window, for example, we can assign the CTRL-F6 shortcut to the Copy command as follows: public partial class CustomerWindow : Window{ public CustomerWindow() { InitializeComponent(); this.InputBindings.Add(new InputBinding(ApplicationCommands.Copy, new KeyGesture(Key.F6, ModifierKeys.Control))); }}Pressing CTRL-C, CTRL-INS or CTRL-F6 in this window will now all execute the Copy command. Calling The Execute MethodYou can also invoke a command through code, by calling the Execute method defined on the RoutedCommand class. Suppose that we have a button on our Customer window named AddCustomerButton, defined in XAML as follows: Add Customer...We could then write the following code in its event handler to invoke the AddCustomerCommand: void AddCustomerButtonClicked(object sender, EventArgs e){ AddCustomerCommand.Execute(null, (IInputElement)sender);}As you may recall from the RoutedCommand definition above, the first argument is the command parameter (any optional parameter you want to pass along), the second is the target of the routed command, i.e. the UI element where the bubbling starts. This can be important because commands can be handled at different places in the UI tree, as we will see later on. If you don‘t provide a target, the UI element that currently has the keyboard focus will be used as the command target. Alternatively, if you don‘t have a reference to a RoutedCommand but to an ICommand in stead, you can call its Execute method as well. The difference is that it doesn‘t have the second argument to define the target element, but if it‘s a RoutedCommand (and it probably is, since WPF doesn‘t provide any other implementation of the ICommand interface), the UI element with the focus will again be used as the target. void AddCustomerButtonClicked(object sender, EventArgs e){ ICommand cmd = AddCustomerCommand; cmd.Execute(null);}Using The Command PropertyOf course, executing a command when a button or menu item is clicked is a very common scenario, so WPF provides a Command property on these UI elements that eliminates the code above: CopyThis will simply execute the ApplicationCommands.Copy command when the button is clicked. Because "Copy" is a well-known predefined command, it‘s enough to just use its short name. If you want to use custom commands, you‘ll have to specify the XML namespace prefix and the class name as well: Add Customer...For this to work, you will need to make this "local" XML namespace prefix refer to the current project‘s CLR namespace. This can be done through the standard XAML mechanism of adding a Mapping Processing Instruction (PI) to the top of your XAML file, and a namespace mapping on the Window tag. The complete XAML for the Customer window can now look as follows: Add Customer... This way, when the button is clicked, the AddCustomerCommand of the CustomerControl class in the CustomControls namespace will be executed. Optionally, in case this class is defined in another assembly, you can specify its name in the Assembly attribute of the Mapping PI. This Command property is defined on the System.Windows.Controls.Primitives.ButtonBase class, so all its descendants like Button, RadioButton and CheckBox can directly use it. It‘s also present on the System.Windows.Controls.Primitives.MenuItem class so the same applies for menu items. And finally, the System.Windows.Documents.Hyperlink class also supports the property so you can also invoke commands from hyperlinks. Besides the Command property, these types also define CommandParameter and CommandTarget properties, which allow you to set the Execute method‘s parameter and target arguments, respectively. Handling CommandsNow that we know how commands are defined and how they can be executed, let‘s switch over to the consumer side and see how commands can be handled when they are invoked. Remember that commands are singletons, which means there is only one instance of the command in the entire application. Suppose the command instance had an Executed event you could subscribe to, this would allow you to run some code whenever the command was invoked. However, since there can be multiple windows in an application at any given time, this would mean the event handlers would be invoked on all windows when the command is invoked in one window (because there is only one instance of the command). Imagine Visual Studio pasting the same text in all open documents when the Paste button is clicked. Of course, this is undesirable, so we need a way to constrain a command handler to a part of the user interface. Command BindingsThat‘s where command bindings come into play: these tie the invocation of a command to a UI scope, such as a single window or a panel. This mechanism works through the use of CommandBinding instances, which are defined as follows: public class CommandBinding{ // Events. public event CanExecuteRoutedEventHandler CanExecute; public event ExecutedRoutedEventHandler Executed; public event CanExecuteRoutedEventHandler PreviewCanExecute; public event ExecutedRoutedEventHandler PreviewExecuted; // Methods. public CommandBinding(); public CommandBinding(ICommand command); public CommandBinding(ICommand command,ExecutedRoutedEventHandler executed); public CommandBinding(ICommand command,ExecutedRoutedEventHandler executed, CanExecuteRoutedEventHandler canExecute); // Properties. public ICommand Command { get; set; }}A command binding basically keeps track of a command, and provides four routed events that allow you to preview or handle the invocation of the event (PreviewExecuted and Executed, respectively), and that provide a means for you to indicate if the command can currently execute (PreviewCanExecute and CanExecute, respectively). You can add command bindings to any UIElement or ContentElement in WPF through their CommandBindings property as such: public partial class CustomerWindow : Window{ public CustomerWindow() { InitializeComponent(); CommandBinding binding = new CommandBinding(ApplicationCommands.Copy); binding.Executed += new ExecutedRoutedEventHandler(this.copy_Executed); this.CommandBindings.Add(binding); } void copy_Executed(object sender, RoutedEventArgs e) { MessageBox.Show("Executed the Copy command"); }} This will add an event handler to the Copy command to display a message box, Alternatively, you can declare command bindings in XAML through the standard Property-Element syntax: It‘s important to note that if there are no command bindings for a certain command, then the command will effectively be disabled. This makes sense, because there is no point in clicking a button if there is no code behind it to execute. Handling The Executed EventAs you‘ve seen, the CommandBinding class defines a number of events, the most important one being the Executed event. An event handler for this event (and for its preview event as well) must take a sender of type object as its first argument (following the standard event pattern in .NET) and an event arguments object of type System.Windows.Input.ExecutedRoutedEventArgs.The sender object will always be the control defining the command bindings, not the control that triggered the command. If a button on a window has its Command property set, for example, and the window itself has defined the command binding for that command (like in the example above), the sender would be the window instance, not the button.The ExecutedRoutedEventArgs that is passed into the event handler contains more useful information, especially if you need to know which control triggered the command. It inherits from the standard WPF RoutedEventArgs class to provide an additional Command and Parameter property, so its public interface looks like the following: public sealed class ExecutedRoutedEventArgs{ // Properties public object OriginalSource { get; } public object Source { get; set; } public bool Handled { get; set; } public RoutedEvent RoutedEvent { get; set; } public ICommand Command { get; } public object Parameter { get; }}The read-only OriginalSource property contains the control that triggered the event, so this will contain the button that was clicked, for example. The read-write Source property contains the same control at first, but can be replaced at will to hide a low-level UI element within a composite control, for example. The Handled flag can be used to indicate if the command was handled. This can be used to stop the command from bubbling up the visual tree when it has already been dealt with. When you press CTRL-C in a TextBox that has some selected text, for example, the text will be copied to the clipboard and the event will be marked as handled so it won‘t surface beyond the TextBox. If no text was selected, nothing could be done by the TextBox so the event will bubble further up the visual tree. The last two properties allow you to retrieve some information about the command being executed. The Command property contains the command instance that was executed and whose event is now being routed. The Parameter property contains the optional parameter that was passed into the command when it was executed (through the Execute call or the CommandParameter property of a Button, for example). Enabling CommandsAs mentioned above, if there are no command bindings for a certain command, then the command will effectively be disabled. Apart from not executing any command that isn‘t enabled, WPF will automatically disable the well-known UI elements that are bound to a command that isn‘t enabled. That means that all controls that have a Command property as mentioned above (including menu items and buttons), will automatically reflect the fact that the command is enabled or not. This is an important feature because it means you don‘t need to manage any part of the user interface yourself when dealing with disabled commands. From the moment you add at least one command binding, the associated command will become available (assuming there is no Digital Rights Management policy blocking it). But there may be times when you need more control over when a certain command is enabled. Maybe you don‘t want that "Add Customer" command to be available when there‘s no current account manager to add the customer to. That‘s where the CanExecute event of the CommandBinding class proves useful. You can attach event handlers to it to cast a vote anytime WPF needs to know if the command is enabled. The event handler for the CanExecute event follows the same pattern as the Execute event. In addition to all the properties available on the ExecutedRoutedEventArgs class mentioned above, the CanExecuteRoutedEventArgs object used here adds one extra property: the CanExecute flag. This is a read-write boolean property that determines if the command can currently be executed on a certain target. You can simply set the property to false to indicate that it shouldn‘t be enabled at this time: void addCustomer_CanExecute( object sender, CanExecuteRoutedEventArgs e ){ if( accountManagersList.SelectedIndex