基于模型的开发和原型制作示例(第 2 部分)





5.00/5 (1投票)
自定义生成的代码以支持电话号码格式
介绍
在我上一篇文章中,我展示了如何创建一个简单的联系人管理应用程序。联系人有电话号码,但没有特别格式化,因此它们只是简单的文本框。为了展示模型驱动开发的一个小效果,第一个版本需要增强以支持电话号码作为属性类型。
背景
设计或需求上的改变通常会导致一个可能难以很好地捕捉到的影响。一个简单的例子是将一个更通用的类型改为一个更有意义的自定义业务类型。需求可能是,所有电话号码都必须为国际应用程序用户进行格式化。这意味着对于一个德国号码,格式可能是 0 049 (0)xx?? xxx xxx xxx??。在编写了一个包含多个电话号码出现的大型应用程序后,可能不知道需要进行多少更改。所有表单都必须修改,所有报告也会受到影响。验证规则可能会插入,并且可选地支持激活或停用格式化电话号码的设置,或者固定国际格式与数据依赖格式的切换。
可以编写一个类来返回当前使用的格式化规则和类型。然后将其用于所有控件设置代码。但仍然需要将其应用于表单、网格、查找下拉字段等中所有电话号码字段的出现之处。
准备好改为建模
在第一个版本中,电话号码被添加为一种类型。这是关于如何对电话号码字段进行建模的初步草案。今天,决定将类型命名为:PhoneNumber,但是 UML 模型现在已经冻结了类型。例如,当不同的团队声明建模规则和编写 XSLT 模板时,可能会出现这种情况。您有 phonenumber 和 PhoneNumber。在这种情况下需要进行映射,并且这是可能的。
为了准备使用模型,请获取生成的代码,选择一个出现的地方并进行所需的更改,然后编译代码以确保更改正确且符合预期。
为了实现运行时可配置的格式化,至少在表单打开时,我选择将自定义代码放置在 InitializeComponent() 之后。
对于一个简单的固定表单,文本框的设置如下:// Phonenumber format this.txtNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS); this.txtNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
基于 LayoutControl 的表单的文本框获得以下自定义代码(一些小的差异稍后将修复)
// Phonenumber format
this.textNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.textNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
以下示例将展示如何在网格视图中格式化列
// Phonenumber format
this.repositoryItemTextEditNumber.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEditNumber.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEditNumber.Mask.UseMaskAsDisplayFormat = true;
这里用于提供格式化的新类成为设置格式化文本的中心点。该类在技术上是手动编写的代码,但在每次转换时都会生成。该类不会在本文章中开发,但已包含在下载中。
增强类型映射
从扩展开始,我发现从 UML 类型到数据库表示和原型设计器内部模型类型的映射是起点。电话号码以文本格式存储在数据库中,使用 bpchar 列(原型设计器支持的数据库列类型)。基于原型设计器的内部类型是我们定义为 PhoneNumber 的类型。此类型在 formularfoelds 数据结构(或表)中用于定义 UI 中的自定义类型。因此,如果您在 UML 模型中使用 phonenumber,则需要进行映射。文件 XMISysImport.CreateFormularDefinition.Sqlite.xsl 中的类型映射负责原型设计器中的内部类型表示。更改 xsl:variable MappedDataType,使其看起来像以下内容:
<!-- Mapped to internal type representation. This is not a backend type. -->
<xsl:variable name="MappedDataType">
<xsl:choose>
<xsl:when test="$DataType='float'">Float</xsl:when>
<xsl:when test="$DataType='string'">String</xsl:when>
<xsl:when test="$DataType='phonenumber'">PhoneNumber</xsl:when>
<xsl:when test="$DataType='bigstring'">String</xsl:when>
<xsl:when test="$DataType='date'">String</xsl:when>
<xsl:when test="$DataType='int'">Integer</xsl:when>
<xsl:when test="$DataType='bool'">Bit</xsl:when>
<xsl:otherwise>Undefined</xsl:otherwise>
</xsl:choose>
</xsl:variable>
然后,数据库的类型也必须进行映射。以下文件包含 Sqlite 数据库的映射(XMISysImport.FillSchemaAssociations.Sqlite.xsl):
<xsl:variable name="dbtype">
<xsl:choose>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='bool'">BIT</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='date'">DATETIME</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='int'">int4</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='string'">bpchar</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='phonenumber'">bpchar</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='bigstring'">TEXT</xsl:when>
<xsl:when test="//UML:DataType[@xmi.id=$dbtyperef]/@name='float'">FLOAT</xsl:when>
</xsl:choose>
</xsl:variable>
数据库中的映射是 bpchar,一个更通用的类型。如果您使用一个全新的(尚未支持的)类型,可能需要更改原型设计器代码。
如果您至少对 Sqlite 数据库进行了这些更改,您将能够将 Telephone 类中电话号码的 UML 模型属性更改为 phonenumber 类型。
当您重新导入模型(不要忘记覆盖数据库)时,您将能够打开表单并仍然看到一个简单的文本框。这是因为后端类型表示为 bpchar,因此原型设计器知道如何显示该字段。表单字段是后端类型驱动的,而不是模型类型驱动的。
如果您随后开始生成代码,您会在生成的代码中发现错误。新的内部类型 PhoneNumber 尚不受支持,这导致代码不完整。有些部分是在不询问类型的情况下生成的,因此总是生成。从某种角度来看这很好,但是当您希望定制所有出现的地方时,这很糟糕,因为您不能轻松地找到基于(例如)的代码片段,
'<xsl:when test="@dbtype='Bit'">'。
在这里我为我的懒惰付出了代价 " src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" />
通过示例增强受影响的模板
这里的示例并未显示所有更改。要获得一个完全可行的更改,下载中包含了所有更改,但应用程序支持的报告除外。所需的更改尚未完成,可能会留给您尝试——请务必备份 :-)
在这里我将修改简单的 CRUD 表单视图模板(SimpleCRUDFormView.cs.xsl)。计划是使用特定类型(PhoneNumber)属性的代码来扩展构造函数。为此,请找到构造函数并在调用 InitializeComponent() 之后插入以下代码:
public TelephoneSimpleCRUDFormView()
{
InitializeComponent();
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
</xsl:when>
<xsl:when test="//lbDMF/columntypes/columntype[@name=$FieldName][@tablename=$TableName][@specialcolumn='1']">
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="@dbtype='PhoneNumber'">
// Phonenumber format
this.txtNumber.Properties.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.txtNumber.Properties.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
其效果如下:如果您将属性建模为电话号码,则将对控件进行额外设置。但还有另一个需求不能忘记。设计器生成的代码还必须在每个 xsl:choose 片段中知道新类型,因为这并非总是通过通用 xsl:choose 片段完成。
标签必须存在(SimpleCRUDFormView.Designer.cs.xsl):
<xsl:when test="@dbtype='Bit'">
System.Windows.Forms.Label lbl<xsl:value-of select="@name"/>;
</xsl:when>
<xsl:when test="@dbtype='PhoneNumber'">
System.Windows.Forms.Label lbl<xsl:value-of select="@name"/>;
</xsl:when>
文本字段也必须存在(SimpleCRUDFormView.Designer.cs.xsl)
<xsl:when test="@dbtype='Bit'">
private DevExpress.XtraEditors.TextEdit txt<xsl:value-of select="@name"/>;
</xsl:when>
<xsl:when test="@dbtype='PhoneNumber'">
private DevExpress.XtraEditors.TextEdit txt<xsl:value-of select="@name"/>;
</xsl:when>
由于文本字段和标签的设置不是按类型在 xsl:choose 片段中编码的,因此没有操作要求。如果控件类型发生变化,则可能会如此。
遍历所有其他模板,并在需要时进行更改以使电话号码支持完整。您可以搜索 <xsl:when test="@dbtype='Bit'">。
要为网格视图启用类似的格式化,您可能需要做更多工作。显示电话号码的每个列都必须获得一个自定义编辑器,因为默认编辑器不知道格式化,或者至少我没有找到修改现有编辑器的方法。
这是负责设置格式化规则的代码
grid<xsl:value-of select="$FormularName"/>View.MouseDown += grid_MouseDown;
<!-- Custom formatted fields -->
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
</xsl:when>
<xsl:when test="//lbDMF/columntypes/columntype[@name=$FieldName][@tablename=$TableName][@specialcolumn='1']">
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="@dbtype='PhoneNumber'">
// Phonenumber format
this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>.Mask.UseMaskAsDisplayFormat = true;
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- Custom formatted detail fields -->
<xsl:for-each select="//lbDMF/formularactions/action[@formularid=$FormularID]">
<xsl:variable name="actionid" select="@actionid"/>
<xsl:variable name="event" select="@event"/>
<xsl:for-each select="//lbDMF/actionsteps/action[@actionid=$actionid]">
<xsl:variable name="tempDetailFormularName" select="@what"/>
<xsl:variable name="DetailFormularName">
<xsl:call-template name="SubstringReplace">
<xsl:with-param name="stringIn">
<xsl:call-template name="SubstringReplace">
<xsl:with-param name="stringIn">
<xsl:call-template name="SubstringReplace">
<xsl:with-param name="stringIn">
<xsl:value-of select="$tempDetailFormularName"/>
</xsl:with-param>
<xsl:with-param name="substringIn" select="'-'"/>
<xsl:with-param name="substringOut" select="''"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="substringIn" select="'>'"/>
<xsl:with-param name="substringOut" select="''"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="substringIn" select="' '"/>
<xsl:with-param name="substringOut" select="''"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="@steptyp='4'">
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="@dbtype='PhoneNumber'">
// Phonenumber format
this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.EditMask = MaskedTextProvider.GetPhoneNumberFormat(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.MaskType = MaskedTextProvider.GetPhoneNumberMaskType(MaskedTextProvider.STR_EnUS);
this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>.Mask.UseMaskAsDisplayFormat = true;
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
此模板的设计器代码需要进行几处更改。首先是实例的创建
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
this.repositotyItemLookupEdit<xsl:value-of select="@name"/> = new DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit();
this.gridColumn<xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
</xsl:when>
<xsl:otherwise>
this.gridColumn<xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
<xsl:choose>
<xsl:when test="@dbtype='PhoneNumber'">
// Phonenumber format
this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/> = new DevExpress.XtraEditors.Repository.RepositoryItemTextEdit();;
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
然后,详细网格列设置片段得到增强
this.grid<xsl:value-of select="$DetailFormularName"/>View = new DevExpress.XtraGrid.Views.Grid.GridView();
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
</xsl:when>
<xsl:otherwise>
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/> = new DevExpress.XtraGrid.Columns.GridColumn();
<xsl:choose>
<xsl:when test="@dbtype='PhoneNumber'">
// Phonenumber format
this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/> = new DevExpress.XtraEditors.Repository.RepositoryItemTextEdit();;
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
然后,通用网格列可选地获得一个自定义 ColumnEdit 实例:
//
// gridColumn<xsl:value-of select="$FieldName"/>
//
this.gridColumn<xsl:value-of select="$FieldName"/>.Caption = "<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.Name = "gridColumn<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.FieldName = "_<xsl:value-of select="$FieldName"/>";
this.gridColumn<xsl:value-of select="$FieldName"/>.Visible = true;
this.gridColumn<xsl:value-of select="$FieldName"/>.VisibleIndex = <xsl:value-of select="position()-1"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
this.gridColumn<xsl:value-of select="$FieldName"/>.ColumnEdit = this.repositoryItemTextEdit<xsl:value-of select="$FieldName"/>;
</xsl:if>
在这种情况下,我使用了简单的 xsl:if 语句,这也是可行的。然后,详细列可选地获得一个编辑器
//
// gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>
//
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Caption = "<xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Name = "gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.FieldName = "_<xsl:value-of select="$FieldName"/>";
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.Visible = true;
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.VisibleIndex = <xsl:value-of select="position()-1"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
this.gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>.ColumnEdit = this.repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>;
</xsl:if>
最后,需要设置自定义编辑器的变量(详细列,然后是主列):
<xsl:when test="@steptyp='4'">
private DevExpress.XtraGrid.Views.Grid.GridView grid<xsl:value-of select="$DetailFormularName"/>View;
<xsl:variable name="DetailFormularID" select="//lbDMF/formulare/formular[@name=$DetailFormularName][@applicationid=$ApplicationID]/@ID"/>
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$DetailFormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
</xsl:when>
<xsl:otherwise>
private DevExpress.XtraGrid.Columns.GridColumn gridColumnDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="$FieldName"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
private DevExpress.XtraEditors.Repository.RepositoryItemTextEdit repositoryItemTextEditDetail<xsl:value-of select="$DetailFormularName"/><xsl:value-of select="@name"/>;
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
以及主列
<xsl:for-each select="//lbDMF/formularfields/formular[@formularid=$FormularID]">
<xsl:variable name="FieldName" select="@name"/>
<xsl:variable name="TableName" select="@tablename"/>
<xsl:choose>
<xsl:when test="@isfk='1'">
private DevExpress.XtraGrid.Columns.GridColumn gridColumn<xsl:value-of select="$FieldName"/>;
private DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit repositotyItemLookupEdit<xsl:value-of select="$FieldName"/>;
private System.Windows.Forms.BindingSource <xsl:value-of select="$FieldName"/>BindingSource;
</xsl:when>
<xsl:otherwise>
private DevExpress.XtraGrid.Columns.GridColumn gridColumn<xsl:value-of select="$FieldName"/>;
<xsl:if test="@dbtype='PhoneNumber'">
// Phonenumber format
private DevExpress.XtraEditors.Repository.RepositoryItemTextEdit repositoryItemTextEdit<xsl:value-of select="$FieldName"/>;
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
}
}
</xsl:template>
</xsl:stylesheet>
使用代码
包含所有所需更改的代码已作为 zip 文件附加。您需要将 zip 文件的内容复制到 lbDMF 所在的 C:\ 文件夹中,以覆盖我更改的所有文件。
兴趣点
本文展示了如何增强模型驱动的开发方法。与手动完成的工作量相比,添加新的业务类型需要更多的努力。对于一个单独的发生是如此,但是当您到达手动更改需要超过一天的时间时,模型驱动的方法就开始回本了。(实际上,我只用了一天时间就完成了这些更改,包括这篇文章!)
下一步可能是验证和基于自定义数据的格式化内容,如上所述。这值得另一篇文章。
文章中另一个有趣的观点是选择另一个框架和完全不同的模板来生成代码。在这里,我希望在讨论中得到一些反馈,关于我应该选择哪个框架最好。我可能会选择 ASP.NET(通用 - 非 DevExpress)、CakePHP 或 wxWidgets。请评论哪个是最佳选择。
历史
初始发布。