Windows 工作流和 WPF





5.00/5 (7投票s)
将 WPF 与 Windows 工作流 (WF) 连接。
引言
Windows Workflow (WF) 是 .NET 中的一个框架,它允许开发一个领域特定语言 (DSL),其中用户定义的活动充当动词,框架提供调用它们的逻辑,包括控制流和决策逻辑。一个描述不足的问题是如何将工作流与 WPF (Windows Presentation Framework) 应用程序连接起来,特别是在一个采用 MVVM (Model, View, View Model) 分区的应用程序中。在本文中,我将演示一种简化的方法来托管工作流并将其连接到 MVVM 分区应用程序的上层。
背景
MVVM
这里我们关注的 MVVM 的核心特性是分层和数据绑定。我们希望 GUI 元素通过声明式数据绑定来呈现工作流的状态,尽量少或不使用代码命令,以促进关注点分离的分层。
WF 数据
WF 允许数据通过 Dictionary
对象在 WF 框架内外传递。各个活动类可以通过 WF 命令访问 Dictionary
的元素。但是,Dictionary
的元素不适合 WPF 数据绑定。过去有一些文章演示了将 WF 数据上下文连接到 WPF DataContext
的机制(Methods of WPF - WF Data Exchange, Direct WPF - WF Data Binding),还有一篇使用 WorkflowApplication
的 Extensions 属性将 Model 注入到工作流中(How to use Workflow from WPF MVVM)。这允许访问 Model 中的属性,并触发 View Model 可以订阅的事件。然而,自 .NET 4.0 及更高版本引入了 Dynamic 功能以来,有了一种更简单的机制。
ExpandoObject
.NET 4.0 中引入的 ExpandoObject
是一个 dynamic
类,允许在运行时添加属性,这些属性通过 INotifyPropertyChanged
接口自动可用于 WPF 数据绑定。这使其成为工作流和 WPF 视图之间的理想连接。即使属性尚未声明,WPF 元素也可以在 XAML 中在编译时绑定到 ExpandoObject
的属性(假设 WPF DataContext
已绑定到 ExpandoObject
)。稍后,当该属性在运行时在 ExpandoObject
中实例化时,WPF 绑定会立即生效。
例如
using System.Dynamic;
...
dynamic Properties = new ExpandoObject();
// more statements
Properties.demo = "this property is added at runtime";
"Properties
" 对象现在包含一个运行时定义的字符串属性 "demo
",它会自动实现更改通知。如果 demo 属性通过 XAML 中的数据绑定绑定到例如 Label
元素的 Content
属性,当赋值语句执行时,Label
的内容将立即设置为“this property is added at runtime”。即使在 XAML 编译时不存在名为 "demo
" 的属性,也会发生这种情况。在底层,ExpandoObject
是一个 Dictionary
,可以存储各种类型,包括委托。这使得可以访问 ExpandoObject
的 WF 活动触发事件,创建、设置和获取绑定到 GUI 的属性,而无需依赖 GUI。
将 WF 与 ExpandoObject 连接
WF 的 WorkflowApplication
有一个 Extensions
属性,您可以使用它将对象注入到 WF 框架中,并使其对活动可用。这与向工作流传递参数的机制不同。在这里,ExpandoObject
的 Properties 在启动工作流之前被注入到 WorkflowApplication
的 Extensions 属性中。
Activity activity = new ActivityLibrary1.Activity1();
WorkflowApplication workflow = new WorkflowApplication(activity);
// Inject the ExpandoObject into the workflow via the Extensions property.
// This will make it available inside the workflow activities.
workflow.Extensions.Add(Properties);
workflow.Run();
在 Activity
内部,Extensions
属性中的对象根据其类型从活动的上下文参数中检索。请注意,本地引用对象是 "dynamic
",但搜索的类型是 "ExpandoObject
"。在此代码中,该活动是 NativeActivity
,但相同的技术也适用于 CodeActivity
。
protected override void Execute(NativeActivityContext context)
{
dynamic extensionObj = context.GetExtension<ExpandoObject>();
// rest of code
通过访问 ExpandoObject
,可以创建和设置属性
extensionObj.label1text = "This is label 1 text";
此外,还可以调用 delegate
。由于消费的 View Model 可能尚未创建此类 delegate
,因此有责任进行测试。在此示例中,delegate
是一个带有 string
参数的 Action
委托。与所有 ExpandoObject
成员一样,模板是 <string, object>
。我们必须将 ExpandoObject
强制转换为 IDictionary
类型,以测试它是否具有用于 delegate
的键(AddList
)。如果我们直接测试 AddList
成员,ExpandoObject
将抛出 RuntimeBinderException
。ExpandoObjects
上的属性可以通过赋值自动创建,但不能通过访问创建。请记住,在底层,它们是通过 Dictionary<string, object>
实现的。ExpandoObjects
有点棘手!
// Test for existence of AddList delegate before invoking it
if (((IDictionary<string, object>)extensionObj).ContainsKey("AddList"))
{
extensionObj.AddList("this is a list item1");
}
如果我们想更谨慎,可以像这样测试 delegate
是否不为 null
,然后再调用它:
// Test for existence of AddList delegate before invoking it
if (((IDictionary<string, object>)extensionObj).ContainsKey("AddList"))
{
extensionObj.AddList?.Invoke("this is a list item1");
}
在我看来,这是一种更简单的机制,可以将 WF 活动与 MVVM WPF 应用程序中的 View 和 View Model 层连接起来。
示例应用程序
示例应用程序演示了这些概念。工作流被创建为一个库 (DLL),不依赖于应用程序的任何 GUI 方面。可绑定的元素是两个标签和一个列表框。一个 Action 委托会弹出一个 MessageBox
对话框,另一个会将它的 string
参数添加到绑定到列表框的 ObservableCollection
中。Grid 的 DataContext 在 MainWindow
构造函数中设置为 ExpandoObject
"Properties"。
Action
委托必须使用 Dispatcher
在 UI 线程上调用,因为调用它们的活动在 Workflow 线程上运行。
NativeActivity1
休眠 2 秒以模拟工作。
该应用程序编译并运行后,会显示一个带有“Start Workflow”按钮和列表框的窗口。有两个标签不立即可见,因为在程序启动时它们没有内容。点击按钮后,点击处理程序会调用 ViewModel
方法,该方法创建工作流,注入 ExpandoObject
,并启动工作流。在这个微小示例中,按钮点击由点击处理程序处理,而不是由 Command
处理。ViewModel
不依赖于 View
。
两个工作流活动填充标签并将 string
添加到列表框中。标签绑定到尚未创建的 Properties ExpandoObject
的属性;在创建属性后,它们的标题立即显示出来。ObservableCollection
"list" 被赋值给 Properties.items
属性,该属性被绑定到列表框作为其 ItemSource
。