前言

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

一、务分析

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

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

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

ViewModel

public class EntityEditorView1ViewModel : BindableBase
{
public event Action<Person> Completed; private Person _person;
public Person Person
{
get => _person;
set => this.SetProperty(ref _person, value);
} public DelegateCommand<object> ConfirmCommand
{
get
{
return new DelegateCommand<object>(parameter =>
{
if (!string.IsNullOrEmpty(Person.Error))
{
MessageBox.Show(Person.Error);
}
else
{
Completed?.Invoke(Person);
}
});
}
}
}

Model

public class Person : ValidationObject
{
private string _name;
private DateTime _dateOfBirth = DateTime.Now;
private int _age;
private int _gender = 1;
private string _phoneNumber;
private string _email;
private string _address;
private string _idCardNumber;
private EducationInfo _education;
private MaritalStatus _maritalStatus; /// <summary>
/// 姓名
/// </summary>
[DisplayOrder(1)]
[DisplayHint("请填写姓名")]
[DisplayName("姓名")]
[StringLength(15, MinimumLength = 2, ErrorMessage = "姓名的有效长度为2-15个字符.")]
[Required(ErrorMessage = "姓名为必填项.")]
[Column(Name = "姓名", Order = 1)]
public string Name
{
get => _name;
set => this.SetProperty(ref _name, value);
} /// <summary>
/// 出生日期
/// </summary>
[DisplayOrder(2)]
[DisplayHint("请选择出生日期")]
[DisplayName("出生日期")]
[Required(ErrorMessage = "出生日期为必填项.")]
[Column(Name = "出生日期", StringFormat = "{0:yyyy年MM月dd日}", Order = 2)]
public DateTime DateOfBirth
{
get => _dateOfBirth;
set => this.SetProperty(ref _dateOfBirth, value);
} /// <summary>
/// 年龄
/// </summary>
[DisplayOrder(3)]
[DisplayHint("请填写年龄")]
[DisplayName("年龄")]
[Range(1, 120, ErrorMessage = "年龄的有效值为1-120.")]
[Required(ErrorMessage = "年龄为必填项.")]
[Column(Name = "年龄", Order = 3)]
public int Age
{
get => _age;
set => this.SetProperty(ref _age, value);
} /// <summary>
/// // 性别
/// </summary>
[DisplayOrder(4)]
[DisplayHint("请选择性别")]
[DisplayName("性别")]
[OverwriteType(HandleMethod.RadioButton, "1=男", "2=女", "3=其他")]
[Column(Name = "性别", Order = 4)]
public int Gender
{
get => _gender;
set => this.SetProperty(ref _gender, value);
} /// <summary>
/// 手机号码
/// </summary>
[DisplayOrder(5)]
[DisplayHint("请填写电话号码")]
[DisplayName("电话号码")]
[Phone(ErrorMessage = "电话号码格式不正确.")]
[Column(Name = "电话号码", Order = 5)]
public string PhoneNumber
{
get => _phoneNumber;
set => this.SetProperty(ref _phoneNumber, value);
} /// <summary>
/// 电子邮箱
/// </summary>
[DisplayOrder(6)]
[DisplayHint("请填写电子邮箱")]
[DisplayName("电子邮箱")]
[EmailAddress(ErrorMessage = "电子邮箱格式不正确.")]
[Column(Name = "电子邮箱", Order = 6)]
public string Email
{
get => _email;
set => this.SetProperty(ref _email, value);
} /// <summary>
/// 地址信息
/// </summary>
[DisplayOrder(7)]
[DisplayHint("请填写地址")]
[DisplayName("地址")]
[StringLength(50, ErrorMessage = "地址的最大长度为50个字符.")]
[Column(Name = "地址", Order = 7)]
public string Address
{
get => _address;
set => this.SetProperty(ref _address, value);
} /// <summary>
/// 身份证号码
/// </summary>
[DisplayOrder(8)]
[DisplayName("身份证号码")]
[DisplayHint("请填写身份证号码")]
[RegularExpression(RegexHelper.IdCardNumber, ErrorMessage = "身份证号码格式不正确.")]
[Column(Name = "身份证号码", Order = 8)]
public string IdCardNumber
{
get => _idCardNumber;
set => this.SetProperty(ref _idCardNumber, value);
} /// <summary>
/// 教育信息
/// </summary>
[DisplayOrder(9)]
[DisplayHint("请填写教育信息")]
[DisplayName("教育信息")]
[Browsable(false)]
public EducationInfo Education
{
get => _education;
set => this.SetProperty(ref _education, value);
} /// <summary>
/// 婚姻状况
/// </summary>
[DisplayOrder(10)]
[DisplayHint("请选择婚姻状况")]
[DisplayName("婚姻状况")]
[Column(Name = "婚姻状况", Order = 10)]
public MaritalStatus MaritalStatus
{
get => _maritalStatus;
set => this.SetProperty(ref _maritalStatus, value);
}
} public class EducationInfo
{
public string Degree { get; set; } // 学位
public string Major { get; set; } // 专业
public string School { get; set; } // 学校
public DateTime GraduationDate { get; set; } // 毕业日期
} public enum MaritalStatus
{
/// <summary>
/// 单身
/// </summary>
[Description("单身")]
Single,
/// <summary>
/// 已婚
/// </summary>
[Description("已婚")]
Married,
/// <summary>
/// 离异
/// </summary>
[Description("离异")]
Divorced,
/// <summary>
/// 丧偶
/// </summary>
[Description("丧偶")]

运行效果

三、答疑解惑

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

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

<Form Source="{Binding Person}">
<Form.StringTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="RowHeight" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
<TextBlock
VerticalAlignment="Center"
Foreground="Red"
Text="*"
Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
<TextBox
Grid.Column="2"
MinWidth="150"
VerticalAlignment="Center"
extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
extensions:BindingExtensions.BindingSource="{Binding}" />
</Grid>
</DataTemplate>
</Form.StringTemplate>
</Form>

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

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

<Form Source="{Binding Person}">
<Form.CustomTypeTemplates>
<CustomTypeDataTemplateCollection>
<CustomTypeDataTemplate CustomType="{x:Type model:EducationInfo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="RowHeight" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
<TextBlock
VerticalAlignment="Center"
Foreground="Red"
Text="*"
Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
<TextBox
Grid.Column="2"
MinWidth="150"
VerticalAlignment="Center"
extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
extensions:BindingExtensions.BindingSource="{Binding}" />
</Grid>
</CustomTypeDataTemplate>
</CustomTypeDataTemplateCollection>
</Form.CustomTypeTemplates>
</Form>

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

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

<Form Source="{Binding Person}">
<Form.CustomNameTemplates>
<CustomNameDataTemplateCollection>
<CustomNameDataTemplate CustomName="Name">
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="RowHeight" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Title" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
<TextBlock
VerticalAlignment="Center"
Foreground="Red"
Text="*"
Visibility="{Binding IsRequired, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
<TextBox
Grid.Column="2"
MinWidth="150"
VerticalAlignment="Center"
extensions:BindingExtensions.BindingProperty="{x:Static TextBox.TextProperty}"
extensions:BindingExtensions.BindingSource="{Binding}" />
</Grid>
</CustomNameDataTemplate>
</CustomNameDataTemplateCollection>
</Form.CustomNameTemplates>
</Form>

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

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

[Browsable(false)]
public EducationInfo Education
{
get => _education;
set => this.SetProperty(ref _education, value);
}

3.5 如何绑定枚举类型?

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

public enum MaritalStatus
{
/// <summary>
/// 单身
/// </summary>
[Description("单身")]
Single,
/// <summary>
/// 已婚
/// </summary>
[Description("已婚")]
Married,
/// <summary>
/// 离异
/// </summary>
[Description("离异")]
Divorced,
/// <summary>
/// 丧偶
/// </summary>
[Description("丧偶")]
Widowed
}

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

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

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

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

一个可以让你有更多时间摸鱼的WPF控件(一)的更多相关文章

  1. MFC_VC++_时间获取与保存列表控件内容到文件操作方法

    MFC_VC++_时间获取与保存列表控件内容到excel文件操作方法 void CDataView::OnBnClickedBtnExporttoexcel() { CTime time = CTim ...

  2. Android一个炫酷的树状图组织架构图开源控件实现过程

    Android一个炫酷的树状图组织架构图开源控件 文章目录 [1 简介] [2 效果展示] [3 使用步骤] [4 实现基本布局流程] [5 实现自由放缩及拖动] [6 实现添加删除及节点动画] [7 ...

  3. 使用 WebView2 封装一个生成 PDF 的 WPF 控件

    使用 WebView2 封装一个生成 PDF 的 WPF 控件 最近在迁移项目到 .net6,发现项目中用的 PDF 库不支持 .net6,于是想着换一个库.结果找了一大圈,发现不是版本不支持,就是收 ...

  4. [MFC] 梳理一个简单的图片处理桌面软件中用到的MFC控件技巧

     前言 前些天应好友之拖,帮忙设计一个简单的图像处理的小软件.朋友把核心算法封装好了,但是是用openCV类似于console的编程环境,要我在此基础上改成MFC桌面程序.下图是做成之后的效果: 我是 ...

  5. 我写的一个Qt 显示二维码( QR Code)的控件(可以去掉对 libpthread 的依赖,而且编译出的库文件可以在 vc2010 的release 模式下使用)

    最近一个项目需要显示二维码,所以花了点时间(只用了一个晚上,写的很不完善),写了个显示二维码的控件.当然这个控件用到了些开源的代码,比如qrencode,所以我也打算把我的代码开源. 我的代码参考了 ...

  6. 一个WPF控件 诡异的MouseEvent 。

    背景: private System.Windows.Controls.Border _borderTouch; private bool _mouseDown = false;  private S ...

  7. Birt时间参数添加My97日历控件

    首先,思路: 引用My97.js然后为时间参数的textbox添加onclick事件 1.将My97添加到项目中的webcontent目录下(如图:) 2.添加My97引用 在项目路径下找到该文件\w ...

  8. 一个jsqlparse+git做的小工具帮我节省时间摸鱼

    背景 前些时间做了个小工具解决了团队内数据库脚本检验&多测试环境自动执行的问题,感觉挺有意思,在这跟大家分享一下. 工具诞生之前的流程是这样: 1.开发人员先在开发环境编写脚本&执行: ...

  9. WPF 控件事件的一个小坑…

    最近想判断一下 Slider 是由鼠标点击而改变值,还是由程序内部代码改变的值,发现鼠标的各种事件比如 MouseDown.MouseUp.MouseLeftButtonDown 什么的,都没有任何反 ...

  10. 分享一下我封装iOS自定义控件的体会,附上三个好用的控件Demo <时间选择器&多行输入框&日期选择器>

    前段时间有小伙伴问到我:"这样的控件该怎么做呢?",我感觉是个比较简单的控件,可能对于入行不久的同志思路没有很清晰吧.趁着最近工作不忙,就来这里分享一下我封装自定义控件的几点体会吧 ...

随机推荐

  1. MVVM模式的理解

    MVVM模式的理解 MVVM全称Model-View-ViewModel是基于MVC和MVP体系结构模式的改进,MVVM就是MVC模式中的View的状态和行为抽象化,将视图UI和业务逻辑分开,更清楚地 ...

  2. 如何在 libevent 中读取超过 4096 字节的数据

    如何在 libevent 中读取超过 4096 字节的数据 bufferevent 是 libevent 中相对高层的封装,较 event 使用起来方便很多. 之前有一个需求,需要从服务端读取数据进行 ...

  3. 面试官:说一下红锁RedLock的实现原理?

    RedLock 是一种分布式锁的实现算法,由 Redis 的作者 Salvatore Sanfilippo(也称为 Antirez)提出,主要用于解决在分布式系统中实现可靠锁的问题.在 Redis 单 ...

  4. Jmeter Jsonpath 语法你了解多少?

  5. 十五: InnoDB的存储结构

    InnoDB的存储结构 1.数据库的存储结构:页 索引结构给我们提供了高效的索引方式,不过索引|信息以及数据记录都是保存在文件上的,确切说是存储在页结构中.另一方面,索引是在存储引擎中实现的,MySQ ...

  6. docker 系列

    docker 系列 目录 docker 系列 一. docker 定义 1 nameSpnce 命名空间 2 cgroup 控制组 3 为什么使用容器 二. docker 安装 三 .docker 初 ...

  7. Zabbix6.0使用教程 (三)—zabbix6.0的安装要求

    接上篇,我们继续为大家详细介绍zabbix6.0的使用教程之zabbix6.0的安装部署.接下来我们将从zabbix部署要求到四种不同的安装方式逐一详细的为大家介绍.本篇讲的是部署zabbix6.0的 ...

  8. Github登录 2FA(Two-Factor Authentication/两因素认证) 浏览器插件-已验证

    Github登录 2FA(Two-Factor Authentication/两因素认证) 浏览器插件-已验证 chrome 装下这个扩展 身份验证器 https://chromewebstore.g ...

  9. 虚拟机 centos web nodejs服务 外网映射

    虚拟机 centos web nodejs服务 外网映射 起因 为了不买云服务器也是拼了 1. 安装虚拟机 VMware-Workstation-Lite-15.5.1-15018445精简官方中文安 ...

  10. VS Code Snippet Generator 插件 生成 vscode代码片段

    VS Code Snippet Generator 插件 生成 vscode代码片段