经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
一个可以让你有更多时间摸鱼的WPF控件(一)
来源:cnblogs  作者:趋时软件  时间:2024/3/29 10:40:27  对本文有异议

前言

我们平时在开发软件的过程中,有这样一类比较常见的功能,它没什么技术含量,开发起来也没有什么成就感,但是你又不得不花大量的时间来处理它,它就是对数据的增删改查。当我们每增加一个需求就需要对应若干个页面来处理数据的添加、修改、删除、查询,每个页面因为数据字段的差异需要单独处理布局及排列,在处理数据录入或修改的过程中还需要校验数据的准确性。那么有没有什么方法可以简化这个过程,提升我们的工作效率?今天这篇文章我们来讨论这个问题。

一、务分析

    我们分析一下传统的开发流程,看看有哪些地方可以优化。

1.1 添加数据

    数据录入的时候我们需要先确定要录入哪些数据,每条数据都是什么类型,然后在新增数据界面上设计布局,确认参数的排列方式,还需要做必要的数据校验工作,比如判断输入是否为空,判断电子邮箱格式是否正确,判断电话号码格式是否正确等,有时还需要在输入前提示一些必要的信息,比如告知用户正确的输入格式,限定输入的内容必须为某些字符等。

    优化方案:

        1)使用反射读取实体类属性,根据实体类的属性类型生成不同的数据录入控件。

        2)实体类实现IDataErrorInfo接口,实现数据校验功能。

1.2 修改数据

    基本与添加数据一致。

1.3 删除数据

    选中数据后执行删除操作,基本无优化空间。

1.4 查询数据

    查询数据一般使用DataGrid或ListView作为数据列表使用,DataGrid对很多表格功能做了封装,它可以对每行的数据进行编辑,可以自动生成列,如果不是特别复杂的需求使用DataGrid的自动列,确实可以节省很多工作,只要简单的绑定一下就可以使用这些功能。但是真实的业务场景需求千变万化,我们来看看会碰到哪些问题。

      1) 设置自动列的时候,DataGrid列显示的是属性名,而属性名往往都是英文的,中文环境中基本都是使用中文列名。

      2) 设置自动列的时候无法对数据进行格式化操作,无法使用转换器。

      3) 设置自动列时无法对列的顺序做自定义排列。

     4) 设置自动列时无法控制自定义列的排列,比如在第一列设置一个CheckBox复选框,在列尾设置编辑、删除按钮等。

      5)设置自动列时无法单独指定某列的宽度。

      6)设置自动列时无法单独隐藏某些列。

    虽然以上问题也有解决方案,但是实现起来略显繁琐。

    优化方案:

        1)为实体类开发一个特性类(ColumnAttribute),添加列名、排序、宽度、是否可见、转换器、格式化字符串等属性。

         2)添加一个ListView控件附加属性,读取以上特性,在ListView控件附加属性上实现以上功能。

二、例代码

    通过对业务需求的分析,我们总结出了几点优化方案,下面展示根据优化方案开发出来的Form控件,该控件可以大大节省开发期间枯燥的重复工作,提升工作效率。

 

View

  1. <Window
  2. x:Class="QuShi.Controls.Samples.Views.EntityEditorView1"
  3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5. xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
  6. xmlns:prism="http://prismlibrary.com/"
  7. Width="450"
  8. prism:ViewModelLocator.AutoWireViewModel="True"
  9. SizeToContent="Height"
  10. WindowStartupLocation="CenterScreen">
  11.  
  12. <StackPanel Margin="20" Orientation="Vertical">
  13. <Form Source="{Binding Person}" />
  14. <Button Margin="10" Padding="20,5" HorizontalAlignment="Center" Command="{Binding ConfirmCommand}" Content="提交" />
  15. </StackPanel>
  16. </Window>

ViewModel

  1. public class EntityEditorView1ViewModel : BindableBase
  2. {
  3. public event Action<Person> Completed;
  4. private Person _person;
  5. public Person Person
  6. {
  7. get => _person;
  8. set => this.SetProperty(ref _person, value);
  9. }
  10. public DelegateCommand<object> ConfirmCommand
  11. {
  12. get
  13. {
  14. return new DelegateCommand<object>(parameter =>
  15. {
  16. if (!string.IsNullOrEmpty(Person.Error))
  17. {
  18. MessageBox.Show(Person.Error);
  19. }
  20. else
  21. {
  22. Completed?.Invoke(Person);
  23. }
  24. });
  25. }
  26. }
  27. }

 

Model

  1. public class Person : ValidationObject
  2. {
  3. private string _name;
  4. private DateTime _dateOfBirth = DateTime.Now;
  5. private int _age;
  6. private int _gender = 1;
  7. private string _phoneNumber;
  8. private string _email;
  9. private string _address;
  10. private string _idCardNumber;
  11. private EducationInfo _education;
  12. private MaritalStatus _maritalStatus;
  13. /// <summary>
  14. /// 姓名
  15. /// </summary>
  16. [DisplayOrder(1)]
  17. [DisplayHint("请填写姓名")]
  18. [DisplayName("姓名")]
  19. [StringLength(15, MinimumLength = 2, ErrorMessage = "姓名的有效长度为2-15个字符.")]
  20. [Required(ErrorMessage = "姓名为必填项.")]
  21. [Column(Name = "姓名", Order = 1)]
  22. public string Name
  23. {
  24. get => _name;
  25. set => this.SetProperty(ref _name, value);
  26. }
  27. /// <summary>
  28. /// 出生日期
  29. /// </summary>
  30. [DisplayOrder(2)]
  31. [DisplayHint("请选择出生日期")]
  32. [DisplayName("出生日期")]
  33. [Required(ErrorMessage = "出生日期为必填项.")]
  34. [Column(Name = "出生日期", StringFormat = "{0:yyyy年MM月dd日}", Order = 2)]
  35. public DateTime DateOfBirth
  36. {
  37. get => _dateOfBirth;
  38. set => this.SetProperty(ref _dateOfBirth, value);
  39. }
  40. /// <summary>
  41. /// 年龄
  42. /// </summary>
  43. [DisplayOrder(3)]
  44. [DisplayHint("请填写年龄")]
  45. [DisplayName("年龄")]
  46. [Range(1, 120, ErrorMessage = "年龄的有效值为1-120.")]
  47. [Required(ErrorMessage = "年龄为必填项.")]
  48. [Column(Name = "年龄", Order = 3)]
  49. public int Age
  50. {
  51. get => _age;
  52. set => this.SetProperty(ref _age, value);
  53. }
  54. /// <summary>
  55. /// // 性别
  56. /// </summary>
  57. [DisplayOrder(4)]
  58. [DisplayHint("请选择性别")]
  59. [DisplayName("性别")]
  60. [OverwriteType(HandleMethod.RadioButton, "1=男", "2=女", "3=其他")]
  61. [Column(Name = "性别", Order = 4)]
  62. public int Gender
  63. {
  64. get => _gender;
  65. set => this.SetProperty(ref _gender, value);
  66. }
  67. /// <summary>
  68. /// 手机号码
  69. /// </summary>
  70. [DisplayOrder(5)]
  71. [DisplayHint("请填写电话号码")]
  72. [DisplayName("电话号码")]
  73. [Phone(ErrorMessage = "电话号码格式不正确.")]
  74. [Column(Name = "电话号码", Order = 5)]
  75. public string PhoneNumber
  76. {
  77. get => _phoneNumber;
  78. set => this.SetProperty(ref _phoneNumber, value);
  79. }
  80. /// <summary>
  81. /// 电子邮箱
  82. /// </summary>
  83. [DisplayOrder(6)]
  84. [DisplayHint("请填写电子邮箱")]
  85. [DisplayName("电子邮箱")]
  86. [EmailAddress(ErrorMessage = "电子邮箱格式不正确.")]
  87. [Column(Name = "电子邮箱", Order = 6)]
  88. public string Email
  89. {
  90. get => _email;
  91. set => this.SetProperty(ref _email, value);
  92. }
  93. /// <summary>
  94. /// 地址信息
  95. /// </summary>
  96. [DisplayOrder(7)]
  97. [DisplayHint("请填写地址")]
  98. [DisplayName("地址")]
  99. [StringLength(50, ErrorMessage = "地址的最大长度为50个字符.")]
  100. [Column(Name = "地址", Order = 7)]
  101. public string Address
  102. {
  103. get => _address;
  104. set => this.SetProperty(ref _address, value);
  105. }
  106. /// <summary>
  107. /// 身份证号码
  108. /// </summary>
  109. [DisplayOrder(8)]
  110. [DisplayName("身份证号码")]
  111. [DisplayHint("请填写身份证号码")]
  112. [RegularExpression(RegexHelper.IdCardNumber, ErrorMessage = "身份证号码格式不正确.")]
  113. [Column(Name = "身份证号码", Order = 8)]
  114. public string IdCardNumber
  115. {
  116. get => _idCardNumber;
  117. set => this.SetProperty(ref _idCardNumber, value);
  118. }
  119. /// <summary>
  120. /// 教育信息
  121. /// </summary>
  122. [DisplayOrder(9)]
  123. [DisplayHint("请填写教育信息")]
  124. [DisplayName("教育信息")]
  125. [Browsable(false)]
  126. public EducationInfo Education
  127. {
  128. get => _education;
  129. set => this.SetProperty(ref _education, value);
  130. }
  131. /// <summary>
  132. /// 婚姻状况
  133. /// </summary>
  134. [DisplayOrder(10)]
  135. [DisplayHint("请选择婚姻状况")]
  136. [DisplayName("婚姻状况")]
  137. [Column(Name = "婚姻状况", Order = 10)]
  138. public MaritalStatus MaritalStatus
  139. {
  140. get => _maritalStatus;
  141. set => this.SetProperty(ref _maritalStatus, value);
  142. }
  143. }
  144. public class EducationInfo
  145. {
  146. public string Degree { get; set; } // 学位
  147. public string Major { get; set; } // 专业
  148. public string School { get; set; } // 学校
  149. public DateTime GraduationDate { get; set; } // 毕业日期
  150. }
  151. public enum MaritalStatus
  152. {
  153. /// <summary>
  154. /// 单身
  155. /// </summary>
  156. [Description("单身")]
  157. Single,
  158. /// <summary>
  159. /// 已婚
  160. /// </summary>
  161. [Description("已婚")]
  162. Married,
  163. /// <summary>
  164. /// 离异
  165. /// </summary>
  166. [Description("离异")]
  167. Divorced,
  168. /// <summary>
  169. /// 丧偶
  170. /// </summary>
  171. [Description("丧偶")]

运行效果

三、答疑解惑

3.1 如何实现每个属性的自定义布局?

答:控件提供了默认外观,如果无法满足要求,也可以编辑控件模板,在相应的数据类型模板中修改布局代码,也可以只编辑某种类型的控件模板,下面展示修改String类型模板,其它类型基本相似。

  1. <Form Source="{Binding Person}">
  2. <Form.StringTemplate>
  3. <DataTemplate>
  4. <Grid>
  5. <Grid.RowDefinitions>
  6. <RowDefinition SharedSizeGroup="RowHeight" />
  7. </Grid.RowDefinitions>
  8. <Grid.ColumnDefinitions>
  9. <ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
  10. <ColumnDefinition Width="10" />
  11. <ColumnDefinition Width="*" />
  12. </Grid.ColumnDefinitions>
  13. <StackPanel
  14. HorizontalAlignment="Right"
  15. VerticalAlignment="Center"
  16. Orientation="Horizontal">
  17. <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
  18. <TextBlock
  19. VerticalAlignment="Center"
  20. Foreground="Red"
  21. Text="*"
  22. Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
  23. </StackPanel>
  24. <TextBox
  25. Grid.Column="2"
  26. MinWidth="150"
  27. VerticalAlignment="Center"
  28. extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
  29. extensions:BindingExtensions.BindingSource="{Binding}" />
  30. </Grid>
  31. </DataTemplate>
  32. </Form.StringTemplate>
  33. </Form>

3.2 如果控件模板中提供的基础数据类型没有我需要的属性类型,如何扩展新的属性类型?

答:控件提供了一个名为“CustomTypeTemplates”的属性来处理这个问题,以下为示例代码。

  1. <Form Source="{Binding Person}">
  2. <Form.CustomTypeTemplates>
  3. <CustomTypeDataTemplateCollection>
  4. <CustomTypeDataTemplate CustomType="{x:Type model:EducationInfo}">
  5. <Grid>
  6. <Grid.RowDefinitions>
  7. <RowDefinition SharedSizeGroup="RowHeight" />
  8. </Grid.RowDefinitions>
  9. <Grid.ColumnDefinitions>
  10. <ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
  11. <ColumnDefinition Width="10" />
  12. <ColumnDefinition Width="*" />
  13. </Grid.ColumnDefinitions>
  14. <StackPanel
  15. HorizontalAlignment="Right"
  16. VerticalAlignment="Center"
  17. Orientation="Horizontal">
  18. <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
  19. <TextBlock
  20. VerticalAlignment="Center"
  21. Foreground="Red"
  22. Text="*"
  23. Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
  24. </StackPanel>
  25. <TextBox
  26. Grid.Column="2"
  27. MinWidth="150"
  28. VerticalAlignment="Center"
  29. extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
  30. extensions:BindingExtensions.BindingSource="{Binding}" />
  31. </Grid>
  32. </CustomTypeDataTemplate>
  33. </CustomTypeDataTemplateCollection>
  34. </Form.CustomTypeTemplates>
  35. </Form>

3.3 如果我想自定义某个属性名的模板,如何实现?

答:控件提供了一个名为“CustomNameTemplates”的属性来处理这个问题,以下为示例代码。

  1. <Form Source="{Binding Person}">
  2. <Form.CustomNameTemplates>
  3. <CustomNameDataTemplateCollection>
  4. <CustomNameDataTemplate CustomName="Name">
  5. <Grid>
  6. <Grid.RowDefinitions>
  7. <RowDefinition SharedSizeGroup="RowHeight" />
  8. </Grid.RowDefinitions>
  9. <Grid.ColumnDefinitions>
  10. <ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
  11. <ColumnDefinition Width="10" />
  12. <ColumnDefinition Width="*" />
  13. </Grid.ColumnDefinitions>
  14. <StackPanel
  15. HorizontalAlignment="Right"
  16. VerticalAlignment="Center"
  17. Orientation="Horizontal">
  18. <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
  19. <TextBlock
  20. VerticalAlignment="Center"
  21. Foreground="Red"
  22. Text="*"
  23. Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
  24. </StackPanel>
  25. <TextBox
  26. Grid.Column="2"
  27. MinWidth="150"
  28. VerticalAlignment="Center"
  29. extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
  30. extensions:BindingExtensions.BindingSource="{Binding}" />
  31. </Grid>
  32. </CustomNameDataTemplate>
  33. </CustomNameDataTemplateCollection>
  34. </Form.CustomNameTemplates>
  35. </Form>

 

3.4 如果我想隐藏某些属性应该怎么做?

答:对属性设置“Browsable”特性,以下为示例代码。

  1. [Browsable(false)]
  2. public EducationInfo Education
  3. {
  4. get => _education;
  5. set => this.SetProperty(ref _education, value);
  6. }

3.5 如何绑定枚举类型?

答:枚举在Form控件中默认显示为下拉框(ComboBox),只需要在枚举中设置"Description“特性就可以正常显示中文选项,如果不设置该属性则直接显示枚举名称。

  1. public enum MaritalStatus
  2. {
  3. /// <summary>
  4. /// 单身
  5. /// </summary>
  6. [Description("单身")]
  7. Single,
  8. /// <summary>
  9. /// 已婚
  10. /// </summary>
  11. [Description("已婚")]
  12. Married,
  13. /// <summary>
  14. /// 离异
  15. /// </summary>
  16. [Description("离异")]
  17. Divorced,
  18. /// <summary>
  19. /// 丧偶
  20. /// </summary>
  21. [Description("丧偶")]
  22. Widowed
  23. }

3.6 如何绑定复杂属性,诸如单选框(RadioButton)、多选框(CheckBox)、下拉框(ComboBox)等?

答:控件读取一个名为”OverwriteType“的特性,特性中有一个名为“HandleMethod”的属性,该属性指明了覆盖当前类型的方式,并需要指定映射参数。

  1. [OverwriteType(HandleMethod.RadioButton, "1=男", "2=女", "3=其他")]
  2. public int Gender
  3. {
  4. get => _gender;
  5. set => this.SetProperty(ref _gender, value);
  6. }
  1. public enum HandleMethod
  2. {
  3. ComboBox,
  4. CheckBox,
  5. RadioButton
  6. }

    以上为数据添加及修改部分的优化实现,下一节讲解如何在查询列表中优化。

原文链接:https://www.cnblogs.com/qushi2020/p/18103287

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号