Designers


A WF program is a hierarchical arrangement of activities. It is easy to write and utilize packages of domain-specific activities rather than building WF programs from a fixed set of modeling constructs and adding imperative code. This opens the door for domain experts to participate directly in the authoring of WF programs without having to do any coding. Domain experts can be WF program authors simply by composing activities within a visual design environment.

Application and tool vendors can therefore build visual design environments that are targeted at specific kinds of WF program authors. Because the WF programming model for activities is public, you are free to create whatever WF program authoring applications that you require. You are limited only by your creativity in how you map UI (or other) gestures to the manipulation of an activity object tree.

Rather than starting from scratch, though, it may be helpful to utilize the visualization framework for activities that is provided by the WF programming model. This is the same framework used in the implementation of the Visual Studio Workflow Designer. This framework (the System.Workflow.ComponentModel.Design namespace), however, is not tied in any way to Visual Studio. As we will see in the next section, WF program design elements can be hosted directly in design environments other than Visual Studio.

The WF visualization framework is layered upon the design-time infrastructure used by Windows Forms and ASP.NET. All three technologiesWF, Windows Forms, and ASP.NETbase their design-time architectures on System.ComponentModel.Design, an established part of the .NET Framework. Figure 8.4 should give you a sense of what a visual design environment for WF programs can look like, and also points out several design-time concepts that we will discuss in this section.

Figure 8.4. Visual WF program designer


At a high level, the System.Workflow.ComponentModel.Design namespace contains two kinds of assets. There is a set of types used by activity developers to build activity designer components; we will discuss this use case first. There are also a set of types that can be used in the development of custom WF program design environments (in addition to being utilized within the Visual Studio Workflow Designer); these are discussed in a subsequent section.

The Activity class implements the IComponent interface defined in the System.ComponentModel namespace (as depicted in Figure 8.5). Because of this, activities can associate themselves with designer components, and can participate in design environments, in much the same way as Window Forms controls.

Figure 8.5. Activity type inheritance


A designer component holds specialized logic that renders a visual representation of the activity and allows it to be manipulated within a design environment. The base Activity class is associated with a designer, named ActivityDesigner:

 namespace System.Workflow.ComponentModel {   [Designer(typeof(ActivityDesigner), typeof(IDesigner))]   [Designer(typeof(ActivityDesigner), typeof(IRootDesigner))]   public class Activity : DependencyObject   {     ...   } } 


ActivityDesigner is defined in the System.Workflow.ComponentModel.Design namespace, and is shown in Listing 8.22. This is a mandatory base class for all activity designers. The IDesigner and IRootDesigner interfaces, both of which are implemented by ActivityDesigner, are defined in the System.ComponentModel.Design namespace.

Listing 8.22. ActivityDesigner

 namespace System.Workflow.ComponentModel.Design {   public class ActivityDesigner : IWorkflowRootDesigner, ...   {     public Activity Activity { get; }     public CompositeActivityDesigner ParentDesigner { get; }     ...   } } 


The ActivityDesigner class implements a set of interfaces, the most vital of which is System.Workflow.ComponentModel.Design.IWorkflowRootDesigner. IWorkflowRootDesigner, shown in Listing 8.23, inherits from System.ComponentModel.IRootDesigner (see Listing 8.24), which in turn inherits from System. ComponentModel.Design.IDesigner (see Listing 8.25). A root designer is simply a designer that can function at the top (root) of a hierarchy of designers and exhibits some special behaviors that are accorded only to the designer in this position.

Listing 8.23. IWorkflowRootDesigner

 namespace System.Workflow.ComponentModel.Design {   public interface IWorkflowRootDesigner : IRootDesigner   {     CompositeActivityDesigner InvokingDesigner { get; set; }     bool SupportsLayoutPersistence { get; }     ReadOnlyCollection<WorkflowDesignerMessageFilter>       MessageFilters { get; }     bool IsSupportedActivityType(Type activityType);   } } 


Listing 8.24. System.ComponentModel.Design.IRootDesigner

 namespace System.ComponentModel.Design {   public interface IRootDesigner : IDesigner   {     ViewTechnology[] SupportedTechnologies { get; }     object GetView(ViewTechnology technology);   } } 


IDesigner does not mandate any specific rendering or visualization technology. In fact, IDesigner is just an abstraction representing the visualization of an IComponent. As we will see later in this section, ActivityDesigner provides concrete support for default rendering and visualization behavior. In order to provide consistent visualization services across various activities, the WF programming model demands that any activity's designer component inherits from ActivityDesigner.

Listing 8.25. System.ComponentModel.Design.IDesigner

 namespace System.ComponentModel.Design {   public interface IDesigner : IDisposable   {     IComponent Component { get; }     DesignerVerbCollection Verbs { get; }     void DoDefaultAction();     void Initialize(IComponent component);   } } 


Although all activity designers implement IRootDesigner, for a given WF program being edited in a design environment, only the designer of the root activity is queried for its view. The view object is obtained by the design environment using the GetView method, which returns the actual user interface control with which the user interacts.

A root designer is free to return any view object that represents a design surface on which individual activity designers will be rendered. The GetView method of ActivityDesigner returns an object of type WorkflowView (shown in Listing 8.26).

Listing 8.26. WorkflowView

 namespace System.Workflow.ComponentModel.Design {   public class WorkflowView : System.Windows.Forms.UserControl,     System.Windows.Forms.IMessageFilter,     System.IServiceProvider,     IDesignerVerbProviderService   {     ...   } } 


As Listing 8.26 shows, WorkflowView is a Windows Forms UserControl and therefore can be hosted in any application. The Visual Studio Workflow Designer hosts this control in the Visual Studio document window. Think of this control as a design canvas that hosts individual designer components corresponding to the activities within the WF program being designed. Each activity designer renders the activity it represents within specific rectangular bounds inside the WorkflowView. The WorkflowView acts as a window manager and provides window management functionality including scrolling, hit testing, coordinate management, and other functions. Additionally, the WorkflowView provides a set of specialized capabilities for visualizing WF programs, including scrolling, zooming, panning, undo and redo, drag and drop, clipboard management, print and preview, and layout negotiation. We will discuss the WorkflowView in detail in the next section.

Because creating a USER32 HWND via CreateWindow is a performance hit, and because visualization of a WF program may often require tens or even hundreds of activity designers, making the WF program design surface windowless tremendously boosts overall designer performance.

As you can see from Listing 8.22, every ActivityDesigner holds a reference to its parent designer in the property named ParentDesigner. CompositeActivityDesigner, shown in Listing 8.27, inherits from ActivityDesigner and refers to a collection of contained designers that are the designer components associated with the child activities of the composite activity.

Listing 8.27. CompositeActivityDesigner

 namespace System.Workflow.ComponentModel.Design {   public abstract class CompositeActivityDesigner : ActivityDesigner   {     public virtual ReadOnlyCollection<ActivityDesigner>       ContainedDesigners { get; }     ...   } } 


Designer Base Classes

ActivityDesigner is the base class for all activity designers, but there are a number of derivatives of ActivityDesigner with which you should familiarize yourself in order to save time when developing custom designers. In many cases, you may be able to inherit behavior from the appropriate base class instead of developing it from scratch. Figure 8.6 shows the set of designer base classes found in the System.Workflow.ComponentModel.Design namespace.

Figure 8.6. ActivityDesigner and its derivatives


The development of a custom designer is typically needed only for composite activities (the association of a custom theme with a noncomposite activitydiscussed later in this sectionis one other situation in which a custom designer is required). Even for composite activities, sometimes you need not develop a custom designer at all but instead can associate the activity with one of the base designer types. For instance, the Sequence activity that we had developed earlier can simply use the SequenceDesigner by specifying it as a parameter to the System.ComponentModel.DesignerAttribute. This is shown in Listing 8.28.

Listing 8.28. Applying SequenceDesigner to the Sequence Activity

 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Design; namespace EssentialWF.Activities {   [Designer(typeof(SequenceDesigner),typeof(IDesigner))]   public class Sequence : CompositeActivity   {     ...   } } 


The program in Listing 8.29 shows two WriteLine activities within a Sequence.

Listing 8.29. Program with a Sequence Containing Two WriteLines

 <Sequence x:Name="Sequence1" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   <WriteLine x:Name="WriteLine1" Text="One" />   <WriteLine x:Name="WriteLine2" Text="Two" /> </Sequence> 


WF programs like the one shown in Listing 8.29 can be visually represented by any application or tool that knows how to host activity designers; we will discuss designer hosting later in this chapter. Figure 8.7 shows the visual representation of the program in Listing 8.29. The SequenceDesigner automatically renders the two WriteLine activities in sequential order.

Figure 8.7. Visual representation of program in Listing 8.29


As we know, a WF program is a hierarchical arrangement of activities. In the context of a design environment, this hierarchy will be mirrored by a hierarchy of activity designers. Figure 8.8 illustrates the correspondence between an activity hierarchy and its hierarchy of designer components. As depicted, the WriteLine activity does not define its own designer (that is why it renders as a simple rectangle in Figure 8.7), and defaults to using ActivityDesigner (which is specified by the base class Activity).

Figure 8.8. WYSIWYG hierarchy of designers for a WF program


Consider another example: the PrioritizedInterleave activity (discussed in Chapter 3). We can begin by associating the ParallelActivityDesigner provided by WF (see Figure 8.6) with PrioritizedInterleave:

 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Design; namespace EssentialWF.Activities {   [Designer(typeof(ParallelActivityDesigner, typeof(IDesigner))]   public class PrioritizedInterleave: CompositeActivity   {   ...   } } 


The association between an activity (that implements IComponent) and its designer is made using the DesignerAttribute type, defined in the System.ComponentModel.Design namespace. This is no different than associating a Windows Forms control (or other IComponent) with a designer.

The result of the association of the ParallelActivityDesigner with PrioritizedInterleave activity is shown in Figure 8.9.

Figure 8.9. ParallelActivityDesigner to visualize PrioritizedInterleave


As you can see in Figure 8.9, the choice of ParallelActivityDesigner as the designer component is unsatisfying (and perhaps even misleading) because it does not visually convey the special prioritization scheme employed by this composite activity. This presents a perfect opportunity to develop a custom designer.

As we discussed in Chapter 3, PrioritizedInterleave attaches an integer property called Priority to each of its child activities. Child activities with the same priority execute in an interleaved manner. Thus, it would be helpful for the designer component of PrioritizedInterleave to arrange the child activities (more accurately, the child activity designers) in a way that groups them by priority. Figure 8.10 illustrates one such approach, in which horizontal bands within the design area of PrioritizedInterleave contain child activities with the same priority.

Figure 8.10. PrioritizedInterleave designer component


One nice feature that can accompany this visual design is automatic setting of the Priority property when a child activity is added to the PrioritizedInterleave. When an activity is added to a specific band, the priority associated with that band is used to set the activity's Priority property. The code snippet in Listing 8.30 is the starting point for PrioritizedInterleaveDesigner, which inherits from CompositeActivityDesigner.

Listing 8.30. PrioritizedInterleaveDesigner

 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Design; using System.Windows.Forms; namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner :     CompositeActivityDesigner, IDesignerVerbProvider   {     ...   } } 


We will also need to modify PrioritizedInterleave to specify its association with PrioritizedInterleaveDesigner, as shown in Listing 8.31.

Listing 8.31. Associating PrioritizedInterleaveDesigner with PrioritizedInterleave

 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   [Designer(typeof(PrioritizedInterleaveDesigner), typeof(IDesigner))]   public class PrioritizedInterleave: CompositeActivity   {     ...   } } 


The (incomplete) implementation of PrioritizedParallelDesigner shown in the next section is for illustration purposes only. It makes several simplifying assumptions (for instance, it supports only noncomposite designers for its child activities) and provides only a cursory overview of all the steps involved in writing a custom composite designer. Our goal with this example is to give you a taste of the richness of capabilities you can build into designer components and not delve into details (which are beyond the scope of this book).

Attached Properties

The PrioritizedInterleave activity attaches a Priority property to its child activities by calling DependencyProperty.RegisterAttached. This is shown in Listing 8.32.

Listing 8.32. Priority as an Attached Property of PrioritizedInterleave

 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   [Designer(typeof(PrioritizedInterleaveDesigner), typeof(IDesigner))]   public class PrioritizedInterleave: CompositeActivity   {     public static DependencyProperty PriorityProperty =       DependencyProperty.RegisterAttached("Priority", typeof(int),         typeof(PrioritizedInterleave), new PropertyMetadata(1,         DependencyPropertyOptions.Metadata));     public static object GetPriority(object dependencyObject)     {       return ((DependencyObject)dependencyObject).         GetValue(PriorityProperty);     }     public static void SetPriority(object dependencyObject,       object priority)     {       ((DependencyObject)dependencyObject).         SetValue(PriorityProperty, priority);     }   } } 


Because Priority is an attached property, it is not available as a standard property on child activities of PrioritizedInterleave. The PrioritizedInterleaveDesigner must do some extra work in order to have Priority appear in the property grid (of the design environment) when a child activity is selected, as shown in Figure 8.11.

Figure 8.11. Priority property on child activities of PrioritizedInterleave


Specifically, PrioritizedInterleaveDesigner registers an extender provider for the Priority property by overriding ActivityDesigner.Initialize. This is shown in Listing 8.33.

Listing 8.33. PrioritizedInterleaveDesigner.Initialize

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner : ...   {     protected override void Initialize(Activity activity)     {       base.Initialize(activity);       IDesignerVerbProviderService verbProviderService =            (IDesignerVerbProviderService)GetService(             typeof(IDesignerVerbProviderService));       if (verbProviderService != null)           verbProviderService.AddVerbProvider(this);       IExtenderListService extenderListService =          (IExtenderListService)GetService(              typeof(IExtenderListService));       if (extenderListService != null)       {         bool foundExtender = false;         foreach (IExtenderProvider extenderProvider in            extenderListService.GetExtenderProviders())         {           if (extenderProvider.GetType() ==                   typeof(PriorityExtenderProvider))             foundExtender = true;         }         if (!foundExtender)         {           IExtenderProviderService extenderProviderService =                (IExtenderProviderService)GetService(                   typeof(IExtenderProviderService));           if (extenderProviderService != null)           {              extenderProviderService.AddExtenderProvider(new                   PriorityExtenderProvider());           }         }       }     }   } } 


Listing 8.34 shows the PriorityExtenderProvider class, which provides a custom implementation of the System.ComponentModel.IExtenderProvider interface.

Listing 8.34. PriorityExtenderProvider

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner : ...   {     // initial number of levels (rows)     internal readonly int MaxPriorityLevel = 3;     ...     [ProvideProperty("Priority", typeof(Activity))]     private class PriorityExtenderProvider : IExtenderProvider     {       public int GetPriority(Activity activity)     {       if (activity.Parent is PrioritizedInterleave)         return          (int)activity.GetValue(              PrioritizedInterleave.PriorityProperty);       else         return 0;     }     public void SetPriority(Activity activity, int priority)       {         if (activity.Parent is PrioritizedInterleave)           activity.SetValue(             PrioritizedInterleave.PriorityProperty, priority);       }       bool IExtenderProvider.CanExtend(object extendee)       {         return ((extendee != this) &&              (extendee is Activity) &&               (((Activity)extendee).Parent is PrioritizedInterleave));       }     }   } } 


The System.ComponentModel.ProvidePropertyAttribute is applied to the PriorityExtenderProvider. Doing so informs the design environment that the designer is interested in exposing an extended property whose name is Priority.

PriorityExtenderProvider implements IExtenderProvider.CanExtend and returns true for any activity whose Parent property is the PrioritizedInterleave activity object associated with the designer. The property grid control (which is the typical consumer of the IExtenderProvider) will look for Set<PropertyName> and Get<PropertyName> methods on the extender class when the user tries to configure the property. To meet that requirement, the PriorityExtenderProvider implements GetPriority and SetPriority methods.

Designer Verbs

Designer verbs are essentially menu commands that represent actions to be taken in the context of a designer. An activity designer can override the ActivityDesigner.Verbs property to provide its verbs to the design environment. These verbs will appear as items in the context menu of the activity. In some cases, an activity designer may need to apply verbs on behalf of other activity designers. In this situation, the activity designer that applies the verbs is required to implement IDesignerVerbProvider, an interface that is defined in the System.Workflow. ComponentModel.Design namespace.

PrioritizedInterleaveDesigner implements the IDesignerVerbProvider interface. The implementation of the GetVerbs method adds two verbs, "Increase Priority" and "Decrease Priority", to the context menu of each child activity (see Listing 8.35).

Listing 8.35. PrioritizedInterleaveDesigner.GetVerbs

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner : ...   {     ...     ActivityDesignerVerbCollection IDesignerVerbProvider.          GetVerbs(ActivityDesigner activityDesigner)     {       ActivityDesignerVerbCollection extendedVerbs = new              ActivityDesignerVerbCollection();       if (ContainedDesigners.Contains(activityDesigner))       {         ActivityDesignerVerb verb = new                 ActivityDesignerVerb(                   activityDesigner, DesignerVerbGroup.Actions,                    "Increase Priority", new                          EventHandler(OnIncreasePriority));         verb.Properties["Activity"] = activityDesigner.Activity;         extendedVerbs.Add(verb);         verb = new ActivityDesignerVerb(activityDesigner,           DesignerVerbGroup.Actions, "Decrease Priority", new                 EventHandler(OnDecreasePriority));         verb.Properties["Activity"] = activityDesigner.Activity;         extendedVerbs.Add(verb);       }       return extendedVerbs;     }     private void OnIncreasePriority(object sender, EventArgs e)     {       ActivityDesignerVerb verb = sender as ActivityDesignerVerb;       if (verb != null)       {         PrioritizedInterleave interleave = Activity as                 PrioritizedInterleave;         Activity activity = verb.Properties["Activity"] as Activity;         int newPriority =           (int)PrioritizedInterleave.GetPriority(activity) - 1;         if (newPriority < MaxPriorityLevel) {           PrioritizedInterleave.SetPriority(activity, newPriority);           PerformLayout();         }       }     }     private void OnDecreasePriority(object sender, EventArgs e)     {       ActivityDesignerVerb verb = sender as ActivityDesignerVerb;       if (verb != null)       {         Activity activity = verb.Properties["Activity"] as Activity;         int newPriority =            (int)PrioritizedInterleave.GetPriority(activity) + 1;         if (newPriority >= 0)         {           PrioritizedInterleave.SetPriority(activity, newPriority);           PerformLayout();         }       }     }   } } 


PrioritizedInterleaveDesigner can register itself as a verb provider by adding the following logic to its Initialize method (which we previously used to register the extender provider for the Priority property):

 IDesignerVerbProviderService verbProviderService =   (IDesignerVerbProviderService)GetService(     typeof(IDesignerVerbProviderService)); if (verbProviderService != null)   verbProviderService.AddVerbProvider(this); 


As proper cleanup for this initialization logic, PrioritizedInterleaveDesigner should override the Dispose method and call IDesignerVerbProvider-Service.RemoveVerbProvider.

The result of this work is shown in Figure 8.12, which displays the context menu for a child activity of PrioritizedInterleave.

Figure 8.12. Context menu for child activities of PrioritizedInterleave


Designer Glyphs

Another helpful feature we can add to PrioritizedInterleaveDesigner is a visual indication of the priority associated with each band of child activities (see the left edge of the PrioritizedInterleave designer as shown in Figure 8.11). We can achieve this using a designer adornment called a designer glyph. As shown in Listing 8.36, PrioritizedInterleaveDesigner overrides the ActivityDesigner.Glyphs.

Listing 8.36. PrioritizedInterleaveDesigner.Glyphs

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner : ...   {     protected override ActivityDesignerGlyphCollection Glyphs     {       get       {         ActivityDesignerGlyphCollection glyphs = base.Glyphs;         if (Expanded)         {           PrioritizedInterleave interleave = this.Activity as              PrioritizedInterleave;           for (int i = 0; i < MaxPriorityLevel; i++)             glyphs.Add(new PriorityIndicatorGlyph(i + 1));         }         return glyphs;       }     }   } } 


The PriorityIndicatorGlyph that is added to the collection of glyphs is a simple derivative of the base System.Workflow.ComponentModel.Design.DesignerGlyph type. PriorityIndicatorGlyph overrides the GetBounds method to return the rectangular bounds of the glyph circle and overrides the OnPaint method to perform the requisite graphics functions (see Listing 8.37).

Listing 8.37. PriorityIndicatorGlyph

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PriorityIndicatorGlyph : DesignerGlyph   {     private int priorityLevel;     public PriorityIndicatorGlyph(int priorityLevel)     {     this.priorityLevel = priorityLevel;   }   public override Rectangle GetBounds(ActivityDesigner designer,     bool activated)   {     PrioritizedInterleaveDesigner interleaveDesigner =        designer as PrioritizedInterleaveDesigner;     Rectangle bounds = new Rectangle(Point.Empty, new Size(16, 16));     bounds.X = interleaveDesigner.Bounds.Left - bounds.Width / 2;     bounds.Y = interleaveDesigner.Bounds.Top +        interleaveDesigner.CellOffset.Height +       ((this.priorityLevel - 1) *           interleaveDesigner.CellSize.Height) +         (interleaveDesigner.CellSize.Height / 2 - bounds.Height / 2);     return bounds;     }     protected override void OnPaint(Graphics graphics, bool activated,       AmbientTheme ambientTheme, ActivityDesigner designer)     {       Rectangle bounds = GetBounds(designer, activated);       int priorityLevels =           ((PrioritizedInterleaveDesigner)designer).MaxPriorityLevel;       int increment = 255 / Math.Max(1, priorityLevels);       Color fillColor = Color.FromArgb(Math.Min(255,             (this.priorityLevel - 1) * increment),            Math.Max(0, 255 - (increment *               (this.priorityLevel - 1))), 48);       using (Brush fillBrush = new SolidBrush(fillColor))         graphics.FillRectangle(fillBrush, bounds);       graphics.DrawRectangle(designer.DesignerTheme.BorderPen, bounds);       ActivityDesignerPaint.DrawText(graphics,          designer.DesignerTheme.BoldFont,          this.priorityLevel.ToString(),            bounds, StringAlignment.Center,             TextQuality.Aliased, Brushes.Black);     }   } } 


Designer Layout Management

A composite activity designer is responsible for arranging the designers of its child activities. So, as part of this management of canvas real estate, PrioritizedInterleaveDesigner overrides the OnLayoutSize and OnLayoutPosition methods that are defined by ActivityDesigner. OnLayoutSize returns the total size of the designer (in this case, by adding up the sizes of its contained bands). The OnLayoutPosition method calculates the offset of a child activity designer (see Listing 8.38).

Listing 8.38. PrioritizedInterleaveDesigner.Glyphs

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   public class PrioritizedInterleaveDesigner : ...   {     internal Size CellOffset     {       get       {         int headerHeight = TextRectangle.Height +             ImageRectangle.Height + 3 *             WorkflowTheme.CurrentTheme.AmbientTheme.Margin.Height;         return new Size(0, headerHeight);       }     }     internal Size CellSize     {       get       {         Size margin = WorkflowTheme.CurrentTheme.AmbientTheme.Margin;         Size size = DesignerTheme.Size;         size.Width += 6 * margin.Width;         size.Height += 6 * margin.Height;         return size;       }     }     protected override Size OnLayoutSize(          ActivityDesignerLayoutEventArgs e)     {       Size baseSize = base.OnLayoutSize(e);       if (Expanded)       {         PrioritizedInterleave interleave = Activity as                    PrioritizedInterleave;         Size designerSize = new Size(           CellSize.Width * 3, CellSize.Height * MaxPriorityLevel);         designerSize.Width += CellOffset.Width;         designerSize.Height += CellOffset.Height;         return designerSize;       }       else         return baseSize;     }     protected override void OnLayoutPosition(       ActivityDesignerLayoutEventArgs e)     {       base.OnLayoutPosition(e);       foreach (ActivityDesigner designer in ContainedDesigners)       {         int priority =            (int)PrioritizedInterleave.GetPriority(designer.Activity);         int position = (int)designer.Activity.GetValue(               PrioritizedInterleave.PositionProperty);         designer.Location = new Point(Location.X +              CellOffset.Width + (position * CellSize.Width) +              (3 * e.AmbientTheme.Margin.Width),              Location.Y + CellOffset.Height +                ((priority-1) * CellSize.Height) +              (3 * e.AmbientTheme.Margin.Height));       }     }   } } 


The logic for layout of child activities relies on the value of PrioritizedInterleave.PositionProperty. The property represents the horizontal position of a child activity within a priority band. This is an internal attached property registered by the PrioritizedInterleave for each child. Each time a child activity is added to the PrioritizedInterleave, it updates the value of this property (see Listing 8.39).

Listing 8.39. Setting PrioritizedInterleave.PositionProperty

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   [Designer(typeof(PrioritizedInterleaveDesigner), typeof(IDesigner))]   public class PrioritizedInterleave : CompositeActivity   {     internal static DependencyProperty PositionProperty =       DependencyProperty.RegisterAttached("Position",         typeof(int),         typeof(PrioritizedInterleave));     public PrioritizedInterleave() {       if(this.DesignMode)         this.Activities.ListChanged += new           EventHandler<ActivityCollectionChangeEventArgs>             (Activities_ListChanged);     }     void Activities_ListChanged(object sender,              ActivityCollectionChangeEventArgs e)     {       foreach (Activity child in e.AddedItems)       {         int priority = (int)child.GetValue(               PrioritizedInterleave.PriorityProperty);         int count = FindChildActivitiesOfSamePriority(priority);         child.SetValue(PositionProperty, count - 1);       }     }     int FindChildActivitiesOfSamePriority(int priority)     {       int count = 0;       foreach (Activity child in this.Activities)       {         if (priority == (int)child.GetValue(            PrioritizedInterleave.PriorityProperty))           count++;       }       return count;     }   } } 


Designer Themes

When a designer component renders itself on a design surface, it utilizes types in the System.Drawing namespace (such as Color, Pen, Font, and Image). But rather than fixing the choice of colors, pens, fonts, and images as part of the implementation of a designer, the WF design-time programming model allows the association of a separate component, called a designer theme, with an activity designer. The theme component holds the set of drawing-related resources to be used by the designer during its rendering.

Separation of resource selection from the activity designer component enables activity designer components to inherit the look and feel of the ambient design environment. The look and feel of an activity designer can be customized simply by changing the thematic elements in use within the broader design environment. This is a big help when you are trying to create domain-specific tooling experiences. Additionally, this approach allows the WF designer infrastructure to efficiently manage drawing-related resources.

A theme component is associated with an activity designer using the ActivityDesignerThemeAttribute, which is defined in the System.Workflow.ComponentModel.Design namespace. We can illustrate use of this attribute by associating a theme component with a designer for our WriteLine activity (see Listing 8.40).

Listing 8.40. Associating a DesignerTheme with a Designer

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   [Designer(typeof(WriteLineDesigner), typeof(IDesigner))]   public class WriteLine : Activity {...}   [ActivityDesignerTheme(typeof(WriteLineDesignerTheme))]   public class WriteLineDesigner : ActivityDesigner { }   public class WriteLineDesignerTheme : ActivityDesignerTheme   {     public WriteLineDesignerTheme(WorkflowTheme theme)       : base(theme)     {       this.ForeColor = Color.FromArgb(0xFF, 0x00, 0x00, 0x00);       this.BorderColor = Color.FromArgb(0xFF, 0xA5, 0x79, 0x73);       this.BorderStyle = DashStyle.Solid;       this.BackColorStart = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xDF);       this.BackColorEnd = Color.FromArgb(0xFF, 0xFF, 0xFF, 0x95);       this.BackgroundStyle = LinearGradientMode.Horizontal;     }   } } 


The WriteLineDesignerTheme class inherits from ActivityDesignerTheme as all theme components for activities must.

The designer's theme component is made available to the designer within its methods, notably the ActivityDesigner.OnPaint. Listing 8.41 (which presumes a PrioritizedInterleaveDesignerTheme associated with PrioritizedInterleaveDesigner) illustrates how the designer component of PrioritizedInterleave can make use of the drawing-related resources defined by the theme.

Listing 8.41. PrioritizedInterleaveDesigner.OnPaint

 // namespace imports same as Listing 8.30 namespace EssentialWF.Activities {   [ActivityDesignerTheme(typeof(PrioritizedInterleaveDesignerTheme))]   public class PrioritizedInterleaveDesigner : ...   {     ...     protected override void OnPaint(ActivityDesignerPaintEventArgs e)     {       base.OnPaint(e);       PrioritizedInterleave interleave = Activity as               PrioritizedInterleave;       PrioritizedInterleaveDesignerTheme interleaveTheme =          e.DesignerTheme as PrioritizedInterleaveDesignerTheme;       Rectangle titleRectangle = new Rectangle(          Location.X, Location.Y, Size.Width, CellOffset.Height);       e.Graphics.FillRectangle(            interleaveTheme.TitleBrush, titleRectangle);       Rectangle glassShadowRectangle = new           Rectangle(titleRectangle.Left, titleRectangle.Top,              titleRectangle.Width, titleRectangle.Height / 2); e.Graphics.FillRectangle(          interleaveTheme.GetGlassShadowBrush(       glassShadowRectangle), glassShadowRectangle);       ActivityDesignerPaint.DrawText(e.Graphics,            interleaveTheme.Font, Text,           TextRectangle, StringAlignment.Near,             e.AmbientTheme.TextQuality,             interleaveTheme.ForegroundBrush);       ActivityDesignerPaint.DrawImage(e.Graphics, Image,           ImageRectangle, DesignerContentAlignment.Fill);       ActivityDesignerPaint.DrawExpandButton(e.Graphics,           ExpandButtonRectangle, !Expanded, interleaveTheme);       if (Expanded)       {         Rectangle layoutRectangle = new Rectangle(            Location.X + CellOffset.Width,             Location.Y + CellOffset.Height,             Size.Width - CellOffset.Width, Size.Height                  CellOffset.Height);         e.Graphics.DrawLine(interleaveTheme.BorderPen,             layoutRectangle.Left, layoutRectangle.Top,             layoutRectangle.Right, layoutRectangle.Top);         for (int i = 1; i < MaxPriorityLevel; i++)           e.Graphics.DrawLine(interleaveTheme.PrioritySeparatorPen,                layoutRectangle.Left, layoutRectangle.Top +             CellSize.Height * i,             layoutRectangle.Right,              layoutRectangle.Top + CellSize.Height * i);         foreach (ActivityDesigner containedDesigner in             ContainedDesigners)         {           Rectangle bounds = containedDesigner.Bounds;           e.Graphics.DrawLine(interleaveTheme.BorderPen,             bounds.Left + bounds.Width / 2,             bounds.Top - 3 * e.AmbientTheme.Margin.Height,             bounds.Left + bounds.Width / 2, bounds.Top);           e.Graphics.DrawLine(interleaveTheme.BorderPen,             bounds.Left + bounds.Width / 2, bounds.Bottom,             bounds.Left + bounds.Width / 2,             bounds.Bottom + 3 * e.AmbientTheme.Margin.Height - 1);         }       }     }   } } 


Toolbox Items

A toolbox item is associated with a component using the ToolboxItemAttribute type defined in the System.ComponentModel namespace. Its job is to represent the component within the toolbox of a design environment, as shown in Figure 8.13. Listing 8.42 confirms that there is a toolbox item, named ActivityToolboxItem, associated with the Activity class.

Figure 8.13. Toolbox containing toolbox items


Listing 8.42. Activity Has an Associated ActivityToolboxItem

 namespace System.Workflow.ComponentModel {   [ToolboxItem(typeof(ActivityToolboxItem)]   public class Activity : DependencyObject   {     ...   } } 


ActivityToolboxItem derives from the ToolboxItem class defined in the System.Drawing.Design namespace.

Within a design environment, users will drag items from the toolbox onto their design surface. When this occurs during the design of a WF program, an activity toolbox item has an opportunity to configure the activity being added to the WF program. In the case of a composite activity, it is sometimes desirable to configure a set of child activities. For example, the Conditional composite activity might for convenience be automatically given two child activities, each of type Sequence. We can easily implement a toolbox item for Conditional that will automatically add these two child activities when a Conditional activity is added to a WF program via the toolbox.

First we attribute Conditional to refer to the custom toolbox item:

 using System; using System.Workflow.ComponentModel; using System.ComponentModel; using System.Drawing.Design; namespace EssentialWF.Activities {   [ToolboxItem(typeof(ConditionalToolboxItem))]   public class Conditional : CompositeActivity   {     ...   } } 


The ConditionalToolboxItem (see Listing 8.43) class overrides the CreateComponentsCore method.

Listing 8.43. ConditionalToolboxItem

 using System; using System.Workflow.ComponentModel; using System.ComponentModel; using System.Drawing.Design; using System.Runtime.Serialization; namespace EssentialWF.Activities {   [Serializable]   public class ConditionalToolboxItem : ActivityToolboxItem   {     public ConditionalToolboxItem(Type type)       : base(type) {}     private ConditionalToolboxItem(SerializationInfo info,       StreamingContext context)     {       base.Deserialize(info, context);     }     protected override IComponent[] CreateComponentsCore(       IDesignerHost designerHost)     {       CompositeActivity cond = new Conditional();       cond.Activities.Add(new Sequence());       cond.Activities.Add(new Sequence());       return new IComponent[] { cond };     }   } } 


The bitmap that is shown on the toolbox (refer to Figure 8.13) for a particular activity can be customized using the System.Drawing.ToolboxBitmapAttribute (see Listing 8.44).

Listing 8.44. Associating a Toolbox Bitmap with an Activity

 using System; using System.Workflow.ComponentModel; using System.Drawing; using System.Drawing.Design; namespace EssentialWF.Activities {   [ToolboxBitmap(typeof(Conditional), "conditional.png")]   public class Conditional : CompositeActivity   {     ...   } } 





Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net