本文主要介绍如何改造DataGridView,实现列样式的自动加载。
文章目录
1. 环境搭建+效果演示
1.1 环境搭建之界面设计
环境:Visual Studio 2019 + ,NET Framework 4.8
首先在VS中创建一个桌面应用程序:
然后添加引用:
然后将自定义组件加载进工具箱中:
然后我们就可以使用组件设计界面了:
调整CLCDataGridView大小位置后,在依次添加四个按钮:
按钮从左到右依次为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:
点击button2:
点击button3:
点击button4:
1.4 保存列样式演示
首先在CLCDataGridView的属性面板中打开EnableSave
:
然后再次启动项目,点击button1,然后调整列顺序,列宽度等:
点击button2后,再次点击button1,你会发现列样式仍然是调整之前的模样。
在我们的程序目录下,存在两个XML文件,保存着列样式,这样下次用户打开程序时,DataGridView会从用户本地读取XML文件,初始化列样式:
我们再次修改CLCDataGridView的属性SaveModel
,改为ModelTwo
:
再次启动项目,调整列样式,关闭项目后,之后启动项目,列样式仍然时调整之后的模样。
ModelTwo是在用户缓存中以JSON格式保存了列样式:
C:\Users\Administrator\AppData\Local\CLCDataGridView_demo\CLCDataGridView-demo.exe_Url_aopwkjtarpi0ilkonjp2rsv5yilycwlg\1.0.0.0\user.config
打开user.config文件,列样式信息如下:
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/