C#— —[winforms]自定义DataGridView,实现列样式自适应呈现

本文主要介绍如何改造DataGridView,实现列样式的自动加载。

1. 环境搭建+效果演示

1.1 环境搭建之界面设计

环境:Visual Studio 2019 + ,NET Framework 4.8

首先在VS中创建一个桌面应用程序:

image-20210811171737117

然后添加引用:

image-20210811171950859

image-20210811172020719

然后将自定义组件加载进工具箱中:

image-20210811172717042

image-20210811172903086

然后我们就可以使用组件设计界面了:

image-20210811173025309

调整CLCDataGridView大小位置后,在依次添加四个按钮:

image-20210811173235181

按钮从左到右依次为button1-4。

至此,界面设计完成。

1.2 环境搭建之数据准备

首先准备实体类Student:

using Com.Lee.Attribute;

namespace CLCDataGridView_demo
{
    public class Student
    {
        [ColumnStyle(HeaderText = "姓名")]
        public string Name { get; set; }

        [ColumnStyle(HeaderText = "性别")]
        public string Gender { get; set; }

        [ColumnStyle(HeaderText = "年龄")]
        public int Age { get; set; }

        [ColumnStyle(HeaderText = "学校")]
        public string School { get; set; }

        [Ignore]
        public string Hobby { get; set; }

        [ColumnStyle(ColumnType = ColumnStyleEnum.ButtonColumn)]
        public string ButtonValue { get; set; }

        public Student(string name, string gender, int age, string school, string hobby, string buttonValue)
        {
            Name = name;
            Gender = gender;
            Age = age;
            School = school;
            Hobby = hobby;
            ButtonValue = buttonValue;
        }
    }
}

然后准备实体类User:

namespace CLCDataGridView_demo
{
    public class User
    {
        public string Username { get; set; }
        public string Password { get; set; }
        public string Salt { get; set; }

        public User(string username, string password, string salt)
        {
            Username = username;
            Password = password;
            Salt = salt;
        }
    }
}

为四个按钮添加点击事件:

private void button1_Click(object sender, EventArgs e)
{
    List<Student> students = new List<Student>()
    {
        new Student("张三", "男", 19, "xx大学", "电影", "button1"),
        new Student("李四", "男", 20, "xx大学", "旅游", "button2"),
        new Student("小花", "女", 19, "xx大学", "电影、旅游", "button3"),
        new Student("小红", "女", 20, "xx大学", "电影", "button4")
    };

    clcDataGridView1.DataSource = students;
}

private void button2_Click(object sender, EventArgs e)
{
    List<User> users = new List<User>()
    {
        new User("admin", "admin", "123"),
        new User("scott", "tiger", "saly")
    };

    clcDataGridView1.DataSource = users;
}

private void button3_Click(object sender, EventArgs e)
{
    List<object> objects = new List<object>()
    {
        new User("admin", "admin", "123"),
        new User("scott", "tiger", "saly")
    };

    clcDataGridView1.DataSource = objects;
}

private void button4_Click(object sender, EventArgs e)
{
    clcDataGridView1.DataSource = 1;
}

1.3 运行项目

运行项目后,效果如下:

点击button1:

image-20210811174909703

点击button2:

image-20210811174924492

点击button3:

image-20210811174936015

点击button4:

image-20210811174951113

1.4 保存列样式演示

首先在CLCDataGridView的属性面板中打开EnableSave

image-20210811175315926

然后再次启动项目,点击button1,然后调整列顺序,列宽度等:

image-20210811175423067

点击button2后,再次点击button1,你会发现列样式仍然是调整之前的模样。

在我们的程序目录下,存在两个XML文件,保存着列样式,这样下次用户打开程序时,DataGridView会从用户本地读取XML文件,初始化列样式:

image-20210811175615280

我们再次修改CLCDataGridView的属性SaveModel,改为ModelTwo

image-20210811175754731

再次启动项目,调整列样式,关闭项目后,之后启动项目,列样式仍然时调整之后的模样。

ModelTwo是在用户缓存中以JSON格式保存了列样式:

C:\Users\Administrator\AppData\Local\CLCDataGridView_demo\CLCDataGridView-demo.exe_Url_aopwkjtarpi0ilkonjp2rsv5yilycwlg\1.0.0.0\user.config

打开user.config文件,列样式信息如下:

image-20210811180125845

2. 使用介绍

自定义DataGridView利用反射,实现了根据实体属性上的特性[ColumnStyle],自动生成列样式。接下来就详细说明一下[ColumnStyle]的用法,定义如下:

public class ColumnStyleAttribute : System.Attribute
{
    public string Name { get; set; }
    public string HeaderText { get; set; }
    public int Width { get; set; }
    private int displayIndex;

    public int DisplayIndex
    {
        get => displayIndex;
        set
        {
            if (value <= 0)
            {
                displayIndex = -1;
                return;
            }

            displayIndex = value;
        }
    }

    public string DataPropertyName { get; set; }
    public string Visible { get; set; }
    public ColumnStyleEnum ColumnType { get; set; }
}

public enum ColumnStyleEnum
{
    TextBoxColumn,
    ButtonColumn,
    CheckBoxColumn,
    ComboBoxColumn,
    ImageColumn,
    LinkColumn
}

我们只需要在实体属性上标注[ColumnStyle]特性,并指明相关信息,就可以了:

[ColumnStyle(
    Name = "Name",
    HeaderText = "姓名",
    DisplayIndex = 0,
    Width = 80,
    DataPropertyName = "Name",
    ColumnType = ColumnStyleEnum.TextBoxColumn,
    Visible = "true")]
public string Name { get; set; }

上述示例完整给出了列样式信息,表示Name属性对应着DataGridView中的Name列,名字是Name,列名是姓名,显示序列为0,宽度为80,绑定的实体数据项名为Name,显示格式为TextBox,可见性为True。

我们不一定需要完整给出每一个列样式信息,只需要按需给出,其余未给出的列样式信息会有相应的默认值:Name、HeaderText、DataPropertyName的默认值为属性名,DisplayIndex的默认值是属性在实体中的声明顺序,Width的默认值是60,ColumnType的默认值是TextBox,Visiblef的默认值是true。

我们甚至可以不用给出[ColumnStyle],这样属性对应的列样式都是默认值。

除了[ColumnStyle],还声明了[Ignore]特性,该特性表示该属性没有对应的列,即在DataGridView中不生成对应的列。

除了通过反射解析列样式,我们还可以开启列样式保存开关EnableSave=true,这样当用户关闭程序或者切换DataGridView中的显示实体时,就会自动保存当前显示实体的列样式,下次再显示同类的实体数据时,就会读取用户保存的列样式了。通过SaveModel属性,我们提供了两种保存方式。

3. 实现说明

3.1 反射解析实体特性实现说明

首先定义接口:

public interface ITypeHandler
{
    // 确定是否能处理Type类型的数据
    bool CanHandle(Type type);

    // 处理Type类型的数据,即在这个方法中反射解析实体,然后返回列样式
    List<DataGridViewColumn> Handle(Type type);

}

ListTypeHandler实现上述接口,表示该类可以处理实现了IList接口的数据源:

/// <summary>
/// DataSource 数据源实现了IList接口
/// </summary>
public class ListTypeHandler : ITypeHandler
{
    public bool CanHandle(Type type)
    {
        Type ilistType = type.GetInterface("System.Collections.Generic.IList`1");
        if (ilistType != null)
        {
            return true;
        }
        return false;
    }

    public List<DataGridViewColumn> Handle(Type type)
    {
        // 获取实体Type
        Type modelType = type.GenericTypeArguments.First();
        // 反射解析实体
        return ParseModel(modelType);
    }

    /// <summary>
    /// 用户本地环境没有保存列样式,则根据实体特性利用反射生成列样式
    /// </summary>
    /// <param name="modelType">实体类型</param>
    /// <returns>DataGridView列样式</returns>
    private List<DataGridViewColumn> ParseModel(Type modelType)
    {
        List<DataGridViewColumn> columns = new List<DataGridViewColumn>();

        PropertyInfo[] propertyInfos = modelType.GetProperties();
        for (int i = 0; i < propertyInfos.Length; i++)
        {
            PropertyInfo propertyInfo = propertyInfos[i];
            // 如果该属性没有标注Ignore特性
            if (propertyInfo.GetCustomAttribute(typeof(IgnoreAttribute)) == null)
            {
                // 获取属性上的Display特性
                ColumnStyleAttribute display = (ColumnStyleAttribute)propertyInfo.GetCustomAttribute(typeof(ColumnStyleAttribute));

                DataGridViewColumn column = new DataGridViewTextBoxColumn();
                column.Name = propertyInfo.Name;
                column.HeaderText = propertyInfo.Name;
                column.DataPropertyName = propertyInfo.Name;
                column.Width = 60;
                column.DisplayIndex = i;
                column.Visible = true;

                if (display != null)
                {
                    switch (display.ColumnType)
                    {
                        case ColumnStyleEnum.TextBoxColumn:
                            column = new DataGridViewTextBoxColumn();
                            break;
                        case ColumnStyleEnum.ButtonColumn:
                            column = new DataGridViewButtonColumn();
                            break;
                        case ColumnStyleEnum.ComboBoxColumn:
                            column = new DataGridViewComboBoxColumn();
                            break;
                        case ColumnStyleEnum.CheckBoxColumn:
                            column = new DataGridViewCheckBoxColumn();
                            break;
                        case ColumnStyleEnum.ImageColumn:
                            column = new DataGridViewImageColumn();
                            break;
                        case ColumnStyleEnum.LinkColumn:
                            column = new DataGridViewLinkColumn();
                            break;
                    }

                    column.Name = display.Name ?? propertyInfo.Name;
                    column.HeaderText = display.HeaderText ?? propertyInfo.Name;
                    column.DataPropertyName = display.DataPropertyName ?? propertyInfo.Name;
                    if (display.Width > 0)
                    {
                        column.Width = display.Width;
                    }

                    if (display.DisplayIndex > 0)
                    {
                        column.DisplayIndex = display.DisplayIndex - 1;
                    }

                    if (!string.IsNullOrEmpty(display.Visible))
                    {
                        column.Visible = bool.Parse(display.Visible);
                    }
                }

                columns.Add(column);
            }
        }

        return columns;
    }
}

3.2 列样式的自动保存与加载

在这里给出模式一的列样式自动保存与加载:

/// <summary>
/// 以模式一加载列样式-从项目路径中的XML文件加载列样式
/// </summary>
/// <returns></returns>
private bool BindColumnStyleOne()
{
    try
    {
        // 如果不存在列样式XML文件,加载失败
        if (string.IsNullOrEmpty(path) || !File.Exists(path))
        {
            return false;
        }
        //先清除当前的列样式,再加载新的列样式
        dataTable.Rows.Clear();
        dataTable.ReadXml(path);
        // 将加载后的DataTable中的列样式写入DataGridView中
        ReadFromDataTable();
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

/// <summary>
/// 以模式一的方式将DataGridView列样式保存到用户本地中-XML
/// </summary>
private void SaveColumnStyleOne()
{
    try
    {
        if (string.IsNullOrEmpty(path) || Columns.Count <= 0)
        {
            return;
        }
        //如果目录不存在则创建
        var dir = path.Substring(0, path.LastIndexOf('\\'));
        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

        dataTable.Rows.Clear();
        // 将DataGridView中的列样式读入DataTable中
        WriteDataTable();
        // 保存列样式到XML文件中
        dataTable.WriteXml(path);
    }
    catch (Exception)
    {
    }
}

3.3 CLCDataGridView中的最主要方法

/// <summary>
/// 当数据源改变时,自适应加载列样式
/// </summary>
/// <param name="e"></param>
protected override void OnDataSourceChanged(EventArgs e)
{
    base.OnDataSourceChanged(e);

    if (DataSource != null)
    {
        if (dataTable == null)
        {
            InitDataTable();
        }

        // 如果开启了列样式保存
        if (EnableSave)
        {
            // 先保存当前的列样式
            if (!string.IsNullOrEmpty(path) && Columns.Count > 0)
            {
                SaveColumnStyle();
            }
        }
        // 然后清空当前的列信息
        if (Columns.Count > 0)
        {
            Columns.Clear();
        }

        // 改变实体类别信息等
        Type type = DataSource.GetType();
        // 判断数据源是否合法,如果不合法,则直接返回
        bool canHandleFlag = false;
        foreach (ITypeHandler typeHandler in TypeHandlers)
        {
            if (typeHandler.CanHandle(type))
            {
                canHandleFlag = true;
                break;
            }
        }
        if (!canHandleFlag)
        {
            MessageBox.Show("数据源必须为列表");
            return;
        }

        modelType = type.GenericTypeArguments.First();
        path = Application.StartupPath + @"\User_tmp\" 
            + this.Name + "-" + modelType.FullName.Replace(".", "_") + ".xml";


        // 如果开启了列样式保存
        if (EnableSave)
        {
            // 从用户本地环境加载已保存的列样式
            bool res = BindColumnStyle();
            if (res)
            {
                // 用户本地环境有保存的列样式,直接返回
                return;
            }
        }

        // 没有开启列样式保存,或者用户本地环境没有保存的列样式
        // 反射解析实体,得到列样式
        bool flag = false;
        List<DataGridViewColumn> columns = new List<DataGridViewColumn>();
        foreach (ITypeHandler typeHandler in TypeHandlers)
        {
            if (typeHandler.CanHandle(type))
            {
                flag = true;
                columns = typeHandler.Handle(type);
                break;
            }
        }

        if (flag)
        {
            Columns.AddRange(columns.ToArray());
        }
        else
        {
            MessageBox.Show("数据源应为列表");
        }
    }
}

4. 下载地址

CSDN下载地址:https://download.csdn.net/download/qq_41261251/21042943

参考资料

[1] https://blog.csdn.net/cxu123321/article/details/103741088/

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值