[Prism] Composite Application Guidance for WPF (9) -- command
Zhou yinhui
The "command" here is the "Command" in command mode. Almost every applicationProgramThis mode is used to copy, paste, undo, and other operations. We know that this mode isolates the operation requestor from the Operation execution logic, and it has good support for request queuing and repeated revocation operations, so it is widely used. While WPF further encapsulates and improves it, so that the WPF program can easily use commands and build custom commands. In addition, the dozens of built-in commonly used commands and advanced command routing modes (routed) in WPF make all of these easy and efficient.
Is the WPF command (routedcommand and routeduicommand) enough to solve all the problems? This is not the case. In fact, there are many shortcomings, that is why prism wants to build its own command. This article will briefly discuss these issues.
------------------------------- WPF command ----------------------------
1. Understand "routed" in WPF routedcommand)
WPF provides routedcommand and routeduicommand commands. routeduicommand inherits from routedcommand and opens msdn. We can see this explanation: "It defines a command to implement icommand and route through the element tree, the execute and canexecute methods on the routedcommand do not contain the application logic of the command (for example, a typical icommand is like this). Instead, they will trigger events that traverse the element tree to find objects with commandbinding. The event handler appended to commandbinding contains the command logic ." This is the so-called "routing": it searches for commandbinding on a specific element tree (actually a visual tree) path, then, call the canexecute and execute of commandbinding to determine whether the command can be executed and how to execute the command. What is the path like? See the following example:
Buttons in the above two images Code Shape
<Button command = "copy" content = "{binding Path = command. Text, relativesource = {relativesource self}"/>
First, look at the first figure. When the text in the textbox is selected, the "copy" button on the toolbar is enabled, but the button on the right of the textbox is not enabled;
In the second figure, when the text in flowdocument is selected, both the buttons in flowdocument and the ELE. Me buttons in the toolbar are enabled. The button next to the text box is never enabled.
Why?
Observe the main element tree involved in the program (Omitted not important), in which there are settings for applicationcommands. the commandbinding element of copy is "text box" and "flowdocument (actually its viewer)". When the command of "button on the right of the text box" is set to "copy, it searches for applicationcommands in the root direction of the element tree from its location. the commandbinding element of copy is not found, so this button is always disabled. Similarly, the button in flowdocument looks at the root of the element. OK, it finds applicationcommands on flowdocumentpageviewer. copy commandbinding, so it will call canexecute and execute in the commandbinding of flowdocumentpageviewer to determine whether the command can be executed and how to execute the command. However, why can the "toolbar button" be enabled? This is because the search direction for commandbinding along the element tree can be reversed in addition to the above lookup from the element to the root: find the element from the element root (here our "window") to the element direction, and the buttons on the toolbar adopt this special method. When to use this method: This method is used when the element is in the toolbar, menu bar, or focusmanager. isfocusscope of the element is set to "true". Otherwise, the former method is used by default.
If we set "button on the right of the text box" <button focusmanager. isfocusscope = "true" command = "copy" content = "{binding Path = command. text, relativesource = {relativesource self} "/>, and then look at the effect (the button on the right of the text box can be enabled ):
Routedcommand has one of the following limitations:
When the software UI is complex, its routing method will make developers dizzy and prone to errors, especially when the command element is not in the toolbar or menu bar. One way to compromise is to design the commandtarget attribute of the command and specify the target of the command. However, this does not seem easy to achieve in Composite Application. Unless view exposes the Command target or provides the corresponding API, it is unknown what to expose, because you don't know which elements will be used as commandtarget by other modules.
2. Understand the commandbinding of WPF
Commandbinding plays the matchmaking role. It associates command, canexecute (actual judgment logic) with execute (actual execution logic). When a specified command is mounted to the class where the actual logic is located, then canexecute and execute register to the specified command. In WPF, The commandbindings attribute allows you to add commandbinding to a commandbinding of the following types: uielement, contentelement, and uielement3d. To put it bluntly, they are all visual elements. Why? The built-in support of WPF is routedcommand. It doesn't matter if it is not a visualization element. Even if you use the commandmanager. registerclasscommandbinding (type, commandbinding) method, you should still pass in the three visible elements above. In this case, the commandbinding of WPF is seriously coupled with the view layer elements. You cannot directly mount the canexecute and execute of command to other non-UI Layer classes.
Routedcommand limit 2:
It is coupled with the UI element. You cannot directly mount the canexecute and execute commands to other classes that are not on the UI Layer. This makes it inconvenient for applications that adopt MVC and MVP modes, unless you add a canexecute and execute handler for each command on the top-level window or view Root Using commandbinding on The View layer, the handler then calls the corresponding methods of other layers.
3. Combined commands
Command combinations are often used. For example, you may have stored dozens of steps to undo a stack when editing text, this is where the application may provide a "undo all" button to undo all of these operations. (Note: You may mistakenly think that this is only a for loop for multiple Undo operations. If you click the "undo all" button, click "repeat" to see it. It is actually the implementation of the "Combination Mode ). However, the built-in routedcommand of WPF does not support command combinations.
Routedcommand limited to three
The combination mode is not supported. This makes complex UI operations inconvenient. For example, there are two text boxes and each corresponding Save button on the interface. You can click the Save button to save the text of the corresponding text box. However, it is not that easy to add a "saveall" button on the menu bar.
4. Attach multiple canexecute and execute logics to a command.
The routedcommand of WPF does not support multiple canexecute and execute logic mounts. For example, you cannot call a command to paste a piece of text into two text boxes at the same time. Routedcommand calls only one processor at a time (for example, the current focus winner in two text boxes ). This is why you don't have to worry about finding two reasons at the same time when you use the default route for commandbinding lookup.
Routedcommand limitations 4
Multiple processing logic cannot be attached. Multiple processing logic connections are often useful in complex UIS, which is why many manufacturers write their own command modes to meet their specific needs.
For more information about WPF commands, see bind commands in WPF to commands (1) bind commands to commands in WPF (2)
------------------------------- Create your own command ----------------------------
The custom command is relatively simple. It mainly involves two aspects: first, finding a place to store the canexecute and execute agents mounted outside, and second, when to trigger the canexecutechanged event. The latter is very important and prone to errors. If canexecutechanged is not correctly triggered, it will cause errors when deciding whether to execute the command execute method, the most intuitive display on the UI is that the UI element is disabled and the activation status is abnormal.
The following code creates a command that can be attached with multiple canexecute and execute proxies. That is to say, the multiple processing methods you can register with them will be called in sequence when the commands are execute:
Public class greetingscommand: icommand
{
Private func <bool> canexecutehandler = () => false;
Private action <string> executehandler = OBJ => {};
Public event eventhandler canexecutechanged = (sender, e) => {};
Public event func <bool> canexecutehandler
{
Add
{
Canexecutehandler + = value;
Canexecutechanged (this, eventargs. Empty );
}
Remove
{
Canexecutehandler-= value;
Canexecutechanged (this, eventargs. Empty );
}
}
public event action executehandler
{< br> Add
{< br> executehandler + = value;
}< br> remove
{< br> executehandler-= value;
}< BR >}
Public void execute (object parameter)
{< br> delegate [] delegates = executehandler. getinvocationlist ();
foreach (Action act in delegates)
{< br> act. invoke (parameter! = NULL? Parameter. tostring (): String. Empty);
}< BR >}
Public bool canexecute (object parameter)
{
Delegate [] delegates = canexecutehandler. getinvocationlist ();
Foreach (func <bool> fun in delegates)
{
If (fun. Invoke ())
{
Return true;
}
}
Return false;
}
Public void raisecanexecutechanged ()
{
Canexecutechanged (this, eventargs. Empty );
}
}
Note that there is a public raisecanexecutechanged () method, which aims to enable non-callers to manually trigger the canexecutechanged event. For example, when the presenter finds that the data in the model has changed, presenter will trigger this event so that the interface element changes its status:
Void model_propertychanged (Object sender, propertychangedeventargs E)
{
If (string. Equals (E. propertyname, "Greetings "))
{
Greetings. raisecanexecutechanged ();
}
}
Complete code download
------------------------------- Prism command ----------------------------
The P & P team also saw many restrictions on WPF command, especially coupling with UI elements and unsupported command combinations. Therefore, they added another set of commands in Prism: delegatecommand and compositecommand.
Delegatecommand: implements the icommand interface of WPF and supports only one canexecute and execute hook. However, it implements an interface called iactiveaware to indicate whether the interface is in the set state, non-active delegatecommand is never executed.
Compositecommand: it is also an implementation of the icommand interface of WPF, but it is also a combination of delegatecommand, which can be registered or canceled to register delegatecommand, when all built-in delegatecommands in the active state can be executed, canexecute returns true. When the compositecommand is executed, the internal delegatecommand is executed in sequence (if executable)
The syntax of delegatecommand and compositecommand is skipped here.
In addition, delegatecommand and compositecommand are not a substitute for routecommand, but a powerful supplement. The routecommand Routing Mechanism frees us from worrying about the current user focus. Besides, the dozens of commonly used commands built in WPF save us a lot of development time.