65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows 工作流和 WPF

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2017年6月30日

CPOL

5分钟阅读

viewsIcon

14397

downloadIcon

439

将 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 将抛出 RuntimeBinderExceptionExpandoObjects 上的属性可以通过赋值自动创建,但不能通过访问创建。请记住,在底层,它们是通过 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

图 1:应用程序启动时

图 2:NativeActivity1 运行后应用程序

图 3:NativeActivity2 运行后应用程序。请注意,NativeActivity2 修改了两个标签的内容
© . All rights reserved.