Designer Hosting


As mentioned earlier, one of the goals of the WF programming model is to enable WF programs to be authored by non-developers. Because WF programs can be developed using domain-specific sets of activities, it often makes sense to offer custom domain-specific design environments to WF program authors who are not developers. On the other hand, Visual Studio will be the first choice of many developers who build programs and applications using WF. The purpose of this section is to explore some territory that lies between these two ends of a spectrumVisual Studio at the one end, and entirely custom design environments built from scratch at the other. To be specific, the WF visualization framework (supporting both activities and declarative conditions and rules) has been designed so that visualization components can be hosted in design environments other than Visual Studio. Thus, you do not need to start from scratchunless your scenario warrants itwhen building design environments for your WF program authors.

The WorkflowView Control Revisited

As we learned in the previous section, every activity type has a designer component with which it is associated. The base class of these activity designer components is ActivityDesigner. Activity designer components are windowless entities. The designer component of each activity in a WF program renders itself within the bounds allotted to it on a design canvas. All activity designer components inherit from the ActivityDesigner type an implementation of the System.ComponentModel.Design.IRootDesigner interface. Of interest to us in this section is the fact that this implementation's IRootDesigner.GetView method returns an object of type System.Workflow.ComponentModel.Design.WorkflowView.

The WorkflowView type is a derivative of System.Windows.Forms.User-Control. The WF extensions for Visual Studio host this control in the Visual Studio document window (as shown in Figure 8.14). WorkflowView is the design canvas on which individual activity designers are rendered. WorkflowView is responsible for dispatching messages to individual activity designers and also provides window management, command routing, scrolling, drag and drop, rubber banding and connection management, zooming, panning, layout management, glyphs, and print and preview support. We will see a sample of how to use WorkflowView outside of Visual Studio a little later in this section.

Figure 8.14. The WorkflowView control hosted in Visual Studio


System.Workflow.ComponentModel.Design.WorkflowOutline is another Windows Forms user control (also shown in Figure 8.14) that can be used outside of Visual Studio. This control visualizes the structure of a WF program in its true formas a tree.

Hosting activity designers in a custom design environment carries some responsibilities. The design environment must associate components (activities) with instances of their designer components, and must also support the designer components in specific ways. Types in the System.ComponentModel.Design namespace provide off-the-shelf capabilities that can help you build custom design environments. Specifically, System.ComponentModel.Design introduces the concept of a designer host that is responsible for managing the interaction between an IComponent (such as an activity) and an associated IDesigner (in the case of an activity, a derivative of ActivityDesigner).

A designer host is a component that implements the IDesignerHost interface, which is defined in the System.ComponentModel.Design namespace. IDesignerHost is a service container, and as such can be configured to provide support for undo operations, clipboard functions, and many other capabilities that activity designer components rely upon to deliver their visualization functionality.

An activity designer component can access the ambient IDesignerHost via the associated activity's Site property, like so:

 // Get the designer host IComponent component = this.Activity as IComponent; IDesignerHost host = component.Site.GetService(    typeof(IDesignerHost)) as IDesignerHost; 


Every design environment that hosts activity designers can provide a specific implementation of IDesignerHost. Thus, activity components must not rely upon the details of any specific implementation of IDesignerHost.

A designer host is also responsible for design-time serialization of components (activities) and their associated designers. To do this, the designer host relies on a designer loader. The designer loader may, in turn, utilize individual serializers (discussed in Chapter 4) to serialize components (activities). The System.Workflow. ComponentModel.Design namespace includes an abstract class, WorkflowDesignerLoader, which provides standard design-time serialization capabilities for WF programs. Included is the serialization of designer-specific layout information (into a file with a .layout extension), which is leveraged by activity designers that inherit from FreeFormActivityDesigner (one of the base types for activity designers defined in the System.Workflow.ComponentModel.Design namespace).

Within a design environment, a design surface manages a designer host, and presents a self-contained user interface with which the user interacts (a design environment may have multiple design surfaces). System.ComponentModel.Design. DesignSurface is the base class for design surfaces. The design surface is not the same thing as the canvas (WorkflowView) that manages the rendering of activity designers. The canvas is obtained via the View property of DesignSurface, which in its getter implementation invokes the GetView method of the current IRootDesigner (obtained from the designer host). As mentioned previously, ActivityDesigner implements IRootDesigner, and in its implementation of the IRootDesigner.GetView method returns a WorkflowView object.

The relationships between a design surface, its designer host, and a designer loader are depicted in Figure 8.15.

Figure 8.15. A design surface with a designer host and a designer loader


With this information, we can write a Windows Forms application that hosts the designer component of the Sequence composite activity. We subscribe to the Form.Load event within the InitializeComponent method (not shown), and in the event handler for this event, we create and configure the design surface (see Listing 8.45).

Listing 8.45. Hosting the Designer in a Windows Form Application

 using System; using System.Data; using System.Drawing; using System.Text; using System.IO; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.Windows.Forms; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Design; using System.Workflow.ComponentModel.Compiler; using EssentialWF.Activities; namespace DesignerHosting {   public partial class Form1 : Form   {     public Form1()     {       // Will subscribe to the Form.Load event       InitializeComponent();     }     private void Form1_Load(object sender, EventArgs e)     {       // Set up the design surface       DesignSurface surface = new DesignSurface();       DesignerLoader loader = new WorkflowLoader();       surface.BeginLoad(loader);       // Get the designer host       IDesignerHost host = surface.GetService(         typeof(IDesignerHost)) as IDesignerHost;       // Build a WF program       Sequence sequence = host.CreateComponent(         typeof(Sequence)) as Sequence;       WriteLine wl = new WriteLine();       sequence.Activities.Add(wl);       host.RootComponent.Site.Container.Add(wl);       // Set up the canvas       Control canvas = surface.View as Control;       canvas.Parent = this;       canvas.Dock = DockStyle.Fill;       canvas.Refresh();       host.Activate();       surface.EndLoad();     }   }   public class WorkflowLoader : WorkflowDesignerLoader   {     public override TextReader GetFileReader(string filePath)     {       return new StreamReader(         new FileStream(filePath, FileMode.Open));     }     public override TextWriter GetFileWriter(string filePath)     {       return new StreamWriter(         new FileStream(filePath, FileMode.OpenOrCreate));     }     ...   } } 


The Form.Load event handler creates a design surface and passes a designer loader to the DesignSurface.BeginLoad method. This initializes the services required by activity designer components. Next, we obtain the designer host from the design surface and use it to create a Sequence activity. We add a couple of WriteLine activities to the Sequence in the usual manner. The WriteLine activities are also added to the container associated with the Site of the designer host's root component. Finally, we obtain the WorkflowView from the design surface and prepare it for rendering.

When we run our application, we see our form as shown in Figure 8.16.

Figure 8.16. A form that hosts activity designer components


Dynamic Resolution of Activity Designers

As we have learned, an activity type is associated with a designer component using DesignerAttribute. There are situations, though, in which a design environment may need to override this association and dynamically provide a custom activity designer component for an activity. The mechanism for doing this is not specific to the WF visualization infrastructure; dynamic resolution of an IDesigner to be used with an IComponent is achieved using ITypeDescriptorFilterService, which is found in the System.ComponentModel namespace.

In the following code snippet, we dynamically replace the activity designer for the WriteLine activity type:

 // Set up the design surface DesignSurface surface = new DesignSurface(); DesignerLoader loader = new WorkflowLoader(); surface.BeginLoad(loader); // Get the designer host IDesignerHost host = surface.GetService(   typeof(IDesignerHost)) as IDesignerHost; ITypeDescriptorFilterService oldFilter =   (ITypeDescriptorFilterService) designerhost.GetService(     typeof(ITypeDescriptorFilterService)); if (oldFilter != null)   host.RemoveService(typeof(ITypeDescriptorFilterService)); host.AddService(typeof(ITypeDescriptorFilterService), new CustomDesignerFilterService(oldFilter)); 


A custom ITypeDescriptorFilterService, shown next, can effectively replace the DesignerAttribute associated with WriteLine with a new Designer-Attribute that indicates a custom designer component:

 namespace DesignerHosting {   public class CustomDesignerFilterService :     ITypeDescriptorFilterService   {     private ITypeDescriptorFilterService oldService = null;     public CustomDesignerFilterService(       ITypeDescriptorFilterService service)     {       this.oldService = service;     }     bool ITypeDescriptorFilterService.FilterAttributes(       IComponent component, IDictionary attributes)     {       this.oldService.FilterAttributes(component, attributes);       if (component is WriteLine)       {         DesignerAttribute da = new DesignerAttribute(         typeof(CustomWriteLineDesigner));         attributes[da.TypeId] = da;       }       return true;     }     ...   } } 





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