Windows Workflow Odyssey - Creating Your First Activity Part II

by Khalid Abuhakmeh 14. August 2008 14:32


So where were did we leave off last time.... oh yeah, we created a beep activity. Now you are wondering, "how do we shine the diamond up?" What we'll need to do is add validation, configurability, and style to our activity. Caution, there will be things that look very scary, but are not. You will see alot of attributes and you will see alot of "magic strings." I will try to explain these things, but if you want to learn more about the code you can look on MSDN. Ok so let's pick up where we left off so you can finish your activity and start making some money (hopefully).  Oh and one more thing I forgot to mention in my last post, never never never never put anything in the constructor of an activity. The constructor of an activity is run continuously by the WF dedsigner, so it can potentially reek havok on your machine and development environment; you've been warned.

MonstersGotMyWorkflow-Part2.zip (963.99 kb) (Visual studio 2008, sorry workflows are not supported in express editions. I know, lame.)

Creating a Configurable Property

Let's start with the most important thing first, configuration. We have two properties we'd like to set for a beep: duration and frequency. When adding a property to an activity you will need to create two properties inside of our activity class. These are known as DependencyProperty types. Let's go ahead and define the two properties of duration and frequency.

        public static DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(int), typeof(BeepActivity));

        public static DependencyProperty FrequencyProperty =
            DependencyProperty.Register("Frequency", typeof(int), typeof(BeepActivity));

So let me explain here what the register method does. It takes a name of the property, the type of the property, and the activity that it's related to. Now if you build your activity you will have those properties. The only problem is that they will not show up in the designer. This is where we need to go attribute crazy. Getting all the nice things an activity can have requires a lot of attributes. We are going to add two wrapper properties for our DependencyProperties and we'll add several attributes to help the designer dispaly nicely.

        [TypeConverterAttribute(typeof(int))]
        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("The frequency at which to beep.")]
        [CategoryAttribute("Beep")]
        public int Frequency
        {
            get { return (int)GetValue(FrequencyProperty); }
            set { SetValue(FrequencyProperty, value); }
        }

        [TypeConverterAttribute(typeof(int))]
        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("The duration at which to beep in milliseconds.")]
        [CategoryAttribute("Beep")]
        public int Duration
        {
            get { return (int)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

If you build the project now you should see that your properties are now exposed in the designer. Now you can set them, but they are still not being used in the code.

We are going to need to use these properties when calling the execute method of the beep activity. Change the execute method to this.

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Console.Beep(Frequency,Duration);

            return ActivityExecutionStatus.Closed; 
        }

There you have the first step in creating a configurable activity. Now lets get to validation.

Adding Validation to An Activity

To add validation to an activity we will need to create a class that inherits from the ActivityValidator class System.Workflow.ComponentModel.Compiler namespace. After creating the class we need to override the Validate method. We have two properties to validate: Duration and Frequency. A frequency can only be between 37 and 32767, and a duration has to be greater or equal to 0. The validation class will end up looking like this.

    public class BeepActivityValidator : ActivityValidator
    {
        public override ValidationErrorCollection Validate(
            ValidationManager manager, object obj)
        {
            if (manager == null)
                throw new ArgumentNullException("manager");

            if (obj == null)
                throw new ArgumentNullException("obj");

            BeepActivity activity = obj as BeepActivity;

            if (activity == null)
                throw new
                    InvalidOperationException("obj should be a PingActivity");

            if (activity.Parent != null)
            {
                ValidationErrorCollection errors = base.Validate(manager, obj);

                if(activity.Frequency < 37 || activity.Frequency > 32767)
                    errors.Add(new ValidationError("Frequency must be between 37 and 32767.",0,false,"Frequency"));

                if (activity.Duration <= 0)
                    errors.Add(new ValidationError("Duration must be at least 1 millisecond.", 0, false, "Duration"));
               
                return errors;
            }

            return new ValidationErrorCollection();
        }
    }

Now we need to link this validator with our activity. We will need to decorate the activity and the properties within the class so that the designer can run the validator.  Put this attribute on top of the BeepActivity class:

[ActivityValidator(typeof(BeepActivityValidator))]

And there you go, now trying to build your project should give you build errors. Your workflow will not build due to the validation we just implemented.

Awesome right?! I hope you are getting excited, if not then you are obviously not a geek. Our activity still looks butt ugly, the next step will be pretty it up a quite a bit.

Making an Activity Pretty

So you'll need two resource images, both will be used in the designer. I've included the resources below.

Save these two files and add them as image resources in your project. I'll assume you know how to add an image as a resource to your Resources.resx file. The next step will be to create two classes to support the designer: BeepActivityDesigner and BeepActivityDesignerTheme. Both classes are important to the look of the activity in the designer. The BeepActivityDesigner class defines the inside look of the activity, things like text padding, text size, and icon image are denfined in this class. The BeepActivityDesignerTheme defines the outer appearance of the activity, things like borders colors, background color of activity. This is where I feel activity creation can get sticky because it heavily depends on GDI+.

The BeepActivityDesigner class inherits from the ActivityDesigner. We will need to override the ImageRectangle, TextRectangle, OnLayoutSize and Initialize methods. There is no magic, here just math; get your calculator and figure out how big you need your activity to be. The class is as follows.

[ActivityDesignerTheme(typeof (BeepActivityDesignerTheme))]
    public class BeepActivityDesigner : ActivityDesigner
    {
        private const int PADDING = 4;
        private const int TEXT_WIDTH = 75;

        protected override Rectangle ImageRectangle
        {
            get
            {
                Rectangle rect = new Rectangle();
                rect.X = Bounds.Left + PADDING;
                rect.Y = Bounds.Top + PADDING;
                rect.Size = Resources.beepBig.Size;
                return rect;
            }
        }

        protected override Rectangle TextRectangle
        {
            get
            {
                Rectangle imgRect = ImageRectangle;

                Rectangle rect = new Rectangle(
                    imgRect.Right + PADDING,
                    imgRect.Top,
                    TEXT_WIDTH,
                    imgRect.Height);
                return rect;
            }
        }

        protected override void Initialize(Activity activity)
        {
            base.Initialize(activity);

            Bitmap image = Resources.beepBig;
            image.MakeTransparent();
            Image = image;
        }

        protected override Size OnLayoutSize(ActivityDesignerLayoutEventArgs e)
        {
            base.OnLayoutSize(e);

            Size imgSize = Resources.beepBig.Size;
            return new Size(imgSize.Width + TEXT_WIDTH + (PADDING*3),
                            imgSize.Height + (PADDING*2));
        }
    }

You'll notice that the BeepActivityDesigner class has an attribute of ActivityDesignerTheme and references the BeepActivityDesignerTheme. Lets see what that class will look like.

  public class BeepActivityDesignerTheme : ActivityDesignerTheme
    {
        public BeepActivityDesignerTheme(WorkflowTheme theme)
            : base(theme)
        {
        }

        public override void Initialize()
        {
            ForeColor = Color.Black;
            BorderColor = Color.GreenYellow;
            BorderStyle = DashStyle.Solid;
            BackgroundStyle = LinearGradientMode.ForwardDiagonal;
            BackColorStart = Color.WhiteSmoke;
            BackColorEnd = Color.MintCream;

            base.Initialize();
        }
    }

The final step is to add the designer attribute to the activity.

    [Designer(typeof(BeepActivityDesigner), typeof(IDesigner))]

 

Activity Toolbox Items

We have our activity, now we need it to appear in the the toolbox. You may say to yourself, "well it's already in my toolbox." You would be right, but thats because you have the activity library as a referenced project. We need to make the icons available to users even when the project is not referenced. This is why we need to create a class called ActivityBaseToolBoxItem. We follow that by decorating the Activity class. This class defines the naming convention used in the toolbox, it string away the word "Activity" so we are left with "Beep" in the toolbox.

[Serializable]
    public class ActivityBaseToolboxItem : ActivityToolboxItem
    {
        public ActivityBaseToolboxItem()
        {
        }

        public ActivityBaseToolboxItem(Type type)
            : base(type)
        {
            if (type != null)
            {
                if (type.Name.EndsWith("Activity") && !type.Name.Equals("Activity"))
                {
                    base.DisplayName =
                        type.Name.Substring(0, type.Name.Length - 8);
                }
            }
        }

        protected ActivityBaseToolboxItem(SerializationInfo info,
                                        StreamingContext context)
        {
            Deserialize(info, context);
        }
    }

your activity should now be decorated with all the following attributes.

    [ToolboxItem(typeof(ActivityBaseToolboxItem))]
    [ToolboxBitmap(typeof(BeepActivity), "Resources.beep.png")]
    [Designer(typeof(BeepActivityDesigner), typeof(IDesigner))]
    [ActivityValidator(typeof(BeepActivityValidator))]

There you have it, now compile the project, package the assembly and give it to your friends to use; or sell it off and be greedy.

Final Notes

So there you have it, your bee activity looks great, has validation, and is configurable. If you had any trouble understanding or following along, that's ok. It is a little bit complex but download the code attached to this post and just play around. It will click and then you'll be on your way to creating new and exciting activities. Don't forget to have fun.

P.S. Run the example I've included for some fun related to the "Black Sheep" at the beginning of the post. A cookie for the first one to post what the easter egg is. 

 

Currently rated 4.5 by 2 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Code | Workflow

Comments

Add comment


 

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

A Little Narcissism

I am a .NET developer mainly focused on Web development and enterprise applications. I strive to keep my skills at their best and always looking to absorb that much more knowledge. I am learning new things like Windows Workflow Foundation, LINQ, Ruby on Rails, WPF, ASP.NET MVC, and anything else thrown at me; I say bring it on!

RecentComments

Comment RSS