Attached Properties and the Workflow Designer

Let me clarify that although this post is actually taking place only several hours after my previous post on "Diving in to workflow". In actuality that last post is reflective of a state I was in weeks ago that I just never got around to posting due to an internal project that was monopolizing my time. Anyway, since that time I've absorbed myself into Darma Shukla and Bob Schmidt's book 'Essential Windows Workflow Foundation'.

First let me say it's a great book. The initial chapters really give you a good insight into the basics of workflow, why you need it, as well as the nuts and bolts of how the Worfkflow infrastructure functions. Also the code samples in the book are really nice, and they get right to the point. Anyway at the end of Chapter 3 on Composite Activity Execution, I read the following.

"It may be helpful to pause here and consolidate what you've learned from this chapter so far. As an exercise, we suggest writing a custom composite activity. An appropriate choice on which to test your skills is PrioritizedInterleave. The PrioritizedInterleave activity executes its child activities in priority order. Each child activity has a property, named Priority, of type int."

So I took a base example from the book, added an attached Priority property and then added the logic for performing the prioritized interleaved execution. The Execute() method basically starts off at priority 1 then loops through all the child activities that are priority 1 and queues them next it does priority 2 etc until all activities are queued.

After compiling the code without issue, I then created a console project, referenced my PrioritizedInterleave library and then dropped my new PrioritizedInterleaveActivity on the Workflow designer. First thing I noticed was that the designer does not allow me to drop child activities in to my new activity. After a bit of playing, I found that I need to inherit from SequenceActivity in order to give me a designer that supports children. Next I dropped my new activity into the designer in order to create my new workflow. My plan was to drop a set of child activities (I created a dummy activity just to make life easy) and then set them to various priorities in order to test my algorithm.

All that sounded good except for one simple problem, Priority wouldn't show in the property window. Where the heck was it? I tried a million different things including adding Browsable, and DesignerSerializationVisibility attributes to my property as well as toying with the parameters passed to the RegisterAttached method none of which worked. What was the problem? I assumed that the workflow designer would automatically recognize that I added an attached property and it would just magically show up in the prop window. It works for regular dependency properties right, so why wouldn't it work for attached properties.

Well it turns out that was a very bad assumption. Whenever a component (any component) is dropped on a designer surface Visual Studio 2005 automatically picks up public properties that are not flagged with DesignerSerializationVisibility.Hidden. These properties then show up in the property window. Normal Dependency properties have public property accessors which is the only reason that they show up. Attached properties however do not have property accessors. Now that doesn't mean you can't get them in the designer, but it takes a bit of work. After doing a search on the web I came across the following article from Dennis Pilarinos, a PM on the Workflow team which shows you how to do it.

In summary the steps are as follows.

  1. Create a class that implements IExtendedProvider. In the extender implement getter and setter methods for the property that you want to project into the property window.
  2. Create a Designer, and in the Initialize method adds the new extender to the designer.
  3. Add a ProvideProperty attribute to the Designer class passing the new property as a parameter.
  4. Add a Designer attribute to the activity class specifying the newly created designer class.

After following the steps in the article and recompiling, I went back to the WorkFlow designer and low and behold my attached Priority property showed up in the property window. See code below and the attachment for the completed solution.

using System;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Diagnostics;

using System.Drawing;

using System.Workflow.ComponentModel.Compiler;

using System.Workflow.ComponentModel.Serialization;

using System.Workflow.ComponentModel;

using System.Workflow.ComponentModel.Design;

using System.Workflow.Runtime;

using System.Workflow.Activities;

using System.Workflow.Activities.Rules;

namespace PrioritizedInterleaveLibrary

{

    [Designer(typeof(PrioritizedInterleaveActivityDesigner))]

    public partial class PrioritizedInterleaveActivity : System.Workflow.Activities.CompositeActivity

    {

        public PrioritizedInterleaveActivity()

        {

            InitializeComponent();

        }

        int currentPriority = 1;

        int activitiesProcessed = 0;

        #region Priority

        public static DependencyProperty PriorityProperty = DependencyProperty.RegisterAttached("Priority", typeof(int), typeof(PrioritizedInterleaveActivity));

        public static object GetPriority(object dependencyObject)

        {

            return ((DependencyObject)

              dependencyObject).GetValue(PriorityProperty);

        }

        public static void SetPriority(object dependencyObject,

          object value)

        {

            ((DependencyObject)dependencyObject).SetValue(

              PriorityProperty, value);

        }

        #endregion

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

        {

            if (this.EnabledActivities.Count == 0)

                return ActivityExecutionStatus.Closed;

            //loop until all activities have been processed

            while (activitiesProcessed < EnabledActivities.Count)

            {

                foreach (Activity child in EnabledActivities)

                {

                    //grab the attached priority

                    int activityPriority = (int)child.GetValue(PriorityProperty);

                    //if the priority matches then execute it

                    if (activityPriority == currentPriority)

                    {

                        activitiesProcessed++;

                        child.Closed += ContinueAt;

                        executionContext.ExecuteActivity(child);

                        Trace.WriteLine(

                            string.Format("Activity Name: {0}, Priority {1}", child.Name, activityPriority)

                        );

       }

                }

                //increment the current priority

                currentPriority++;

            }

            return ActivityExecutionStatus.Executing;

        }

        void ContinueAt(object sender, ActivityExecutionStatusChangedEventArgs e)

        {

            e.Activity.Closed -= ContinueAt;

            ActivityExecutionContext context =

              sender as ActivityExecutionContext;

            foreach (Activity child in this.EnabledActivities)

            {

                if ((child.ExecutionStatus !=

                      ActivityExecutionStatus.Initialized)

                    && (child.ExecutionStatus !=

                      ActivityExecutionStatus.Closed))

                    return;

  }

            context.CloseActivity();

        }

    }

    [ProvideProperty("Priority", typeof(Activity))]

    class PrioritiedInterleaveActivityExtenderProvider : IExtenderProvider

    {

        public int GetPriority(Activity activity)

        {

            if (activity.Parent is PrioritizedInterleaveActivity)

                return (int)activity.GetValue(PrioritizedInterleaveActivity.PriorityProperty);

            else

                return 0;

       }

        public void SetPriority(Activity activity, int value)

        {

            if (activity.Parent is PrioritizedInterleaveActivity)

                activity.SetValue(PrioritizedInterleaveActivity.PriorityProperty, value);

        }

        bool IExtenderProvider.CanExtend(object extendee)

        {

            return (extendee is Activity) && (((Activity)extendee).Parent is PrioritizedInterleaveActivity);

        }

    }

    class PrioritizedInterleaveActivityDesigner : SequenceDesigner

    {

        protected override void Initialize(Activity activity)

        {

            base.Initialize(activity);

            IExtenderListService listService = (IExtenderListService)GetService(typeof(IExtenderListService));

            if (listService != null)

            {

                bool found = false;

                foreach (IExtenderProvider provider in listService.GetExtenderProviders())

                {

                    if (provider.GetType() == typeof(PrioritiedInterleaveActivityExtenderProvider))

                        found = true;

                }

                if (!found)

                {

                    IExtenderProviderService providerService = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));

         if (providerService != null)

                    {

                        providerService.AddExtenderProvider(new PrioritiedInterleaveActivityExtenderProvider());

                    }

                }

            }

        }

    }

}

 

PrioritizedInterleave.zip