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

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 通用架构的更改

窗体的功能方面

表单具有下列功能特性:

  • 大小和位置

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

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

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

    大小和位置

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

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

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

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

    当窗体托管在 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 ,该窗体上所有控件的默认 Source

    窗体上的控件可以绑定到目标实例以外的数据源,表单基础结构库包含许多隐式执行绑定的控件。 例如,实例选取器控件绑定到提供实例集合供选择的数据源。 还可以使用 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>
  • 使用 Service Manager 创作工具自定义和创作表单
  • 自定义创作工具示例方案的默认事件表单
  •