背景
最近看了Winform在DataGrid中怎么嵌入Combo,想到平时项目中写到的一些临时小工具,经常用配置参数,有些配置是简单的地址或文本,有些则是类似1代表SQL,2代表Oracle等。于是想结合刚刚学的知识,做一个示例。
关于参数的保存,想到用数据库,简单点就是用SQLite,可以无需安装数据库,后来想想,干脆用文件进行存储,这样简单,而且.net对Json的支持很好。
资源下载
实现
1.设计数据结构。
public class ListItem { public List<ListItem> lstItem = new List<ListItem>(); public string Text { get; set; } public object Value { get; set; } public ListItem() { } public ListItem(string pText, string pValue) { Text = pText; Value = pValue; } public override string ToString() { return Text; } }
public class ConfigRow { public string Name { get; set; }//参数名称 public string Text { get; set; }//参数显示值 public string Value { get; set; }//参数真实值 public string Description { get; set; }//参数描述 public int Type { get; set; }//参数类别 1 常规字符 2 下拉框 public List<ListItem> lstItem { get; set; }//参数下拉框数据源列表 #region 数据源操作 //得到数据源组织的字符串 public string GetItemText() { var ret = new StringBuilder(); foreach (var li in lstItem) { ret.AppendLine(li.Value + "|" + li.Text); } return ret.ToString(); } //根据格式化字符串设置数据源 public void SetItem(string text) { if (lstItem == null) lstItem = new List<ListItem>(); else lstItem.Clear(); var arr = text.Trim(' ').Split(' '); foreach (var a in arr) { if (a.Length > 0) { var item = a.Split('|'); if (item.Length > 0) { ListItem li = new ListItem(); li.Value = item[0]; li.Text = item[1]; lstItem.Add(li); } } } } //检查某项是否存在数据源中 public bool CheckItem(string text, string value) { var index = lstItem.FindIndex((item) => { return item.Text == text && item.Value.ToString() == value; }); return index > -1; } #endregion }
public class MyConfig { #region 公共成员 public ThreadStart ComboChange { get; set; } //下拉框改变事件 public string Path = @"./1.json";//文件保存路径 private List<ConfigRow> lstRow { get; set; } //所有配置行 #endregion #region 私有成员 private ComboBox cbb; private DataGridView dgv; private MyConfig() { } #endregion #region 控件设置与事件 private void Load() { #region DataGridView UI var clName = new System.Windows.Forms.DataGridViewTextBoxColumn(); var clValue = new System.Windows.Forms.DataGridViewTextBoxColumn(); var clDesp = new System.Windows.Forms.DataGridViewTextBoxColumn(); var clType = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.dgv.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { clName, clValue, clDesp, clType}); // // clName // clName.DataPropertyName = "Name"; clName.HeaderText = "参数名称"; clName.Name = "clName"; clName.ReadOnly = true; clName.Width = (int)Math.Floor(dgv.Width * 0.2); // // clValue // clValue.DataPropertyName = "Text"; clValue.HeaderText = "参数内容"; clValue.Name = "clValue"; clValue.Width = (int)Math.Floor(dgv.Width * 0.3); // // clDesp // clDesp.DataPropertyName = "Description"; clDesp.HeaderText = "描述"; clDesp.Name = "clDesp"; clDesp.ReadOnly = true; clDesp.Width = (int)Math.Floor(dgv.Width * 0.5); // // clType // clType.DataPropertyName = "Type"; clType.HeaderText = "参数状态"; clType.Name = "clType"; clType.ReadOnly = true; clType.Visible = false; #endregion //其他属性设置 cbb = new ComboBox(); cbb.Visible = false; cbb.DropDownStyle = ComboBoxStyle.DropDownList; dgv.AutoGenerateColumns = false;//不允许自动创建列 dgv.Controls.Add(cbb); //绑定数据源 if (lstRow != null && lstRow.Count != 0) dgv.DataSource = lstRow; //注册控件事件 dgv.CurrentCellDirtyStateChanged += CurrentCellDirtyStateChanged; dgv.CurrentCellChanged += CurrentCellChanged; dgv.ColumnWidthChanged += ColumnWidthChanged; cbb.SelectedIndexChanged += SelectedIndexChanged; dgv.RowPostPaint += RowPostPaint; } private void CurrentCellDirtyStateChanged(object sender, EventArgs e) { var currentRow = dgv.CurrentRow.DataBoundItem as ConfigRow; if (currentRow.Type != 2) //下拉框类别单元格,在更改下拉框时已修改,此处忽略 { if (dgv.CurrentCell.Value == null) currentRow.Value = currentRow.Text = string.Empty; else currentRow.Value = currentRow.Text = dgv.CurrentCell.Value.ToString(); } Update(currentRow); } private void CurrentCellChanged(object sender, EventArgs e) { if (dgv.CurrentCell == null) return; //首先清空该事件 否则连续点击2个下拉框执行时有错误 if (ComboChange != null) ComboChange = null; //若点击的是Value列,而且该参数类别为下拉框 if (dgv.CurrentCell.OwningColumn.Name == "clValue" && dgv.CurrentRow.Cells["clType"].Value.ToString() == "2") { var current = dgv.CurrentRow.DataBoundItem as ConfigRow; cbb.DataSource = current.lstItem; cbb.Text = dgv.CurrentCell.Value.ToString(); //设置下拉框的位置 var rect = dgv.GetCellDisplayRectangle(dgv.CurrentCell.ColumnIndex, dgv.CurrentCell.RowIndex, false); cbb.Visible = true; cbb.Bounds = rect; //注册下拉框事件 ComboChange = new ThreadStart(() => { var li = cbb.SelectedItem as ListItem; //若值发生变更,则更新DataGridView以及绑定的数据源 if (li.Text != dgv.CurrentCell.Value.ToString()) { dgv.CurrentCell.Value = li.Text; current.Text = li.Text; current.Value = li.Value.ToString(); //这里是引用传递,修改后直接体现在DataBoundItem属性上 无需再次赋值 //dataGridView1.CurrentRow.DataBoundItem = current; //下拉框直接赋值将不会触发CurrentCellDirtyStateChanged事件 直接更新 Update(current); } }); } else cbb.Visible = false; } private void ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) { dgv.ClearSelection(); cbb.Visible = false; } private void RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e) { SolidBrush b = new SolidBrush(dgv.RowHeadersDefaultCellStyle.ForeColor); e.Graphics.DrawString((e.RowIndex + 1).ToString(System.Globalization.CultureInfo.CurrentUICulture), dgv.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4); } private void SelectedIndexChanged(object sender, EventArgs e) { if (ComboChange != null) ComboChange(); } #endregion #region 加载文件 //根据路径加载配置文件 并显示在DataGridView控件上 public static MyConfig Load(DataGridView dataGridView1, string path) { MyConfig my = new MyConfig(); my.dgv = dataGridView1; try { var json = File.ReadAllText(path); my.lstRow = JsonConvert.DeserializeObject<List<ConfigRow>>(json); } catch { my.lstRow = new List<ConfigRow>(); } my.Load(); return my; } //根据路径加载配置文件 public static MyConfig Load(string path) { MyConfig my = new MyConfig(); try { var json = File.ReadAllText(path); my.lstRow = JsonConvert.DeserializeObject<List<ConfigRow>>(json); } catch { my.lstRow = new List<ConfigRow>(); } return my; } //保存 private void Save() { if (dgv != null) { dgv.DataSource = null; dgv.DataSource = lstRow; } var json = JsonConvert.SerializeObject(lstRow); File.WriteAllText(Path, json); } #endregion #region 增删改查 //增加配置 public void Add(ConfigRow row) { var index = lstRow.FindIndex((config) => { return config.Name == row.Name; }); if (index == -1) lstRow.Add(row); //找到则更新 未找到则添加 else lstRow[index] = row; Save(); } //删除配置 public bool Remove(string name) { var index = lstRow.FindIndex((config) => { return config.Name == name; }); if (index == -1) return false; lstRow.RemoveAt(index); Save(); //删除最后一行时不会出发CurrentCellChanged事件,下拉框可能未被隐藏 if (lstRow.Count == 0 && cbb != null && cbb.Visible) cbb.Visible = false; return true; } //更新配置 public bool Update(ConfigRow row) { var index = lstRow.FindIndex((config) => { return config.Name == row.Name; }); if (index == -1) return false; else lstRow[index] = row; Save(); return true; } //查询配置 public ConfigRow Find(string name) { return lstRow.Find((config) => { return config.Name == name; }); } #endregion #region 读取某项的值,并指定默认值 //根据Name读取配置 public string GetString(string name, string defaultValue) { var currentRow = Find(name); if (currentRow == null) return defaultValue; return currentRow.Value; } //根据Name读取配置 public int GetInt(string name, int defaultValue) { var currentRow = lstRow.Find((config) => { return config.Name == name; }); if (currentRow == null) return defaultValue; int value; if (Int32.TryParse(currentRow.Value, out value)) return value; return defaultValue; } #endregion //测试 public static MyConfig Create(DataGridView dgv) { var my = new MyConfig(); my.lstRow = new List<ConfigRow>(); //添加配置项 用户姓名 var row1 = new ConfigRow(); row1.Name = "UserName"; row1.Value = "admin"; row1.Text = "admin"; row1.Description = "配置用户的姓名"; row1.Type = 1;//常规字符 my.lstRow.Add(row1); //添加配置项 地址 var row2 = new ConfigRow(); row2.Name = "UserAddress"; row2.Value = "湖南省长沙市"; row2.Text = "湖南省长沙市"; row2.Description = "配置用户的地址"; row2.Type = 1;//常规字符 my.lstRow.Add(row2); //添加配置项 性别 var row3 = new ConfigRow(); row3.Name = "UserSex"; row3.Value = "1"; row3.Text = "男"; row3.Description = "配置用户的性别"; row3.Type = 2;//下拉数据源 //非常规字符必须添加数据源 var item = new ListItem(); item.Text = "男"; item.Value = "1"; var item2 = new ListItem(); item2.Text = "女"; item2.Value = "0"; row3.lstItem = new List<ListItem>() { item, item2 }; my.lstRow.Add(row3); //添加配置项 年龄 var row4 = new ConfigRow(); row4.Name = "UserAge"; row4.Value = "15"; row4.Text = "15岁"; row4.Description = "配置用户的年龄"; row4.Type = 2;//下拉数据源 //非常规字符必须添加数据源 row4.lstItem = new List<ListItem>(); for (var i = 0; i < 20; i++) { row4.lstItem.Add(new ListItem(i.ToString() + "岁", i.ToString())); } my.lstRow.Add(row4); return my; } }
2.典型的窗体使用实例
MyConfig my; public frmMain() { InitializeComponent(); } private void Form3_Load(object sender, EventArgs e) { my = MyConfig.Load(dataGridView1, "./1.json"); ; } private void btnAdd_Click(object sender, EventArgs e) { frmCreate fc = new frmCreate(my); if (fc.ShowDialog() == DialogResult.OK) { my = fc.my; } } private void btnDel_Click(object sender, EventArgs e) { if (dataGridView1.CurrentCell != null) { var name = dataGridView1.CurrentRow.Cells["clName"].Value.ToString(); my.Remove(name); } } private void btnUpdate_Click(object sender, EventArgs e) { if (dataGridView1.CurrentCell != null) { var name = dataGridView1.CurrentRow.Cells["clName"].Value.ToString(); frmCreate fc = new frmCreate(my, name); if (fc.ShowDialog() == DialogResult.OK) { my = fc.my; } } }
有些控件列的操作和一些必需的属性设置,把这些代码从Designer.cs中拷贝出来,直接放在方法里直接调用,是为了避免新建一个窗体,又有重复那么多设置操作。但这样肯定会带来一定的不灵活性。
就这样做了2天,做完发现实用性有限,权当技术研究吧。
另外还可以配置参数实体上添加正则属性,这样就方便进行正则检查参数是否合法。