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

Rehosting WorkflowDesigner

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2019年5月3日

CPOL

6分钟阅读

viewsIcon

13955

downloadIcon

525

在 WPF 和 Windows Forms 应用程序中重新托管 WorkflowDesigner,并调用工作流

引言

本文介绍如何创建和执行工作流。它使用了 Microsoft 的 System.Activities.Presentation.WorkflowDesigner 类用于工作流的可视化设计,以及 System.Activities.Presentation.WorkflowInvoker 类用于执行工作流。文中提供了一个 WPF 应用程序和一个 WinForm 应用程序。

工作流设计

工作流执行

本文还介绍了如何创建和执行规则。它使用了 Microsoft 的 System.Workflow.Activities.Rules.Design.RuleSetDialog 类用于规则的可视化设计,以及 System.Workflow.Activities.Rules.RuleExecution 类用于执行规则。

规则设计

规则执行

背景

Microsoft 的 重新托管工作流设计器 文章描述了如何使用 WorkflowDesigner 类。本文描述了如何将工具箱、设计器和属性窗格捆绑到一个用户控件中,以及如何在 WPF 和 WinForm 应用程序中使用该用户控件。

Using the Code

此解决方案包含三个项目

  1. WFLibrary:一个包含 WPF 用户控件 WFDesigner 和普通 CLR 对象 Order 的库
  2. WPFApp:一个用于设计和运行工作流,以及设计和运行规则的 WPF 应用程序
  3. WinFormApp:一个用于设计和运行工作流的 WinForm 应用程序

WFLibrary

WFDesigner 用户控件由一个包含三个列的网格组成:工具箱、设计器和属性视图。无法在可视化设计器或 XAML 中添加 WorkflowDesigner,必须在构造函数中通过代码添加。

    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        Dim dm = New DesignerMetadata()
        dm.Register()
        Dim tc = GetToolboxControl()
        Grid.SetColumn(tc, 0)
        grid1.Children.Add(tc)
        RecreateDesigner(GetActivityEmpty)
    End Sub

需要 DesignerMetadata.Register 方法来注册 Microsoft.Activities.Design 的设计器元数据。

ToolboxControl

GetToolboxControl 函数创建 System.Activities.Presentation.Toolbox.ToolboxControl 类的实例。向工具箱添加以下语句:If、ForEachSequenceSwitchWhileAssignInvokeMethodDelayRethrowThrow
工具箱控件添加到网格的第一列:Grid.SetColumn(tc, 0)

从本地资源加载空工作流

该项目包含一个空工作流 ActivityEmpty.xaml,其中只有一个 Sequence 活动。此文件的生成操作为 ResourceGetActivityEmpty 函数读取此文件

    Private Function GetActivityEmpty() As String
        Dim assembly = Me.GetType().Assembly
        Dim manifestResourceStream = assembly.GetManifestResourceStream
                                     (assembly.GetName.Name & ".g.resources")
        Dim resourceReader = New Resources.ResourceReader(manifestResourceStream)
        Dim resourceEnumerator = resourceReader.GetEnumerator
        While resourceEnumerator.MoveNext
            If resourceEnumerator.Key = "activityempty.xaml" Then
                Dim streamReader = New StreamReader(CType(resourceEnumerator.Value, Stream))
                Return streamReader.ReadToEnd
            End If
        End While
    End Function

WorkflowDesigner

RecreateDesigner 方法创建 System.Activities.Presentation.WorkflowDesigner 类的实例

    Private Sub RecreateDesigner(text As String)
        WD = New WorkflowDesigner
        AddHandler WD.ModelChanged, AddressOf OnModelChanged
        WD.Text = text
        If WD.Text > "" Then
            WD.Load()
        End If
        WfDesignerBorder.Child = WD.View
        WfPropertyBorder.Child = WD.PropertyInspectorView
    End Sub

WorkflowDesigner 显示在 Text 属性上的工作流(以 XAML 格式)。它提供两个视图:设计器视图(.View)显示在中间列,属性视图(.PropertyInspectorView)显示在右列。无法更改 Text 属性。因此,必须重新创建 WorkflowDesigner,每次显示另一个工作流。这就是为什么用户控件的 Text 属性会调用 RecreateDesigner 方法的原因。

    Public Property Text As String
        Get
            WD.Flush()
            Return WD.Text
        End Get
        Set(value As String)
            RecreateDesigner(value)
        End Set
    End Property

ModelChanged 事件

用户控件处理 WorkflowDesignerModelChanged 事件,并引发自己的 ModelChanged 事件。因此,客户端应用程序可以得知工作流何时发生更改。

    Private Sub RecreateDesigner(text As String)
        ...
        AddHandler WD.ModelChanged, AddressOf OnModelChanged
        ...
    End Sub

   Private Sub OnModelChanged(sender As Object, e As EventArgs)
        RaiseEvent ModelChanged(Me, e)
    End Sub

验证错误

用户控件公开只读属性 NrValidationErrors。因此,客户端应用程序可以检查工作流是否有效。遗憾的是,此信息是通过反射检索的,因为它在 WorkflowDesigner 中是私有的。这意味着此实现可能无法在新版本的 WorkflowDesigner 中工作。

    Public ReadOnly Property NrValidationErrors As Integer
        Get
            Dim prop = WD.GetType.GetProperty("ValidationService", -1)
            Dim srv = prop.GetValue(WD)
            prop = srv.GetType.GetProperty("ValidationErrors", -1)
            Dim dict = prop.GetValue(srv)
            prop = dict.GetType.GetProperty("Count", -1)
            Return CInt(prop.GetValue(dict))
        End Get
    End Property

WPFApp

此 WPF 应用程序有三个选项卡

  1. 设计:包含一个 WFDesigner 用户控件和一个文本框。

    它提供三个菜单
    • 新建:创建一个新的空工作流
    • 打开:从 .xaml 文件打开工作流
    • 保存:将工作流保存到 .xaml 文件
  2. 运行:包含两个属性网格,左侧显示工作流执行前 Order,右侧显示工作流执行后 Order

    它提供一个菜单
    • 运行:执行第一个选项卡中定义的工作流。
  3. 规则:包含两个属性网格,左侧显示规则执行前 Order,右侧显示规则执行后 Order

    它提供三个菜单
    • 新建:创建一个新的规则定义
    • 打开:从 .rules 文件打开规则定义
    • 运行:执行 .rules 文件中定义的规则

    使用 RuleSetDialog 类定义规则。使用 RuleExecution 类执行规则。

        Private Sub MenuItem_RulesRun_Click(sender As Object, e As RoutedEventArgs)
            OrderOut = OrderIn.Copy
            If File.Exists(TextBoxRulesFilePath.Text) Then
                Dim ruleSet = RulesLoad(TextBoxRulesFilePath.Text)
                Dim validation = New RuleValidation(OrderOut.GetType(), Nothing)
                Dim engine = New RuleExecution(validation, OrderOut)
                ruleSet.Execute(engine)
                PropertyGridRulesOrderOut.SelectedObject = OrderOut
                PropertyGridRulesOrderOut.Update()
                MsgBox("ok")
            Else
                MsgBox("Must specify rules.")
            End If
        End Sub

WinFormApp

此 Windows Forms 应用程序与 WPFApp 类似,只是没有第三个选项卡(规则功能)。WPF 控件不能直接添加到 Windows Form 中。需要 ElementHost 类来连接 Windows Form 和 WPF 用户控件。在窗体的构造函数中,调用 InitWFDesigner 方法。但是,可以像 ModelChanged 事件一样直接使用 WPF 控件的事件。

    Private Sub WFDesigner_ModelChanged(sender As Object, e As System.EventArgs)
        ToolStripMenuItemSave.Enabled = True
        RemoveHandler WFDesigner1.ModelChanged, AddressOf WFDesigner_ModelChanged
    End Sub

    Private Sub InitWFDesigner()
        Dim elemHost = New ElementHost
        elemHost.Dock = DockStyle.Fill
        PanelDesign.Controls.Add(elemHost)
        WFDesigner1 = New WFLibrary.WFDesigner
        AddHandler WFDesigner1.ModelChanged, AddressOf WFDesigner_ModelChanged
        elemHost.Child = WFDesigner1
        MaximizeBox = True
    End Sub

演练

使用 Visual Studio 打开并编译 WorkflowTest.sln

带 WPF 的工作流

  1. 启动项目是 WPFApp。按 F5 运行它。
  2. 通过 File/Open 打开一个工作流,选择 WFLibrary/Activity1.xaml。工作流设计器出现。

  3. 切换到“运行”选项卡。
  4. 通过 File/Run 运行工作流。请注意,状态已更改为 Ready。这是因为表达式为:IF order.GetEUREquivalent() < 10 Then order.Status = StatusEnum.Settled Else order.Status = StatusEnum.Ready

  5. 将左侧 Order 中的金额更改为 9 并通过 File/Run 重新运行工作流。右侧 Order 中的 Status 应更改为 Settled,因为 9 < 10

  6. 切换到“设计”选项卡并修改工作流,例如将条件更改为 order.GetEUREquivalent() < 9
  7. 重新运行工作流,应得到 Status = Ready
  8. 您可以添加其他赋值、if 语句等,并在运行工作流时查看效果。
  9. 您可以通过 File/New 从头开始创建一个新的工作流。

规则

  1. 切换到“规则”选项卡。
  2. 通过 File/Open 打开一组规则,选择 WFLibrary/Rules1.rules。规则集设计器打开。

  3. 单击取消
  4. 通过 File/Run 运行规则集。请注意,Status 已更改为 Ready。这是因为条件是:IF order.GetEUREquivalent() < 10Then 操作是:order.Status = StatusEnum.SettledElse 操作是:order.Status = StatusEnum.Ready

  5. 将左侧 Order 中的金额更改为 9 并通过 File/Run 重新运行规则。右侧 Order 中的 Status 应更改为 Settled,因为 9 < 10

  6. 再次通过 File/Open 打开规则集并修改规则,例如,将条件更改为 order.GetEUREquivalent() < 9
  7. 重新运行规则,应得到 Status = Ready
  8. 您可以添加其他规则,并在运行规则集时查看效果。
  9. 您可以通过 File/New 从头创建一个新的规则集。

带 WinForm 的工作流

您可以启动 WinFormApp,并遵循与 WPFApp 相同的步骤。WinFormApp 仅包含工作流功能,不包含规则功能。

关注点

  • WorkflowDesigner 与工具箱和属性窗格捆绑到 WFLibrary.WFDesigner 中的 WPF 用户控件
  • 使用 WPFApp.MainWindow.MenuItem_Run_Click()WinFormApp.Form1.ToolStripMenuItemRun_Click() 中的 WorkflowInvoker 执行工作流
  • WFLibrary.WFDesigner.GetActivityEmpty() 中读取本地资源文件
  • WPFApp.MainWindow.MenuItem_RulesOpen_Click() 中使用 RuleSetDialog 创建和显示规则集
  • WPFApp.MainWindow.MenuItem_RulesRun_Click() 中使用 RuleExecution 执行规则集
  • WinFormApp.Form1.InitWFDesigner 中使用 ElementHost 在 WinForm 应用程序中托管 WPF 控件
  • WinFormApp.Form1.WFDesigner_ModelChanged() 中消耗 WPF 控件的事件
  • WPFApp.MainWindow.xaml 中使用来自 Extended.Wpf.Toolkit 3.5.0 Nuget 包WPF PropertyGrid。不像 WinForms 那样没有原生的 WPF 属性网格。

历史

  1. WorkflowDesigner 捆绑在 WFLibrary.WFDesigner 中,并在 WPF 应用程序和 WinFormApplication 中使用 WFDesigner。使用 WorkflowInvoker 执行工作流。使用 RuleSetDialog 编辑规则集,并使用 RuleExecution 执行规则集。
© . All rights reserved.