(WF4) Enhancing the Context Menu for built-in ActivityDesigners? (and StateMachine)

Recently I noticed that I had automatically installed .Net Framework Platform Update 1 along with something else. So I should be able to use State Machine in my workflows now, right? Half right, half wrong! I forgot (and I am sure I am not the only one to forget this) that there are two parts to the Platform Update 1. The .Net framework assemblies and the Visual Studio enhancements (which require VS2010 SP1) are installed separately, and I only had the .Net framework assemblies installed, (And it’s not a good time for me to install the service pack right now.)

This means that no matter how hard I look, there will not be any StateMachine appearing in my toolbox in VS. Not having SP1 and the VS enhancements doesn’t mean StateMachine is totally unusable though. It’s totally usable within a rehosted app. And it’s arguably usable inside VS too, since you can force it to appear by editing the XAML:

<p:StateMachine/>

However, then it turns out that if you do this there is still no way except XAML to add States to the State Machine, ugh.

“If only State Machine had an ‘Add State’ context menu item”, thought I.

Hmm.

Actually, how would you go about adding a context menu item to a State Machine designer? In a rehosted application?

When it comes to adding custom context menu items to an Activity Designer, there’s a sample for that… right? What? No official sample? OK, surely there’s information on how to add context menu items to a custom activity out there somewhere?

Ah, thank you bing. Not an official sample, but someone’s written a blog post. “Adding a Context Menu to a Workflow 4 Designer” – (blog: Mebyon Kernow)

The sample is about adding a context menu item to your own custom activity designer class.

But here I’m thinking of customizing the experience of a built-in activity designers, like StateMachineDesigner. On the assumption this could actually be useful to someone let’s explore.

Ideas

There are two implementation approaches I can think of to start with:
1) Find the StateMachineDesigner instance(s) and programmatically insert context menu items in their ContextMenu in a way equivalent to the Xaml code in the sample.
2) Modify the global context menu in a way that the change is only visible for StateMachine designers

There are also a couple variations on timing the modification:
A) Up front = on creation of the state machine designer or the workflow designer
B) Just in time = during click handling event, during context menu show, etc.

Checking the ideas out: 1A

Is it possible to detect creation of ActivityDesigner objects in general, StateMachineDesigner objects in particular? How do they get created anyway?

Responsibility for instantiating ActivityDesigner objects belongs to a designer service of type ViewService, implemented by an internal class WorkflowViewService. Sadly, ViewService doesn’t expose any public events where we can listen for view creation. So a 1+A approach seems too hard.

1B

Since we don’t seem to know where StateMachineDesigners are in advance, can we figure this out during the click event?
We could…
- Intercept right-click event at the main window or workflow designer level
- walk up the visual tree from the right-click recipient, looking for a StateMachine designer, or: walk down the model item tree looking for a StateMachine – but walking up should be more efficient.

Woah, that actually sounds almost doable? Let’s try it out:

 

    protected override void OnPreviewMouseDown(MouseButtonEventArgs e)

    {

        if (e.ChangedButton == MouseButton.Right)

        {

            DependencyObject node = e.OriginalSource as DependencyObject;

            while (node != null)

            {

                if (node is ActivityDesigner)

                {

                    break;

                }

                else

                {

                    node = VisualTreeHelper.GetParent(node);

                }

            }

 

            if (node.GetType().Name.Contains("StateMachineDesigner"))

            {

                var designer = (ActivityDesigner)node;

                if (designer.ContextMenu == null)

                {

                    designer.ContextMenu = new ContextMenu();

 

                    MenuItem menuItem = new MenuItem

                    {

                        Header = "Create State",

                        Command = null,

                    };

                    designer.ContextMenu.Items.Add(menuItem);

                }

            }

        }

        base.OnPreviewMouseDown(e);

    }

And tada:

image

Next step will be adding an ICommand implementation to actually add a State - which can be done using DelegateCommand as used in the sample linked above, or by RoutedCommand, custom ICommand, whatever. I’m going to skip this one for today.

Last question which comes up frequently - ‘how could I do a customization like this in VS (where unlike a rehosted designer application, I don’t own the Main Window or the WorkflowDesigner instance)?’ I still haven’t seen any extensibility story for ‘global’ workflow designer customizations in VS based on writing e.g. VS extensions, but I’m holding out hope that there’ll be one some day.