Designer Re-Hosting


This section covers the topic of re-hosting the Windows Workflow Foundation designer. Re-hosting enables you to display the workflow designer outside the bounds of Visual Studio. This can be extremely useful in many scenarios. For example, a custom Windows Forms application that enables end users to monitor the execution of running workflows in a visual manner can be a very powerful tool. Although this type of designer re-hosting does not necessarily provide interactive features such as modifying the workflow, that type of behavior is also possible.

There may be scenarios in which end users are responsible for developing and/or maintaining workflows. Although most developers are comfortable inside the Visual Studio environment, most end users are not. Therefore, an application that is a pared-down version of Visual Studio may be appropriate, depending on the comfort level of your users.

With that in mind, you may choose to include some of the features that make designing workflows in Visual Studio efficient and straightforward. The workflow designer itself is based on the same code that exists in Visual Studio, so its behavior is consistent outside that environment. However, you can provide elements such as an activity toolbox that contains only activities appropriate for what end users might need. You can also embed the properties grid in custom applications. Examples that illustrate how to do these things are shown a little later in this chapter.

The Designer API

The following sections describe the important designer API classes that make hosting the workflow designer in custom applications possible.

WorkflowDesignerLoader

This abstract class is in the System.Workflow.ComponentModel.Design namespace, and is responsible for obtaining a reference to a workflow definition and subsequently building the activity tree that is loaded in the workflow designer. Because this class is marked as abstract, you must implement a concrete inherited version. You can write code to load workflow definitions from a multitude of data sources, such as a filesystem or database.

In the following example, a custom WorkflowDesignerLoader class called MyWorkflowLoader reads a XOML workflow definition file from the filesystem. The points of interest in this class are the PerformLoad and PerformFlush methods.

  public class MyWorkflowLoader : WorkflowDesignerLoader {     private string xomlPath = default(string);     public override string FileName     {         get { return this.xomlPath; }     }     public MyWorkflowLoader(string xomlPath)     {         this.xomlPath = xomlPath;     }     protected override void PerformLoad(         IDesignerSerializationManager serializationManager)     {         WorkflowMarkupSerializer workflowSerializer =             new WorkflowMarkupSerializer();         XmlReader xmlReader = XmlReader.Create(xomlPath);         // obtain a reference to the workflow's root activity         // via the markup serializer         Activity rootActivity =             workflowSerializer.Deserialize(xmlReader) as Activity;         if (rootActivity != null)         {             // set the class name on the designer of our workflow             this.SetBaseComponentClassName(rootActivity.GetType().FullName);             // add the root activity to the designer             // this call will recursively add all activities in the workflow             this.AddActivityToDesigner(rootActivity);         }     }     protected override void PerformFlush(         IDesignerSerializationManager serializationManager)     {         // obtain a reference to the designer host         IDesignerHost designerHost =             (IDesignerHost)this.GetService(typeof(IDesignerHost));         // obtain a reference to the root activity via the designer host         Activity rootActivity = (Activity)designerHost.RootComponent;         WorkflowMarkupSerializer workflowSerializer =             new WorkflowMarkupSerializer();         // this writes the XOML to the file system         XmlWriter xmlWriter = XmlWriter.Create(xomlPath);         workflowSerializer.Serialize(             serializationManager, xmlWriter, rootActivity);         // make sure to call the base flush method         // this will ensure the layout information gets saved if necessary         // i.e. for state machine workflows         base.PerformFlush(serializationManager);     }     public override TextReader GetFileReader(string filePath)     {         return File.OpenText(filePath);     }     public override TextWriter GetFileWriter(string filePath)     {         return File.CreateText(filePath);     } } 

PerformLoad uses the FileName property to read the XOML file and deserializes it using the Workflow MarkupSerializer class. The Deserialize method of WorkflowMarkupSerializer returns the root activity of the workflow, which is the workflow itself. This activity instance is then passed to the AddActivityToDesigner method, which is defined in the base WorkflowDesignerLoader class. When PerformLoad executes, the workflow definition is loaded and is ready to be displayed to the user. However, the custom workflow loader class must be instantiated and used in your application before this can happen. You learn how to do this shortly.

The PerformFlush method essentially does the opposite of PerformLoad - it takes the workflow definition as it exists in the designer and persists it to disk. It obtains an instance of the workflow by calling IDesignerHost.RootComponent. After all the work in PerformFlush has been completed, it calls the base implementation of the same method. There is code in the base class that persists layout information in a state-machine workflow.

DesignSurface

This class is the designer surface in the user interface. However, it is important to note that this class is not specific to Windows Workflow Foundation. The .NET Framework has quite a few classes that support the concept of designability - this being one of them. You can find this class in the System.ComponentModel.Design namespace of the Base Class Library.

Because this class implements the IServiceProvider interface, it acts as a source of services for the containing application. Designer services (discussed in more detail later) provide a pluggable architecture to implement designer-oriented behavior. By default, DesignSurface instances contain references to several services. Table 11-1 describes a few of these services.

Table 11-1: Designer-Related Services
Open table as spreadsheet

Service

Description

ISelectionService

Provides hooks into the design to allow the containing application to select individual components in the designer. In the case of workflows, these components are activities.

IDesignerHost

Allows the calling application to, among other things, access already-added services as well as add new services to the designer.

IComponentChangeService

Exposes events to inform subscribers when something happens within the designable components. For example, this can be used to notify users when the designer is in an unsaved state or includes an undo function.

WorkflowView

The WorkflowView class is located in System.Workflow.ComponentModel.Design and is the UI control that does the drawing of the visual workflow representation. This class is workflow specific, unlike the other design types discussed so far.

To do the simplest of tasks with the workflow designer, you don’t have to interact much with the WorkflowView class aside from adding it to your Windows Form. However, it does expose useful functionality, such as saving a snapshot of the workflow designer as an image, fitting the designer’s view to the current screen size, and manipulating the selection of designer components.

Designer Services

Designer services provide distinct pieces of functionality during design time. Examples of designer services include context menu services, services for generating code-beside code, and services to enable toolbox functionality similar to the Visual Studio Toolbox. You can inherit many of these services from base API classes or implement them from base API interfaces and then custom-develop them for specific solutions.

Some of the interfaces that you can implement to create custom designer services are in the base .NET API in the System.ComponentModel.Design namespace. Others are workflow specific and can be found in various namespaces under the System.Workflow.ComponentModel namespace.

The non-workflow-specific designer service interfaces include IEventBindingService and IProperty ValueUIService. The IEventBindingService service defines the public members that provide the functionality to create event handler methods in a workflow’s code-beside class. (This is similar to double-clicking an event name in the Properties Explorer window in Visual Studio.) The IPropertyValue UIService service defines the functionality that allows custom icons and tool tips to be displayed on the property grid. Figure 11-2 shows an example of what this service can do. In this screenshot, the InitializeTimeoutDuration and TimeoutDuration properties indicate that they are bindable workflow properties. You can also use the IMenuCommandService and IToolboxService designer services in workflow-related applications.

image from book
Figure 11-2

Other service interfaces are included in the Windows Workflow Foundation API itself. Two examples of these workflow designer services are IMemberCreationService and IDesignerGlyphProvider Service.

When implemented in a concrete class, IMemberCreationService is responsible for creating new members (properties, fields, and events) in classes. This is very useful in workflows because of the common need to create new fields and properties in a workflow’s code-beside class for binding. This interface has methods like CreateEvent, CreateField, and CreateProperty.

The IDesignerGlyphProviderService interface defines a contract for a service that provides glyphs to workflow activities. Glyphs, represented by the DesignerGlyph class, are graphical indicators that can be displayed on activities to indicate the activity’s status or anything else related to that activity. For example, the Workflow Monitor in the Windows Workflow Foundation SDK uses glyphs to indicate whether an activity was executed in a given workflow instance.

Bringing It Together

To better illustrate hosting the workflow designer, this section describes a sample application developed for this book. If you have not worked with designers and associated concepts such as designer services and providing toolbox functionality, this topic can be a bit tricky to understand at first; however, the concrete code example provided here should help speed your comprehension process.

The goals for this sample application are as follows:

  • Allow opening and saving from the filesystem of workflows defined as XAML

  • Provide a simple toolbox to show the out-of-the-box workflow activities

  • Allow properties of the workflow and activities to be edited using a standard properties UI

For this example, the first piece of the application to be developed is the nonvisual section. A good place to start is to develop the WorkflowDesignerLoader class. The code shown in the “WorkflowDesignerLoader” section earlier in this chapter accurately represents the implementation for this sample, so it does not need to be displayed again.

Next, the development of the front-end code can commence. Start by fleshing out the user interface. Create a new Windows Forms project in Visual Studio, and rename the default form WorkflowDesigner.cs. To provide an environment similar to that of Visual Studio, the SplitterPanel control is used to segregate the various pieces of the UI.

In this example, the left side of the screen is reserved for the toolbox, the center for the actual workflow designer, and the right side for the properties grid. You can easily achieve this by using two SplitterPanel controls. One SplitterPanel will contain another SplitterPanel in its Panel1 property. When you do this, there are three panels available for the toolbox, designer, and properties grid, respectively.

You implement the toolbox simply by using a ListView control, and the properties on the right side use the PropertyGrid control. After these controls are added, along with a ToolStrip for easy access to opening and saving functions, the form looks like Figure 11-3.

image from book
Figure 11-3

The following code creates the Windows Forms fields and the constructor, which prepares the designer for use:

  private DesignSurface designSurface = new DesignSurface(); private WorkflowView workflowView = null; private IDesignerHost designerHost = null; private ISelectionService selectionService = null; private IComponentChangeService componentChangeService = null; private TypeProvider typeProvider = null; private bool isDirty = false; public WorkflowDesigner() {     InitializeComponent();     // get a reference to the designer host service     this.designerHost =         (IDesignerHost)designSurface.GetService(typeof(IDesignerHost));     // get the selection service instance     this.selectionService =         (ISelectionService)this.designerHost.GetService(typeof(ISelectionService));     // subscribing to this event will allow us to     // display the correct properties in the grid     this.selectionService.SelectionChanged +=         new EventHandler(selectionService_SelectionChanged);     // get the component change service     this.componentChangeService =         (IComponentChangeService)this.designerHost.GetService(             typeof(IComponentChangeService));     // subscribing to this event will allow us to     // monitor for changes in the workflow     this.componentChangeService.ComponentChanged +=         new ComponentChangedEventHandler(componentChangeService_ComponentChanged);     this.typeProvider = new TypeProvider(this.designerHost);     this.designerHost.AddService(typeof(ITypeProvider), typeProvider);     this.LoadToolbox(); } 

In this code, the constructor obtains references to various designer surfaces that can help the application function as desired. ISelectionService notifies the form when the selection of components in the designer is changed. This allows the property grid to be updated whenever a different component is selected.

Next, IComponentChangeService notifies the form through the ComponentChanged event that something in the workflow has changed. This allows the form to keep track of a workflow’s status by using the isDirty field. If you subscribe to the form’s Closing event, a warning can be displayed to the user asking whether the workflow should be saved first.

The constructor also takes care of creating an ITypeProvider service and adding it to the designer host’s services. A type provider does what its name implies. If the designer needs access to a type in an assembly, it has to get it from somewhere. Therefore, if a type provider is added to the designer host’s services, and all necessary assemblies are added to that provider, everything should work fine. (The code that adds an assembly reference to the type provider is shown next.) Finally, the constructor makes a call to the LoadToolbox method.

The next block of code is the method that loads the ListView control with activities from the System.Workflow.Activities assembly:

  private void LoadToolbox() {     Assembly assembly = Assembly.Load("System.Workflow.Activities, " +         "Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");     this.typeProvider.AddAssembly(assembly);     Type toolboxItemType = typeof(ToolboxItemAttribute);     foreach (Type t in assembly.GetExportedTypes())     {         if (t.IsSubclassOf(typeof(Activity)))         {             object[] attributes =                 t.GetCustomAttributes(toolboxItemType, false);             if (attributes.Length == 0 || (attributes.Length == 1 &&                 !ToolboxItemAttribute.None.Equals(attributes[0])))             {                 ListViewItem lvi = new ListViewItem(t.Name);                 lvi.Tag = t;                 lvwToolbox.Items.Add(lvi);             }         }     } } 

In this code block, the assembly is loaded using the Assembly.Load method and then it is added to the type provider that was introduced previously. Next, the code loops through each of the public classes in the assembly and checks to see whether it inherits from Activity. If there is a match, the type’s attributes are checked for a ToolboxItemAttribute. This is logic that determines whether the activity is added to the toolbox, which is important because it may not make any sense to add certain activities. For example, an IfElseBranch activity should not be able to be directly added to a workflow; rather, it is always a branch of an IfElse activity.

Back in the form’s constructor, the code subscribes to the events of a couple of the designer services. The event handlers are shown in the following code:

  private void componentChangeService_ComponentChanged(object sender,     ComponentChangedEventArgs e) {     // only call this code if the workflow has not yet been changed     if (!this.isDirty)     {         // TODO: implement something here to indicate the workflow was changed         this.isDirty = true;     } } private void selectionService_SelectionChanged(object sender, EventArgs e) {     propertyGrid.SelectedObjects =         (object[])this.selectionService.GetSelectedComponents(); } 

In this code, the ComponentChanged event handler monitors for changes in the workflow designer and sets the isDirty flag to true. You could also add code that visually indicates to the user that the workflow is dirty. Visual Studio does this by placing an asterisk in the document’s title tab.

The SelectionChanged event handler for the selection service simply notifies the PropertyGrid control that the selections in the designer have changed. This then changes the properties that are displayed in the grid. This is a simple but necessary step.

The next few methods are related to opening and saving XOML files from and to the filesystem.

The first method, shown in the following code, is EnsureSaved. This is simply logic that uses the isDirty flag to see whether the user needs to be prompted to save before doing something else. This method is used when the user tries to open a new workflow before saving the current workflow and when the user is exiting the application.

  private bool EnsureSaved() {     if (this.isDirty)     {         DialogResult res = MessageBox.Show(             "The current workflow is not saved. Save before continuing?",             "Save?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Stop);         if (res == DialogResult.Cancel)             return false;         if (res == DialogResult.Yes)             this.SaveCurrent();     }     return true; } 

The next method is the event handler for the tool strip’s Open button:

  private void btnOpen_Click(object sender, EventArgs e) {     if (this.EnsureSaved())     {         OpenFileDialog ofd = new OpenFileDialog();         ofd.Filter = "*.xoml|*.xoml";         DialogResult res = ofd.ShowDialog();         if (res == DialogResult.OK)         {             this.designSurface.BeginLoad(                 new KittaWorkflowDesignerLoader(ofd.FileName));             propertyGrid.Site = this.designerHost.RootComponent.Site;             IRootDesigner rootDesigner =                 (IRootDesigner)this.designerHost.GetDesigner(                     this.designerHost.RootComponent);             this.workflowView =                 (WorkflowView)rootDesigner.GetView(ViewTechnology.Default);             this.workflowView.Dock = DockStyle.Fill;             innerSplitContainer.Panel2.Controls.Add(workflowView);             object[] selectedComponent =                 new object[] { this.designerHost.RootComponent };             this.selectionService.SetSelectedComponents(selectedComponent);             this.isDirty = false;         }     } } 

The first thing this method does is to call the EnsureSaved method to make sure that the user saved the workflow before opening a new one. The user is then presented with a dialog box to choose the XOML file to edit. This is where the workflow-specific code begins.

The DesignerSurface class has a method called BeginLoad that takes an instance of a Workflow DesignerLoader class. Of course, the loader developed for this example is what is passed. The remainder of the method configures the WorkflowView and the PropertyGrid controls.

The selection service configured in the form’s constructor comes in handy here because it enables you to dictate which activity is selected by default. This is the opposite of what was discussed previously when the selection service was used to notify the host when something new was selected, not to actually programmatically select activities.

The remaining save and close methods are shown in the following code:

  private void WorkflowDesigner_FormClosing(object sender, FormClosingEventArgs e) {     e.Cancel = !this.EnsureSaved(); } private void btnSave_Click(object sender, EventArgs e) {     this.SaveCurrent(); } private void SaveCurrent() {     KittaWorkflowDesignerLoader workflowLoader =         (KittaWorkflowDesignerLoader)this.designSurface.GetService(             typeof(WorkflowDesignerLoader));     workflowLoader.Flush();     this.isDirty = false; } 

In the SaveCurrent method, the custom WorkflowDesignerLoader class is obtained by calling the design surface’s GetService method. Then the Flush method is called, which in turn saves the current workflow to the file it was opened from. Finally, the isDirty flag is set to false because the workflow is now saved.

Figure 11-4 shows what the example application looks like when running and with a loaded workflow definition.

image from book
Figure 11-4

The last piece of code pertaining to the designer re-hosting example enables the user to drag and drop items from the toolbox to the designer. The most common way to achieve drag-and-drop functionality is to subscribe to the ListView’s MouseMove event, as shown here:

  private void lvwToolbox_MouseMove(object sender, MouseEventArgs e) {     if (e.Button == MouseButtons.Left && lvwToolbox.SelectedItems.Count == 1)     {         Type t = lvwToolbox.SelectedItems[0].Tag as Type;         ToolboxItem toolboxItem = null;         if (t != null && (toolboxItem = ToolboxService.GetToolboxItem(t)) != null)         {             IDataObject theData = null;              IComponent[] components = toolboxItem.CreateComponents();             if (components != null && components.Length > 0)             {                 Activity newActivity = (Activity)components[0];                 IServiceProvider serviceProvider =                     (IServiceProvider)this.workflowView;                 ComponentSerializationService service = serviceProvider.GetService(                     typeof(ComponentSerializationService))                         as ComponentSerializationService;                 if (service != null)                 {                     theData =                         CompositeActivityDesigner.SerializeActivitiesToDataObject(                             this.workflowView, new Activity[] { newActivity });                     lvwToolbox.DoDragDrop(theData,                         DragDropEffects.Copy | DragDropEffects.Move);                 }             }         }     } } 

The event handler in this code includes a lot of checks to make sure things are good enough to start the drag-and-drop process. First, the code ensures that the user has actually clicked something by inspecting the event argument’s Button property against the MouseButtons.Left enumeration value. After this, the code extracts a Type instance from the selected ListViewItem and goes through steps to create an instance of that type.

The ToolboxItem class is used here to create a new instance of whatever the activity type is. For example, the way in which an IfElseActivity instance is created is interesting because two IfElseBranch Activity instances are created and added to the IfElseActivity instance. That is why you see two default branches when dragging that activity from the Visual Studio Toolbox.

After the code has an instance of the activity being dragged from the toolbox, it needs to be serialized so that it can be passed to the designer using the drag-and-drop framework. This serialization functionality is provided by the ComponentSerializationService designer service. Finally, the serialized data is passed to the DoDragDrop method of the toolbox ListView. After the user drags the mouse to a valid spot on the designer, the WorkflowView’s code takes care of the rest.

Although this example covers quite a bit in the way of loading a workflow and providing visual editing, it could do a lot more. The following list represents features that could be useful in a workflow designer hosting scenario:

  • Improve the file management and error handling. This example was built with the bare minimum code to show workflow-specific functionality.

  • Allow the workflow to have a code-beside class and implement the IMemberCreationService to allow new events, fields, and properties to be added.

  • Implement IEventBindingService to allow various activities to perform logic when their events fire.

  • Provide a more robust toolbox with images, and filter out activities in a context-specific manner. For example, don’t show the State activity for sequential workflows.

  • Allow the configuration of assemblies to check for activities for the toolbox.

  • Implement code to allow the red validation glyphs to show error messages and transport the user to the affected property in the PropertyGrid.

  • Implement the IPropertyValueUIService interface to allow the properties window to show custom icons for dependency properties.

The State-Machine Designer

As you’ve probably noticed in your workflow adventures so far, the state-machine designer has an added piece of functionality that does not apply to the sequential workflow designer. The designer for sequential workflows recreates the onscreen view every time a workflow definition is loaded - meaning that one activity is at the top followed by subsequent activities, and the spacing between activities is dynamic, based on the definition. Conversely, the state-machine workflow designer permits the workflow developer to place objects in user-defined positions on the canvas. This allows you to appropriately place State activity representations in clean arrangements on the screen.

However, this layout data must obviously be saved and subsequently reloaded when a state machine is loaded into the designer. Visual Studio handles this by saving a .layout file along with the workflow definition file.

Figure 11-5 shows a sample state-machine workflow.

image from book
Figure 11-5

The following code listing is the layout file for the workflow shown in Figure 11-5:

  <StateMachineWorkflowDesigner   xmlns:ns0="clr-namespace:System.Drawing;Assembly=System.Drawing, Version=2.0.0.0,   Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Name="Workflow1"   Location="30,30" Size="600, 287" AutoSizeMargin="16, 24"   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <StateMachineWorkflowDesigner.DesignerConnectors>     <StateDesignerConnector TargetConnectionIndex="0"       TargetStateName="stateActivity1" SourceConnectionIndex="0"       TargetConnectionEdge="Top" SetStateName="setStateActivity1"       SourceStateName="Workflow1InitialState" SourceConnectionEdge="Right"       TargetActivity="stateActivity1" SourceActivity="Workflow1InitialState"       EventHandlerName="eventDrivenActivity1">       <StateDesignerConnector.Segments>         <ns0:Point X="194" Y="115" />         <ns0:Point X="263" Y="115" />         <ns0:Point X="263" Y="189" />       </StateDesignerConnector.Segments>     </StateDesignerConnector>     <StateDesignerConnector TargetConnectionIndex="0"       TargetStateName="stateActivity2" SourceConnectionIndex="0"       TargetConnectionEdge="Top" SetStateName="setStateActivity2"       SourceStateName="stateActivity1" SourceConnectionEdge="Right"       TargetActivity="stateActivity2" SourceActivity="stateActivity1"       EventHandlerName="eventDrivenActivity2">       <StateDesignerConnector.Segments>         <ns0:Point X="330" Y="230" />         <ns0:Point X="355" Y="230" />         <ns0:Point X="355" Y="173" />         <ns0:Point X="499" Y="173" />         <ns0:Point X="499" Y="181" />       </StateDesignerConnector.Segments>     </StateDesignerConnector>   </StateMachineWorkflowDesigner.DesignerConnectors>   <StateMachineWorkflowDesigner.Designers>     <StateDesigner Name="Workflow1InitialState" Location="47, 74" Size="163, 80"       AutoSizeMargin="16, 24">       <StateDesigner.Designers>         <EventDrivenDesigner Size="110, 152" Name="eventDrivenActivity1"           Location="55, 105">           <EventDrivenDesigner.Designers>             <SetStateDesigner Size="90, 50" Name="setStateActivity1"               Location="65,177" />           </EventDrivenDesigner.Designers>         </EventDrivenDesigner>       </StateDesigner.Designers>     </StateDesigner>     <StateDesigner Name="stateActivity1" Location="183, 189" Size="160, 80"       AutoSizeMargin="16, 24">       <StateDesigner.Designers>         <EventDrivenDesigner Size="110, 152" Name="eventDrivenActivity2"           Location="191, 220">           <EventDrivenDesigner.Designers>             <SetStateDesigner Size="90, 50" Name="setStateActivity2"               Location="201,292" />           </EventDrivenDesigner.Designers>         </EventDrivenDesigner>       </StateDesigner.Designers>     </StateDesigner>     <StateDesigner Name="stateActivity2" Location="419, 181" Size="160, 80"       AutoSizeMargin="16, 24" />   </StateMachineWorkflowDesigner.Designers> </StateMachineWorkflowDesigner> 

When you’re working with workflow definitions in the filesystem, saving this layout information is easy. Code in the base WorkflowDesignerLoader class takes care of it for you. All you have to do is call base.PerformFlush from within an overridden PerformFlush method, as follows:

 protected override void PerformFlush(     IDesignerSerializationManager serializationManager) {     IDesignerHost designerHost =         (IDesignerHost)this.GetService(typeof(IDesignerHost));     Activity rootActivity = (Activity)designerHost.RootComponent;     WorkflowMarkupSerializer workflowSerializer         = new WorkflowMarkupSerializer();     XmlWriter xmlWriter = XmlWriter.Create(xomlPath);     workflowSerializer.Serialize(         serializationManager, xmlWriter, rootActivity);     // make sure to call the base flush method     // this will ensure the layout information gets saved if necessary     // i.e. for state machine workflows     base.PerformFlush(serializationManager); }

However, you may not be storing workflow definitions in the filesystem - you may be using a database. In this case, you should not make the base.PerformFlush method call. Instead, write code to call the WorkflowDesignerLoader’s SaveDesignerLayout method. This method has a signature that looks like the following code. Based on the arguments this method accepts, you simply need to write an XmlWriter instance to create something you can put in a database, like a string.

  protected void SaveDesignerLayout(XmlWriter layoutWriter,     ActivityDesigner rootDesigner, out IList layoutSaveErrors); 

On the flip side, the following code shows how to load layout data from the filesystem and apply it to a state-machine workflow in a custom WorkflowDesignerLoader class. Just as you can when saving the layout information, you can easily modify this code to obtain a string representation of the layout XML from a database and pass it to the LoadDesignerLayout method.

  private IList LoadStateMachineLayout(string fileName) {     if (!File.Exists(fileName))     {         throw new ArgumentException("The file " + fileName + " does not exist.");     }     else     {         IList layoutLoadErrors;         XmlReader xmlReader = XmlReader.Create(fileName);         this.LoadDesignerLayout(xmlReader, out layoutLoadErrors);         return layoutLoadErrors;     } } 

The LoadStateMachineLayout method shown here simply checks to make sure that the layout file exists. If it does, a new XmlReader instance is created and used to read the file by using the LoadDesignerLayoutMethod. If there are errors loading the file, an IList collection is returned.



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

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