• 一个可以让你有更多时间摸鱼的WPF控件(一)


    前言

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

    一、务分析

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

    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 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;
    
        /// 
        /// 姓名
        /// 
        [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);
        }
    
        /// 
        /// 出生日期
        /// 
        [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);
        }
    
        /// 
        /// 年龄
        /// 
        [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);
        }
    
        /// 
        /// // 性别
        /// 
        [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);
        }
    
        /// 
        /// 手机号码
        /// 
        [DisplayOrder(5)]
        [DisplayHint("请填写电话号码")]
        [DisplayName("电话号码")]
        [Phone(ErrorMessage = "电话号码格式不正确.")]
        [Column(Name = "电话号码", Order = 5)]
        public string PhoneNumber
        {
            get => _phoneNumber;
            set => this.SetProperty(ref _phoneNumber, value);
        }
    
        /// 
        /// 电子邮箱
        /// 
        [DisplayOrder(6)]
        [DisplayHint("请填写电子邮箱")]
        [DisplayName("电子邮箱")]
        [EmailAddress(ErrorMessage = "电子邮箱格式不正确.")]
        [Column(Name = "电子邮箱", Order = 6)]
        public string Email
        {
            get => _email;
            set => this.SetProperty(ref _email, value);
        }
    
        /// 
        /// 地址信息
        /// 
        [DisplayOrder(7)]
        [DisplayHint("请填写地址")]
        [DisplayName("地址")]
        [StringLength(50, ErrorMessage = "地址的最大长度为50个字符.")]
        [Column(Name = "地址", Order = 7)]
        public string Address
        {
            get => _address;
            set => this.SetProperty(ref _address, value);
        }
    
        /// 
        /// 身份证号码
        /// 
        [DisplayOrder(8)]
        [DisplayName("身份证号码")]
        [DisplayHint("请填写身份证号码")]
        [RegularExpression(RegexHelper.IdCardNumber, ErrorMessage = "身份证号码格式不正确.")]
        [Column(Name = "身份证号码", Order = 8)]
        public string IdCardNumber
        {
            get => _idCardNumber;
            set => this.SetProperty(ref _idCardNumber, value);
        }
    
        /// 
        /// 教育信息
        /// 
        [DisplayOrder(9)]
        [DisplayHint("请填写教育信息")]
        [DisplayName("教育信息")]
        [Browsable(false)]
        public EducationInfo Education
        {
            get => _education;
            set => this.SetProperty(ref _education, value);
        }
    
        /// 
        /// 婚姻状况
        /// 
        [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
    {
        /// 
        /// 单身
        /// 
        [Description("单身")]
        Single,
        /// 
        /// 已婚
        /// 
        [Description("已婚")]
        Married,
        /// 
        /// 离异
        /// 
        [Description("离异")]
        Divorced,
        /// 
        /// 丧偶
        /// 
        [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
    {
        /// 
        /// 单身
        /// 
        [Description("单身")]
        Single,
        /// 
        /// 已婚
        /// 
        [Description("已婚")]
        Married,
        /// 
        /// 离异
        /// 
        [Description("离异")]
        Divorced,
        /// 
        /// 丧偶
        /// 
        [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
    }
    复制代码

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

     

    技术交流群
     
    联系方式
  • 相关阅读:
    海量数据如何在Web端实现动态可视化?看看这家企业是怎么做的
    kafka介绍
    centos7 install postgres-15
    C#.NET 国密SM3 HASH 哈希 与JAVA互通 ver:20230803
    沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟
    【Python自然语言处理】文本向量化的六种常见模型讲解(独热编码、词袋模型、词频-逆文档频率模型、N元模型、单词-向量模型、文档-向量模型)
    2023全网最火的接口自动化框架对比 (建议收藏)
    【网络编程】TCP/IP协议(互联网的基石)
    Linux防火墙设置开机自启/永久开机不启动
    设计模式定义
  • 原文地址:https://www.cnblogs.com/qushi2020/p/18103287