How To: Pass parameters to Commands Handlers in Composite UI Application Block (CAB) / SCSF
This week there were some questions in the SCSF Codeplex Forum asking for a way to pass custom parameters to a method marked as a Command Handler. As you may know, there is no out-of-the-box way to do it because they are raised internally by the Command class and, since you usually bind a command to a control event via the AddInvoker method, you cannot manage when the command is executed.
Additionally, the signature of a command handler is fixed and should always be like the following:
[CommandHandler(CommandNames.MyCommand)] public void OnMyCommandHandler(object sender, EventArgs args) { // The sender parameter is an instance of the Command class. Command cmd = sender as Command; // The args parameter is empty. bool f = (args == EventArgs.Empty); }
The instance of the Command class received in the sender parameter could be useful, for example, if you want to change the status of the command to Enabled, Disabled or Unavailable.
cmd.Status = CommandStatus.Disabled;
But, what if you want to share the same command handler for different command invokers (like several menu items) and perform some operations based on which item was the invoker? You will somehow need to receive a parameter describing which was the item that raised the command.
Workaround
A possible workaround to receive parameters using a command could be by not adding your items as invokers of the command and instead execute the command programmatically. If you do this, you could use the State collection of the RootWorkItem to pass parameters to the command.
To implement this workaround follow these steps (the code use the ToolStripMenuItem control but could be used with other toolstrip controls):
- Add the parameter that you want to receive in the command handler to the Tag property of your menu item control:
ToolStripMenuItem item = new ToolStripMenuItem() { Text = “My Menu Item”, Tag = “My Tag”};
- Add a handler for the Click event to your menu item:
item.Click += new EventHandler(Item_Click);
- In the Item_Click method, add the Tag of your item to the State collection of the RootWorkItem and execute the command programmatically:
WorkItem.RootWorkItem.State["LastTag"] = item.Tag; WorkItem.Commands[CommandNames.CommandWithParameters].Execute();
- In the command handler, retrieve the Tag from the State collection:
string tag = WorkItem.RootWorkItem.State["LastTag"] as string;
The following code shows a ModuleController class that demonstrates how the approach above could be implemented:
public class ModuleController : WorkItemController { public override void Run() { AddServices(); ExtendMenu(); AddViews(); } private void ExtendMenu() { AddTaggedMenuItem(“Menu Item 1″, “Tag 1″, UIExtensionSiteNames.ModulesMenu); AddTaggedMenuItem(“Menu Item 2″, “Tag 2″, UIExtensionSiteNames.ModulesMenu); AddTaggedMenuItem(“Menu Item 3″, “Tag 3″, UIExtensionSiteNames.ModulesMenu); AddTaggedMenuItem(“Menu Item 4″, “Tag 4″, UIExtensionSiteNames.ModulesMenu); } private void AddTaggedMenuItem(string menuText, string menuTag, string extensionSiteName) { ToolStripMenuItem menuItem = new ToolStripMenuItem() { Text = menuText, ToolTipText = menuText, Tag = menuTag }; menuItem.Click += new EventHandler(MenuItem_Click); WorkItem.UIExtensionSites[extensionSiteName].Add(menuItem); } private void MenuItem_Click(object sender, EventArgs e) { ToolStripMenuItem item = sender as ToolStripMenuItem; if (item != null) { WorkItem.RootWorkItem.State["LastTag"] = item.Tag; WorkItem.Commands[CommandNames.CommandWithParameters].Execute(); } } [CommandHandler(CommandNames.CommandWithParameters)] public void OnCommandWithParameters(object sender, EventArgs args) { string tag = WorkItem.RootWorkItem.State["LastTag"] as string; if (tag != null) { MessageBox.Show(“The tag received in the command handler is: ” + tag, “Tags”); } } // … }
In this way, the State collection is used as a container for your custom command parameters.
Enjoy.
Nice!
Thanks for sharing this.
Hola Mariano muy bueno el Post.
vengo siguiendo tus post y de hecho he implementado algunos de ellos.
Un problema que no he resuelto ni con este Post ni con el post de ActiveViewMonitorService es la posibilidad de tener en un mainToolStrip un boton que te permita cerrar solo la vista actual. Es decir que sea un Handler para hace un close solo de la vista Actual. Lo mas cerca que pude llegar de lograrlo es con ActiveViewMonitorService pero no termina de hacerlo bien puesto que no siempre la vista activa es la que tienes abierta sobre todo cuando tienes varios WorkSpace.
Si tienes una solucion agradeceria que la comentes. Esta misma solucion puede servir para por ejemplo un menu de ayuda o de impresion
Saludos y felicitaciones por tu blog
Desde Cordoba Argentina
Antes que nada, muchas gracias por tu comentario.
Una posible solución que se me ocurre para poder obtener siempre la vista actual es modificar la implementación del servicio ActiveViewMonitorService.
La implementación de este servicio en mi post se suscribe a los eventos SmartPartActivated y Enter. Como bien decis vos, para algunos tipos de Workspace puede no funcionar bien (sobre todo si tu Workspace no hereda de la clase Control).
Para solucionar esto podes probar modificando este servicio para que se suscriba al evento Enter de todas las SmartParts del Workspace (las cuales son UserControls).
En el siguiente post de Ryan Kelley podes ver un ejemplo de cómo quedaría el servicio ActiveViewMonitorService modificado:
CAB & SCSF: Maintaining Active View across Multiple workspaces
Aparentemente este sitio está offline así que acá te paso el chache de google por las dudas:
CAB & SCSF: Maintaining Active View across Multiple workspaces
Saludos y mucha suerte.
Mariano Converti