使用 Service Manager 创作工具自定义和创作表单概述

表单是一种使用户能够与数据库对象进行交互的窗口。 用户可以使用表单查看和编辑对象的属性。 每个表单均与特定的类绑定,并且它仅显示目标类的实例的信息。 表单含有字段。 通常,每个字段都绑定到窗体的目标类的特定属性。 例如,事件表单与事件对象绑定。 因此,事件表单显示数据库中关于事件对象的信息。

Service Manager 窗体由 Microsoft .NET Framework 程序集中的 Windows Presentation Foundation (WPF) 表单实现和 Service Manager 管理包中的表单定义组成。 表单定义指定表单所代表的类以及表单的其他属性。

有关窗体的关键概念

自定义表单之前,应先熟悉下列表单概念。

表单的使用

将包含表单定义的管理包导入 Service Manager 时,表单定义将存储在数据库中。 稍后,当用户启动需要显示对象的 Service Manager 控制台任务时,Service Manager 必须找到一个窗体来显示请求的对象。 Service Manager 访问数据库并搜索已为该对象定义的窗体。 如果未为对象定义窗体,Service Manager 将搜索为对象的父对象定义的窗体。 Service Manager 继续搜索整个对象的继承层次结构,直到找到定义的表单。

泛型表单

如果 Service Manager 找不到对象或其任何父对象的任何窗体,Service Manager 会动态生成该对象的默认 泛型窗体 。 普通表单是一种由系统生成的表单,它足以满足简单的表单用途。 普通表单是一种可用于为对象快速而又简便地创建表单(无需任何表单定义)的方法。

默认情况下,泛型窗体在一个简单的布局中显示窗体的所有属性,无法更改。 泛型窗体显示窗体继承层次结构中所有父对象的属性,并且无法更改该行为。 对普通表单的自定义受到限制。 例如,可以指定希望泛型窗体显示的属性;但是,泛型窗体不能用作自定义的基础。 如果稍后为该对象定义自定义窗体,则自定义窗体将覆盖对象的泛型窗体。

有关在普通表单中隐藏属性以及可用于自定义普通表单的其他方式的信息,请参阅博客文章 Overview of the Forms Infrastructure and the Generic Form(表单基础结构和普通表单概述)

窗体中的组合类

有时,你需要表单显示派生自多个类的信息。 若要执行此操作,请创建 “组合类” ,然后将表单上的字段绑定到该组合类。 有关组合类的详细信息,请参阅 对 System Center 通用架构的更改。

窗体的功能方面

表单具有下列功能特性:

  1. 初始化

  2. 大小和位置

  3. 刷新​​

  4. 提交更改

下列部分中将描述这些特性。

初始化

在初始化期间,将分析窗体的可扩展应用程序标记语言(XAML),并实例化和加载窗体上的所有控件。 窗体的 Loaded 事件指示窗体和所有包含元素的加载时间。 数据加载操作都是异步操作。 因此,引发 Loaded 事件后目标实例可能不可用。 为表单设置目标实例后, DataContextChanged 事件必须改用于通知。 PropertyChanged 属性对应的 DataContext 事件可用于代替 DataContextChanged 事件。

我们建议你对控件相关的自定义初始化使用 Loaded 事件,然后对目标实例相关的自定义初始化使用 DataContextChanged 属性中的 PropertyChangedDataContext 事件。

大小和位置

当窗体显示在弹出窗口中时,其初始大小取决于窗体的 Width、HeightMinWidthMinHeight 属性。 如果未为窗体设置这些属性,将根据窗体的内容计算窗体的初始大小。

我们建议你设置这些属性,如下所示:

  • 设置表单的 WidthHeight 属性以明确指定理想大小。 考虑将这些属性设置为 Auto 值。 这将根据内容的大小设置表单的宽度和高度。

  • 设置表单的 MinWidthMinHeight 属性以指定表单可接受的最小窗口。 如果用户将窗口大小调整到一个比指定大小要小的大小,则会出现滚动条以供滚动到隐藏的表单内容。

当窗体托管在 Service Manager 窗体主机内时,将保留上次使用的大小和位置,以便同一运行会话中的同一用户随后显示该窗体。

刷新​​

表单的目标实例会随着对表单执行 Refresh 命令所产生的结果而发生变化。 用于此命令的处理程序会从数据库中获取新数据。 当数据到达时,窗体的 DataContext 属性值设置为新的目标实例,并 引发 DataContextChanged 事件。

若要区分首次加载表单时引发的 DataContextChanged 事件与被引发以用于处理 Refresh 命令的事件,请检查随事件一起传递的事件参数的 OldValue 属性。 如果表单刚刚初始化,则此属性为 Null。

提交更改

Service Manager 中的窗体主机弹出窗口提供用于提交表单中所做的更改和关闭弹出窗口的按钮。

当用户为窗体选择 “应用 ”按钮时,将提交表单的目标实例进行存储。 此操作是同步的;因此,在提交操作完成之前,用户无法编辑表单。 如果在提交表单过程中出错,则会出现一条错误消息。 表单保持打开状态以供做进一步更改。 如果同时还在编辑该表单的另一个实例,则我们建议用户经常应用其更改以避免冲突。

如果用户选择“ 确定 ”按钮,则行为类似于 “应用”,不同之处在于,如果表单提交操作成功,窗体及其主机窗口将关闭。

如果用户选择“ 取消 ”按钮,将显示一个对话框,要求用户确认该操作。 用户可以选择“是并丢失更改,或选择“否并返回到窗体。

表单的一般准则和最佳做法

可以通过添加或修改表单来扩展 Service Manager 的功能。 本部分介绍有关直接使用各种工具和脚本表单定义创建和使用 Service Manager 表单的一些最佳做法建议。

本部分主要面向使用 Windows Presentation Foundation(WPF)和 Microsoft Visual Studio Team System 或 Microsoft Expression Blend 构建自己的自定义表单的合作伙伴和客户。

创作新表单的一般准则如下所示。

  • 使用标准控件。
  • 遵循表单设计的一般准则。
  • 避免代码隐藏。
  • 包括异常处理。
  • 考虑表单自定义和升级。
  • 命名所有可自定义的控件。
  • 将表单绑定到数据源。
  • 使用 Service Manager 表单基础结构验证规则、值转换器和错误模板。
  • 使用表单基础结构命令和事件。

有关这些准则的信息,请参阅下列部分。

使用标准控件

在表单中使用的控件可以是:

  • 标准控件。 这包括诸如组合框和列表框等 .NET 库控件。
  • 自定义控件。 这包括表单作者或第三方创建的其他控件。

提示

尽量使用标准控件并避免创建自定义控件,可提升表单的用户体验方面的一致性。 如果必须创建自定义控件,请通过使用控件模板来定义控件的外观,从而区分视觉外观、行为和逻辑行为。 最好是每个 Windows 主题有一个单独的控件模板。

遵循常规表单设计指南

设计表单时,请使用公共设计准则以确保表单为用户友好型表单并遵循常见的用户交互模式。

有关常规 Windows 设计的详细信息,请参阅 Windows User Experience Interaction Guidelines(Windows 用户体验交互准则)

此外:

  • 在多个选项卡中分开显示信息以使表单更加简单和更易于阅读。 在第一个选项卡上包括最常用的信息,以及后续选项卡上不太重要的信息。
  • 使用布局面板在表单上对控件进行布局。 这可确保窗体在调整大小和本地化时的行为正确。
  • 避免设置单个控件的可视属性,而应改为使用样式。 这样,你就可以通过修改样式来更改一系列表单中所有控件的外观,并跨相关表单提升一致的外观。

避免代码隐藏

代码隐藏 是一个术语,用于描述在对 XAML 页进行标记编译时与采用标记定义的对象联接的代码。 尽量在表单中限制使用代码隐藏。 最好在控件本身中嵌入窗体的代码,因为稍后更改该代码更容易。 请改用 Service Manager 表单基础结构支持的声明性功能来定义表单中的值转换和验证规则。

作为一般准则,应将代码隐藏的使用限制为无法使用 XAML 的声明性功能提供所需功能的情况,以及 WPF 和表单基础结构库中定义的类。 即使是这样,也请考虑将代码隐藏所实现的功能移到帮助程序库,然后从 XAML 引用它。

包括异常处理

确保表单中的代码包含异常处理,以便可以在创作工具的设计阶段和运行时在 Service Manager 控制台中加载窗体。

考虑表单自定义和升级

设计新窗体时,应考虑将来对该窗体进行自定义和升级。 若要确保在保留自定义项的同时自定义和升级窗体,请遵循本节前面提供的准则和提示以及以下准则:

  • 在设计窗体时,请考虑将来的自定义和升级。 表单在将来版本中可能会演变,请务必考虑用户在将自定义项保留为原始窗体的同时如何升级到表单的新版本。 例如,用户已经投入大量资金自定义原始表单后,你可能会提供一个更新的表单。 用户期望在版本升级后其自定义项能得以保留。

  • 为表单上的每个控件提供唯一名称,以使自定义项能够应用于这些控件。 表单自定义项被存储为一组面向特定控件或一组控件的操作。 目标控件按名称引用,因此必须跨窗体版本保留控件名称。 如果控件没有名称,则窗体自定义编辑器会生成一个名称,但生成的名称不会保留在窗体的不同版本中。

  • 确保控件名称在窗体的不同版本中保持不可变。 这将确保上一个版本中给定控件的自定义项可应用于新版表单中的同一控件。

  • 如有可能,应避免在升级表单时将控件移到同一选项卡上的不同位置。 常见的用户自定义是将表单上的控件移到一个不同位置。 如果更改窗体新版本中控件的位置,则新控件位置可能会与用户已重新定位的控件重叠的风险。

  • 如果可能,请在设计对现有窗体的更新时避免在选项卡之间移动控件。 控件按名称以及控件所在的选项卡进行标识。 在新版表单中将控件从一个选项卡移到另一个选项卡会使用户对该控件所做的自定义项失效,因为这些自定义项将无法标识目标控件。

  • 当对窗体的更新包含新控件时,请考虑将新控件添加到新选项卡。这是避免干扰现有选项卡和控件的任何用户自定义的最安全方法。

  • 请注意控件的绑定方式。 只读控件应仅使用单向绑定。

命名所有可自定义控件

确保控件名称描述控件所绑定到的数据或描述控件的作用。

将窗体绑定到数据源

窗体的主要用途是可视化 Service Manager 数据库中的单个对象。 此对象称为 target instance,它始终由表单的 DataContext 属性指定(其继承自 FrameworkElement 类)。

重要

请勿修改窗体的 DataContext 属性。 表单宿主环境使用此属性来标识表单目标实例。

在 Service Manager 数据模型中,目标实例表示为 BindableDataItem 对象。 此类聚合了基本软件开发工具包 (SDK) 对象,并通过将属性名称用作参数的索引器公开其属性。

BindableDataItem 类也实施 ICustomTypeDescriptor,这使得能够使用 BindableDataItem 类作为数据源进行 WPF 绑定。 下列示例介绍了如何将目标实例属性绑定到 Text 控件的 TextBox 属性:


<TextBox Name="textBoxDescription" Text="{Binding Path=Summary}"/>

不需要指定 绑定的源 ,因为目标实例设置为 窗体的 DataContext ,该窗体充当窗体上所有控件的默认

窗体上的控件可以绑定到目标实例以外的数据源,窗体基础结构库包含许多隐式执行绑定的控件。 例如,实例选取器控件绑定到提供实例集合供选择的数据源。 还可以使用 ObjectDataProvider 和 XmlDataProvider 类以声明方式定义其他数据源。

表单基础结构将目标实例视为表单上的唯一读/写数据源。 因此,实施 Submit 命令将仅存储对目标实例所做的更改。 表单的其他数据源将被视为只读。

使用 Service Manager 表单基础结构验证规则、值转换器和错误模板

建议在表单中使用表单基础结构验证规则来指定无效的数据输入。 WPF 绑定基础结构支持验证通过单向或双向绑定来绑定到数据源的控件属性。 绑定对象具有一个可包含任意数量的 ValidationRule 对象的 ValidationRules 集合。 数据从控件被推送到数据源时,将会调用 ValidationRule 对象来验证值。

表单基础结构库包含许多处理最常见情况的验证规则。 表单基础结构充分利用验证规则来确定是否可提交表单内容进行存储。 例如,如果窗体上存在验证错误的控件,则可以禁用窗体的 “提交 ”按钮。

我们建议你使用表单基础结构库随附的自定义错误模板。 如果控件具有验证错误,默认情况下其周围会出现一个红色边框。 WPF 使之能够通过 Validation.ErrorTemplate 属性定义一个自定义错误指示符,该属性可在任意控件上进行设置。 Service Manager 表单基础结构库包含自定义错误模板,该模板显示错误图标而不是 WPF 红色边框。 此外,当鼠标指向错误图标时,会弹出含有错误消息的工具提示。 错误消息应指明控件中的数据未能通过验证的原因。

下例演示如何引用 XAML 中的错误模板:


<TextBox Text="{Binding SomeProperty}"
         scwpf:Validation.ValueRequired="True"
         Validation.ErrorTemplate="{DynamicResource {ComponentResourceKey {x:Type scwpf:Validation}, InvalidDataErrorTemplate}}"/>

如果内置验证规则不提供所需的验证逻辑,建议生成自定义验证规则来表示该逻辑。 这将使得标准和自定义验证逻辑能够在常见的验证处理机制中共存。

如果验证规则机制不适用于特定方案,则应改为处理 FormEvents.PreviewSubmitEvent 并从那里运行验证。

下列代码示例提供了一个可用于运行自定义验证的模式示例:


void MyForm_Loaded(object sender, RoutedEventArgs e)
{
    // hook to handle form events
    this.AddHandler(
        FormEvents.PreviewSubmitEvent,
        new EventHandler<PreviewFormCommandEventArgs>(this.OnPreviewSubmit));
}
private void OnPreviewSubmit(object sender, PreviewFormCommandEventArgs e)
{
    string errorMessage;
    bool result = this.DoVerify(out errorMessage);
    if (!result)
    {
        // cancel Submit operation
        e.Cancel = true;
        // display error message
        MessageBox.Show(errorMessage);
    }
}
internal bool DoVerify(out string errorMessage)
{
    // Do custom verification and return true to indicate that
    // validation check has passed; otherwise return false and
    // populate errorMessage argument
}

使用表单基础结构命令和事件

窗体基础结构公开了许多可在窗体上运行的命令。 这些命令包括:

  • FormsCommand.Submit,该命令可保存表单的目标实例。

  • FormsCommand.SubmitAndClose,该命令可保存表单的目标实例并关闭表单。

  • FormsCommand.Refresh,该命令可重复查询表单的目标实例。

  • FormCommands.Cancel,它放弃所有更改并关闭窗体。

每一个命令均由命令运行前后引发的事件括起来。

命令运行前引发下列事件:

  • FormEvents.PreviewSubmit 命令运行前引发 FormCommand.Submit 事件, FormEvents.Submitted 命令运行后引发 FormCommand.Submit 事件。

  • FormEvents.PreviewRefresh 命令运行前引发 FormCommands.Refresh 事件, FormCommand.Refreshed 命令运行后引发 FormCommand.Submit 命令。

  • FormEvents.PreviewCancel 命令运行前引发 FormCommands.Cancel 事件, FormCommand.Canceled 命令运行后引发 FormCommand.Cancel 事件。

预览事件传递 PreviewFormCommandEventArgs 对象。 此对象包含易变的 Cancel 属性,该属性被设置为 true时将会阻止相应的命令运行。

命令后事件会传递 FormCommandExecutedEventArgs 对象。 此对象包含 Result 属性,该属性指明命令的运行是已成功、已取消还是导致了发生错误。 如果是发生错误,则 FormCommandExecutedEventArgs 对象的 Error 属性会引用异常来提供有关该错误的信息。

可以同时以编程方式和声明方式启用、禁用和运行窗体命令。

若要以编程方式启用表单命令,请建立表单与相关命令之间的 CommandBinding

在下例中,表单与 Refresh 命令之间建立了命令绑定,并为此命令定义了两个处理程序。 第一个处理程序返回 Refresh 命令是否能运行,第二个处理程序实际包含 Refresh 命令的实施:


    public class MyForm : UserControl
    {
        public MyForm()
        {
            // do standard initialization
            // establish CommandBinding for Refresh command
            this.CommandBindings.Add(
                new CommandBinding(FormCommands.Refresh, this.ExecuteRefresh, this.CanExecuteRefresh));
        }
        private void CanExecuteRefresh(
              object sender,
              CanExecuteRoutedEventArgs e)
        {
            // put your logic that determines whether Refresh
// can be executed here
            bool canExecute = true;
            BindableDataItem dataItem = this.DataContext as BindableDataItem;
            if (dataItem)
            {
                canExecute = dataItem["Status"] != "New";
            }
            e.CanExecute = canExecute;
        }
        private void ExecuteRefresh(
            object sender,
            ExecutedRoutedEventArgs e)
        {
            // here is placeholder for the code that has do be
// executed upon running Refresh command
        }
    }

你也可用声明方式定义表单命令的处理程序。 你可通过使用 规则 对象来完成此操作,该对象使用 RoutedCommandTrigger。 下列代码示例显示如何用声明方式定义处理程序:


    <scwpf:BusinessLogic.Rules>
        <scwpf:RuleCollection>
            <scwpf:Rule>
                <scwpf:Rule.Triggers>
                    <scwpf:RoutedCommandTrigger
RoutedCommand="{x:Static scwpf:FormCommands.Refresh}"/>
                </scwpf:Rule.Triggers>
                <scwpf:Rule.Conditions>
                    <scwpf:PropertyMatchCondition
                        Binding="{Binding Status}"
                        Value="New"
                        Operation="NotEquals" />
                </scwpf:Rule.Conditions>
                <!-- Use RuleAction objects to define the logic that executed
                upon running Refresh command; this can be left empty -->
            </scwpf:Rule>
        </scwpf:RuleCollection>
    </scwpf:BusinessLogic.Rules>

后续步骤