种种原因,好久没在这里写东西了,最近有点闲暇,打算写点工作流的东东,想来想去还是从工作流设计器说起,为用户提供一个轻量级设计器来定制工作流相信是任何与工作流相关的软件开发所必须做到的,后面的文章会一步步阐述如何使用实现它.
种种原因,好久没在这里写东西了,最近有点闲暇,打算写点工作流的东东,想来想去还是从工作流设计器说起,为用户提供一个轻量级设计器来定制工作流相信是任何与工作流相关的软件开发所必须做到的,后面的文章会一步步阐述如何使用实现它.OK,先看效果图片:
我认为有必要对图中的设计器做些说明,我已经在上图中圈出来了:
1.我们的设计器是面向最终用户,而不是开发者,所以设计器标题中的顺序工作流之类的字眼我们要解决
2.活动的名称必须可以定义为我们需要的文字,而不是E文的类名,后面的codeActivity1就是未处理的效果
3.我们的工具箱中同样要显示活动中文名称
4.我们提供一个属性窗体显示活动信息,当然这个是可选的
5.当按钮点击工作流开始箭头图标时候必须屏蔽掉 View SequentialWorkflow, View Cancel Handler和View Fault Handlers这三个菜单
6.提供一个上下文菜单在设计器中进行一般操作
本文先从如何宿主设计器说起,然后一步步完成上述的功能。我们先要了解一些会用到的工作流设计器相关的类:
WorkflowDesignerLoader: 负责从工作流的导入导出,直白点说就是完成工作流的序列化和反序列化。
WorkflowDesignerView: 负责在UI上呈现工作流。
DesignSurface: 负责从宿主中加载工作流设计器,完成WorkflowDesignerView的构建。
这里我只对这些类功能简单介绍,更详细的资料可以参考MSDN。
首先,我们需要改变VS中设计器加载工作流的一些行为,我们要做的很简单,就是从一个XOML文件读取一个工作流和把一个设计好的工作流导出到一个XOML文件。上面已经说过WorkflowDesignerLoader负责这个工作,当然,忧郁VS设计器具有更复杂的功能,那么我们将对WorkflowDesignerLoader做一些控制,让它只完成我们需要的操作。
我们定义一个WorkflowLoader类,让他继承自WorkflowDesignerLoader类,而在WorkflowDesignerLoader类中,加载工作流的动作是由方PerformLoad完成的,我们可以重写这个方法,加入我们自己的逻辑来完成对工作流的加载,首先我们在WorkflowLoader类中定义一个属性来保存XOML文件路径:
public string Xoml{get;set;}
然后重写方法:
protected override void PerformLoad(IDesignerSerializationManager serializationManager)
{
base.PerformLoad(serializationManager);
IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
reader = new XmlTextReader(this.Xoml);
Activity rootActivity = null;
try
{
WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer();
rootActivity = xomlSerializer.Deserialize(reader) as Activity;
}
finally
{
reader.Close();
}
if (rootActivity != null && designerHost != null)
{
//构造设计器中的活动树
AddObjectGraphToDesignerHost(designerHost, rootActivity);
Type companionType = rootActivity.GetValue(WorkflowMarkupSerializer.XClassProperty) as Type;
if (companionType != null)
{
SetBaseComponentClassName(companionType.FullName);
}
}
designerHost.Activate();
// 如果存在规则定义,从规则定义文件中读取规则信息
string rulesFile = Path.Combine(Path.GetDirectoryName(this.Xoml), Path.GetFileNameWithoutExtension(this.Xoml) + ".rules");
if (File.Exists(rulesFile))
{
StreamReader rulesReader = new StreamReader(rulesFile, Encoding.UTF8);
this.tempRulesStream = new StringBuilder(rulesReader.ReadToEnd());
rulesReader.Close();
}
}
代码中有一个AddObjectGraphToDesignerHost方法用来创建活动树的逻辑结构:
AddObjectGraphToDesignerHost
private void AddObjectGraphToDesignerHost(IDesignerHost designerHost, Activity activity)
{
Guid Definitions_Class = new Guid("3FA84B23-B15B-4161-8EB8-37A54EFEEFC7");
if (designerHost == null)
throw new ArgumentNullException("designerHost");
if (activity == null)
throw new ArgumentNullException("activity");
string rootSiteName = activity.QualifiedName;
if (activity.Parent == null)
{
string fullClassName = activity.UserData[Definitions_Class] as string;
if (fullClassName == null)
fullClassName = activity.GetType().FullName;
rootSiteName = (fullClassName.LastIndexOf('.') != -1) ? fullClassName.Substring(fullClassName.LastIndexOf('.') + 1) : fullClassName;
designerHost.Container.Add(activity, rootSiteName);
}
else
{
designerHost.Container.Add(activity, activity.QualifiedName);
}
if (activity is CompositeActivity)
{
foreach (Activity activity2 in GetNestedActivities(activity as CompositeActivity))
designerHost.Container.Add(activity2, activity2.QualifiedName);
}
}
而上面的方法中用到一个方法递归构造一个活动的子活动:
GetNestedActivities
private Activity[] GetNestedActivities(CompositeActivity compositeActivity)
{
if (compositeActivity == null)
throw new ArgumentNullException("compositeActivity");
IList<Activity> childActivities = null;
ArrayList nestedActivities = new ArrayList();
Queue compositeActivities = new Queue();
compositeActivities.Enqueue(compositeActivity);
while (compositeActivities.Count > 0)
{
CompositeActivity compositeActivity2 = (CompositeActivity)compositeActivities.Dequeue();
childActivities = compositeActivity2.Activities;
foreach (Activity activity in childActivities)
{
nestedActivities.Add(activity);
if (activity is CompositeActivity)
compositeActivities.Enqueue(activity);
}
}
return (Activity[])nestedActivities.ToArray(typeof(Activity));
}
对于工作流的导出我们重写PerformFlush方法实现:
PerformFlush
protected override void PerformFlush(IDesignerSerializationManager manager)
{
IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
Activity rootActivity = host.RootComponent as Activity;
if (host != null && host.RootComponent != null)
{
if (rootActivity != null)
{
XmlTextWriter xmlWriter = new XmlTextWriter(this.Xoml, Encoding.UTF8);
try
{
WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer();
xomlSerializer.Serialize(xmlWriter, rootActivity);
}
finally
{
xmlWriter.Close();
}
}
}
// 保存规则定义文件
if (this.tempRulesStream != null)
{
string rulesFile = Path.Combine(Path.GetDirectoryName(this.Xoml), Path.GetFileNameWithoutExtension(this.Xoml) + ".rules");
using (StreamWriter rulesWriter = new StreamWriter(rulesFile,false, Encoding.UTF8))
{
rulesWriter.Write(this.tempRulesStream.ToString());
}
}
}
PerformFlush方法是protected的,可以通过public的Flush方法公开出去,因此我们还要重写一下Flush方法:
public override void Flush()
{
PerformFlush(null);
}
到次,一个WorkflowDesignerLoader的主要功能就完成了,后面的文章我会继续讲述完成宿主设计器到应用程序的其他工作。