Share via


Task 3: Create the WorkflowMenuCommandService

Download sample

In Task 2, you integrated the WorkflowLoader class you created in Task 1 with the Windows Form application WFEdit. At this point, the Windows Workflow Foundation Designer is visible when your application executes. In this task, you will add a service to the Workflow designer in order to enable the workflow designer context menu and its associated workflow-specific commands defined in Windows Workflow Foundation.

Note

Although you are encouraged to follow the exercises in a linear manner, it is not required. You can start on this exercise by opening the sample project and proceeding to the steps in the following section.

To create the Services source code file

  1. In your projects directory, create a new file named Services.
    Give the file a .cs extension if you are creating a C# application or a .vb extension if you are creating a Visual Basic application.

  2. In your main project file (WFEdit), in the ItemGroup element that you created in Task 2: Create the Workflow Designer Hosting Windows Form, add a new Compile element.

  3. Add a new attribute to the Compile element named Include.

    Use the file name that you created in step 1 for the attribute value.

  4. Your final ItemGroup node will appear as follows:

    <ItemGroup>
        <Compile Include="Loader.vb" />
        <Compile Include="Services.vb" />
        <Compile Include="WFEditForm.vb">
            <SubType>Form</SubType>
        </Compile>
        <Compile Include="Program.vb" />
    </ItemGroup>
    
    <ItemGroup>
        <Compile Include="Loader.cs" />
        <Compile Include="Services.cs" />
        <Compile Include="WFEditForm.cs">
            <SubType>Form</SubType>
        </Compile>
        <Compile Include="Program.cs" />
    </ItemGroup>
    

To create the WorkflowMenuCommandService class

  1. In the Services file you created in the previous procedure, create a namespace block using the same namespace as the Windows Form source code. For this tutorial, the namespace is Microsoft.Samples.Workflow.Quickstarts.WFEdit.

    ' To be provided
    
    namespace Microsoft.Samples.Workflow.Quickstarts.WFEdit
    {
    }
    
  2. Add the following namespace directives to import the types that you will need for the WorkflowLoader class. These directives should be placed within the namespace block you created in the previous step.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Design;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Windows.Forms;
    using System.Drawing;
    
  3. Create an internal sealed class named WorkflowMenuCommandService that is derived from the MenuCommandService class.

    ' To be provided
    
    internal sealed class WorkflowMenuCommandService : MenuCommandService
    {
    }
    
  4. In the WorkflowMenuCommandService class, create a public constructor that accepts an IServiceProvider object named serviceProvider as a parameter. Additionally, call the base class implementation of the constructor as shown in the following code:

    public WorkflowMenuCommandService(IServiceProvider serviceProvider)
        : base(serviceProvider)
    {
    }
    

To create the ShowContextMenu method

  1. In the WorkflowMenuCommandService class, override the ShowContextMenu method. This method accepts a CommandID object named menuID, an Int32 named x, and an Int32 named y as parameters.

    ' To be provided
    
    public override void ShowContextMenu(CommandID menuID, int x, int y)
    {
    }
    
  2. In the ShowContextMenu method, create a conditional statement that will evaluate to true if the menuID parameter is equal to System.Workflow.ComponentModel.Design.WorkflowMenuCommands.SelectionMenu. By doing this, the context menu within the hosted designer will only show when a user right-clicks the mouse on a workflow activity.

    Note

    The remaining code in the following steps for this method should be placed within the body of the conditional statement.

  3. In the true block of the conditional statement created in the previous step, create a new ContextMenu object named contextMenu and create a new instance of that class.

  4. Create an enumerator block that enumerates through each DesignerVerb in the Verbs collection.

  5. In the enumeration block created in the previous step, create a MenuItem object named menuItem and create an instance of that class using verb.Text as the first parameter to the constructor and a new EventHandler object using the OnMenuClicked delegate as the second parameter. The OnMenuClicked delegate method will be created in a latter step.

  6. Set the Tag property of the menuItem object created in the previous step equal to the value of the currently enumerated DesignerVerb named verb.

  7. Add the menu item you created in step 6 to the MenuItems collection of the contextMenu object using the Add method. The following code shows how the enumeration block should appear:

    ContextMenu contextMenu = new ContextMenu();
    
    foreach (DesignerVerb verb in Verbs)
    {
        MenuItem menuItem = new MenuItem(verb.Text, new EventHandler(OnMenuClicked));
        menuItem.Tag = verb;
        contextMenu.MenuItems.Add(menuItem);
    }
    
  8. Following the enumeration block you created in the previous steps, create a MenuItem array named items and initialize it using the GetSelectionMenuItems method.

    Note

    The GetSelectionMenuItems method will be created in the next procedure.

  9. Create a new conditional statement that will evaluate to true if the Length property of the items array is greater than 0.

  10. In the true body of the conditional statement created in the previous step, add a new MenuItem object, passing "-" as the constructor parameter and add it to the MenuItems collection of the contextMenu object using the Add method.

  11. Following the code created in the previous step, enumerate through each MenuItem object in the items array and add each item to the MenuItems collection of the contextMenu object using the Add method. The code to create the items array and the code to add each selection menu item to the contextMenu object should appear as follows:

    MenuItem[] items = GetSelectionMenuItems();
    if (items.Length > 0)
    {
        contextMenu.MenuItems.Add(new MenuItem("-"));
        foreach (MenuItem item in items)
            contextMenu.MenuItems.Add(item);
    }
    
  12. Following the conditional statement block created in step 10, create a new WorkflowView object named workflowView and initialize it using the GetService method. Pass the Type object of the WorkflowView class as a parameter to the GetService method.

  13. Create a conditional statement to ensure the workflowView object is not null (Nothing in Visual Basic) and call the Show method of the contextMenu object. The parameters to the Show method include the workflowView object and the result of calling the workflowView.PointToClient method, passing a new Point object initialized using the x and y method parameters. The code for the ShowContextMenu method should appear similar to the following:

    public override void ShowContextMenu(CommandID menuID, int x, int y)
    {
        if (menuID == WorkflowMenuCommands.SelectionMenu)
        {
            ContextMenu contextMenu = new ContextMenu();
    
            foreach (DesignerVerb verb in Verbs)
            {
                MenuItem menuItem = new MenuItem(verb.Text, new EventHandler(OnMenuClicked));
                menuItem.Tag = verb;
                contextMenu.MenuItems.Add(menuItem);
            }
    
            MenuItem[] items = GetSelectionMenuItems();
            if (items.Length > 0)
            {
                contextMenu.MenuItems.Add(new MenuItem("-"));
                foreach (MenuItem item in items)
                    contextMenu.MenuItems.Add(item);
            }
    
            WorkflowView workflowView = GetService(typeof(WorkflowView)) as WorkflowView;
            if (workflowView != null)
                contextMenu.Show(workflowView, workflowView.PointToClient(new Point(x, y)));
        }
    }
    

To create the GetSelectionMenuItems method

  1. In the WorkflowMenuCommandService class, create a new private method named GetSelectionMenuItems that returns a MenuItem array.

    ' To be provided
    
    private MenuItem[] GetSelectionMenuItems()
    {
    }
    
  2. In the GetSelectionMenuItems method, create a new generic List collection of type MenuItem and create a new instance of that class.

  3. Create a new local Boolean variable named addMenuItems and initialize it to false.

  4. Create a new ISelectionService variable named selectionService and initialize it by calling the GetService method, passing the Type object of the ISelectionService interface.

    List<MenuItem> menuItems = new List<MenuItem>();
    bool addMenuItems = true;
    
    ISelectionService selectionService = GetService(typeof(ISelectionService)) as ISelectionService;
    
  5. Create a conditional statement that will evaluate to true if the selectionService is not null (Nothing in Visual Basic).

  6. In the true block of the conditional statement created in the previous step, enumerate each Object object in the collection returned by calling the GetSelectedComponents method from the selectionService object.

  7. In the enumerator block created in the previous step, create a conditional statement that will evaluate to true if the object is not an Activity object. If this is true, set the addMenuItems local variable to false and break from the enumerator block. This will ensure that the context menu is not shown if some other object besides an Activity is selected in the workflow designer.

    if (selectionService != null)
    {
        foreach (object obj in selectionService.GetSelectedComponents())
        {
            if (!(obj is Activity))
            {
                addMenuItems = false;
                break;
            }
        }
    }
    
  8. Create a conditional statement that will evaluate to true if the addMenuItems local variable is true.

  9. In the true block of the conditional statement created in the previous step, create a new generic Dictionary collection using a CommandID type as the key and a String type as the value and create a new instance of the collection.

  10. Using the following table as a guide, call the Add method of the selectionCommands collection, passing a WorkflowMenuCommands enumerated type and its associated displayable string value as parameters.

    WorkflowMenuCommands Type User Interface String Value

    WorkflowMenuCommands.Cut

    "Cut"

    WorkflowMenuCommands.Copy

    "Copy"

    WorkflowMenuCommands.Paste

    "Paste"

    WorkflowMenuCommands.Delete

    "Delete"

    System.Workflow.ComponentModel.Design.WorkflowMenuCommands.Collapse

    "Collapse"

    System.Workflow.ComponentModel.Design.WorkflowMenuCommands.Expand

    "Expand"

    System.Workflow.ComponentModel.Design.WorkflowMenuCommands.Disable

    "Disable"

    System.Workflow.ComponentModel.Design.WorkflowMenuCommands.Enable

    "Enable"

    Dictionary<CommandID, string> selectionCommands = new Dictionary<CommandID, string>();
    selectionCommands.Add(WorkflowMenuCommands.Cut, "Cut");
    selectionCommands.Add(WorkflowMenuCommands.Copy, "Copy");
    selectionCommands.Add(WorkflowMenuCommands.Paste, "Paste");
    selectionCommands.Add(WorkflowMenuCommands.Delete, "Delete");
    selectionCommands.Add(WorkflowMenuCommands.Collapse, "Collapse");
    selectionCommands.Add(WorkflowMenuCommands.Expand, "Expand");
    selectionCommands.Add(WorkflowMenuCommands.Disable, "Disable");
    selectionCommands.Add(WorkflowMenuCommands.Enable, "Enable");
    
  11. Following the code created in the previous step, enumerate through each CommandID object in the Keys collection of the selectionCommands object.

  12. In the enumeration block created in the previous step, create a new MenuCommand object named command and initialize it using the FindCommand method, passing the CommandID object as a parameter.

  13. Create a new conditional statement that will evaluate to true if the command object is not null (Nothing in Visual Basic).

  14. In the true block of the conditional statement created in the previous step, create a new MenuItem object and create a new instance, passing the object returned from the id index of the selectionCommands collection and a new EventHandler object initialized using the OnMenuClicked delegate.

    Note

    The OnMenuClicked delegate method will be created in the next procedure.

  15. Set the Tag property of the menuItem object you created in the previous step equal to the command MenuCommand object.

  16. Add the command MenuCommand object to the menuItems collection using the Add method. The code for the enumeration block created in steps 11-16 should appear as follows:

    foreach (CommandID id in selectionCommands.Keys)
    {
        MenuCommand command = FindCommand(id);
        if (command != null)
        {
            MenuItem menuItem = new MenuItem(selectionCommands[id], new EventHandler(OnMenuClicked));
            menuItem.Tag = command;
            menuItems.Add(menuItem);
        }
    }
    
  17. Return from the method by calling the ToArray method of the menuItems collection. The following code shows how the GetSelectionMenuItems method should appear:

    private MenuItem[] GetSelectionMenuItems()
    {
        List<MenuItem> menuItems = new List<MenuItem>();
        bool addMenuItems = true;
    
        ISelectionService selectionService = GetService(typeof(ISelectionService)) as ISelectionService;
        if (selectionService != null)
        {
            foreach (object obj in selectionService.GetSelectedComponents())
            {
                if (!(obj is Activity))
                {
                    addMenuItems = false;
                    break;
                }
            }
        }
    
        if (addMenuItems)
        {
            Dictionary<CommandID, string> selectionCommands = new Dictionary<CommandID, string>();
            selectionCommands.Add(WorkflowMenuCommands.Cut, "Cut");
            selectionCommands.Add(WorkflowMenuCommands.Copy, "Copy");
            selectionCommands.Add(WorkflowMenuCommands.Paste, "Paste");
            selectionCommands.Add(WorkflowMenuCommands.Delete, "Delete");
            selectionCommands.Add(WorkflowMenuCommands.Collapse, "Collapse");
            selectionCommands.Add(WorkflowMenuCommands.Expand, "Expand");
            selectionCommands.Add(WorkflowMenuCommands.Disable, "Disable");
            selectionCommands.Add(WorkflowMenuCommands.Enable, "Enable");
    
            foreach (CommandID id in selectionCommands.Keys)
            {
                MenuCommand command = FindCommand(id);
                if (command != null)
                {
                    MenuItem menuItem = new MenuItem(selectionCommands[id], new EventHandler(OnMenuClicked));
                    menuItem.Tag = command;
                    menuItems.Add(menuItem);
                }
            }
        }
    
        return menuItems.ToArray();
    }
    

To create the OnMenuClicked method

  1. In the WorkflowMenuCommandService class, create a private method named OnMenuClicked that accepts an Object named sender and an EventArgs object named e as parameters.

  2. In the OnMenuClicked method, create a new MenuItem local variable named menuItem and initialize it by casting the sender object to a MenuItem.

  3. Create a conditional statement that will evaluate to true if the menuItem is not null (Nothing in Visual Basic) and if the Tag property of the menuItem object is a MenuCommand object.

  4. In the true block of the conditional statement created in the previous step, cast the menuItem.Tag property to a MenuCommand object and call the Invoke method on that object. The following code shows the OnMenuClicked method:

    private void OnMenuClicked(object sender, EventArgs e)
    {
        MenuItem menuItem = sender as MenuItem;
        if (menuItem != null && menuItem.Tag is MenuCommand)
        {
            (menuItem.Tag as MenuCommand).Invoke();
        }
    }
    

To add the WorkflowMenuCommandService to the WorkflowLoader class

  1. In the WorkflowLoader class in the Initialize after the call to the base class implementation of Initialize, create a conditional statement that will evaluate to true if the LoaderHost class property is not null (Nothing in Visual Basic). The remaining code for this procedure should be placed in the true block of this conditional statement.

  2. In the true block of the conditional statement created in the previous step, create a TypeProvider local variable named typeProvider and create a new instance of the object, passing the LoaderHost class property as a parameter to the constructor.

  3. Call the AddAssemblyReference method defined in the typeProvider object, passing the Assembly.Location property of the Type object for the String class.

  4. Call the AddService method of the LoaderHost class property, passing the Type of the ITypeProvider interface, the typeProvider object, and the value true as parameters to the method.

  5. Call the AddService method of the LoaderHost class property, passing the Type object of the IMenuCommandService interface and a new instance of the WorkflowMenuCommandService initialized using the LoaderHost property as a parameter to the WorkflowMenuCommandService constructor.

  6. The full Initialize method of the WorkflowLoader class should appear as follows:

    protected override void Initialize()
    {
        base.Initialize();
    
        if (this.LoaderHost != null)
        {
            TypeProvider typeProvider = new TypeProvider(this.LoaderHost);
            typeProvider.AddAssemblyReference(typeof(string).Assembly.Location);
    
            this.LoaderHost.AddService(typeof(ITypeProvider), typeProvider, true);
            this.LoaderHost.AddService(typeof(IMenuCommandService), new WorkflowMenuCommandService(this.LoaderHost));
        }
    }
    

To remove the WorkflowMenuCommandService from the WorkflowLoader

  1. In the WorkflowLoader class in the Dispose method before the base class Dispose method call, create a conditional statement that will evaluate to true if the LoaderHost class property is not null (Nothing in Visual Basic).

  2. In the true block of the conditional statement created in the previous procedure, call the RemoveService ** method of the LoaderHost object, passing the Type object of the ITypeProvider interface and a Boolean true value.

  3. Call the RemoveService method of the LoaderHost object, passing the Type object of the IMenuCommandService interface.

  4. The following code shows how the Dispose method should appear in the WorkflowLoader class.

    public override void Dispose()
    {
        if (this.LoaderHost != null)
        {
            this.LoaderHost.RemoveService(typeof(ITypeProvider), true);
            this.LoaderHost.RemoveService(typeof(IMenuCommandService));
        }
    
        base.Dispose();
    }
    

Compiling the Code

For information about compiling your code, see Compiling the Code.

For a finished version of the tutorial, see Completed Hosting the Windows Workflow Designer Tutorial.

See Also

Concepts

Hosting Workflow Designers

Other Resources

Basic Designer Hosting Sample
Outlook Workflow Wizard Sample
Workflow Monitor Sample
Tracking Profile Designer Sample

Copyright © 2007 by Microsoft Corporation. All rights reserved.
Last Published: 2010-03-04