DataGridView右键菜单自定义显示及隐藏列功能
WinForm程序中表单的列可自定义显示及隐藏,是一种常见的功能,对于用户体验来说是非常好的。这篇文章主要介绍了DataGridView右键菜单自定义显示及隐藏列功能,需要的朋友可以参考下
WinForm程序中表单的列可自定义显示及隐藏,是一种常见的功能,对于用户体验来说是非常好的。笔者经过一段时间的摸索,终于实现了自己想要的功能及效果,现记录一下过程:
1、新建一个自定义控件,命名为:PopupMenuControl。
2、在PopupMenuControl.Designet文件中的InitializeComponent()方法下面,注册以下事件:
1 2 3 | this.Paint += new System.Windows.Forms.PaintEventHandler(this.PopupMenuControl_Paint); this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.PopupMenuControl_MouseDown); this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.PopupMenuControl_MouseMove); |
3、PopupMenuControl的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | public partial class PopupMenuControl : UserControl { public delegate void CheckedChanged(int hitIndex, bool isChecked); //勾选改变委托 public event CheckedChanged CheckedChangedEvent; //勾选改变事件 PopupMenuHelper popupMenuHelper = null; //菜单帮助类,主要负责菜单绘制。 public PopupMenuControl() { InitializeComponent(); } public void Initialize(DataGridView dgvTarget) { //菜单帮助类实例化 popupMenuHelper = new PopupMenuHelper(); //将列标题添加到items foreach (DataGridViewColumn column in dgvTarget.Columns) { popupMenuHelper.AddItem(column.HeaderText, column.Visible); } //菜单绘制 popupMenuHelper.Prepare(CreateGraphics()); Width = popupMenuHelper.Width; Height = popupMenuHelper.Height; } /// <summary> /// 绘制 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PopupMenuControl_Paint(object sender, PaintEventArgs e) { popupMenuHelper.Draw(e.Graphics); } /// <summary> /// 鼠标移过 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PopupMenuControl_MouseMove(object sender, MouseEventArgs e) { if (popupMenuHelper.IsMouseMove(e.X, e.Y)) { popupMenuHelper.Draw(CreateGraphics()); } } /// <summary> /// 鼠标按下 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PopupMenuControl_MouseDown(object sender, MouseEventArgs e) { if (popupMenuHelper.IsMouseDown(e.X, e.Y)) { int hitIndex = popupMenuHelper.HitIndex; if (hitIndex != -1) { bool isChecked = popupMenuHelper.IsCheckedChange(hitIndex, CreateGraphics()); OnCheckedChanged(hitIndex, isChecked); } } } /// <summary> /// 勾选改变 /// </summary> /// <param name="iIndex"></param> /// <param name="bChecked"></param> public virtual void OnCheckedChanged(int hitIndex, bool isChecked) { CheckedChangedEvent?.Invoke(hitIndex, isChecked); } } |
4、这上面涉及到一个PopupMenuHelper的帮助类,此帮助类主要是为PopupMenuControl控件实现菜单绘制的功能,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | class PopupMenuHelper { //变量 private PopupMenuItem hotItem = null; //当前Item private List<PopupMenuItem> items = new List<PopupMenuItem>(); //Item集合 private Bitmap bitmap; //位图 private Graphics graphics; //图像 private static readonly int BasicConst = 24; //Item:高度、Image宽度 private static readonly int BasicGap = 3; //四周间距 private static readonly int BasicRows = 3; //最大行数 private static readonly int BasicSide = 10; //Item:CheckBox边长(建议用偶数) private int totality = 1; //分割总数 private int[] eachWidth = null; //各个宽度 //属性 public int Width { get { return bitmap.Width; } } //宽度 public int Height { get { return bitmap.Height; } } //高度 //PopupMenuItem类 private class PopupMenuItem { //属性 public string ItemText { get; set; } //Item文本 public bool IsChecked { get; set; } //勾选状态 //构造函数 public PopupMenuItem(string itemText) : this(itemText, false) { } public PopupMenuItem(string itemText, bool isChecked) { ItemText = itemText; IsChecked = isChecked; } } //无参构造函数 public PopupMenuHelper() { } /// <summary> /// 被点击Item的Index /// </summary> public int HitIndex { get { return items.IndexOf(hotItem); } } /// <summary> /// 勾选改变状态 /// </summary> /// <param name="hitIndex">被点击Item的Index</param> /// <param name="g">图像</param> /// <returns></returns> public bool IsCheckedChange(int hitIndex, Graphics g) { items[hitIndex].IsChecked = !items[hitIndex].IsChecked; Draw(g); return items[hitIndex].IsChecked; } /// <summary> /// 添加Item /// </summary> /// <param name="itemText">Item文本</param> /// <param name="isChecked">Item勾选状态</param> public void AddItem(string itemText, bool isChecked) { items.Add(new PopupMenuItem(itemText, isChecked)); } /// <summary> /// 绘制菜单准备 /// </summary> /// <param name="g">图像</param> public void Prepare(Graphics g) { //获取菜单的宽度及高度 totality = (int)Math.Ceiling((double)items.Count / BasicRows); eachWidth = new int[totality]; int totalWidth = 0, totalHeight = 0; double maxTextWidth = 0; if (totality == 1) { totalHeight = items.Count * BasicConst + 2 * BasicGap; foreach (PopupMenuItem item in items) { //SizeF:存储有序浮点数对,通常为矩形的宽度和高度。 SizeF sizeF = g.MeasureString(item.ItemText, SystemInformation.MenuFont); maxTextWidth = Math.Max(maxTextWidth, sizeF.Width); } totalWidth = (int)Math.Ceiling((double)maxTextWidth) + BasicConst + 2 * BasicGap; eachWidth[0] = (int)Math.Ceiling((double)maxTextWidth) + BasicConst; } else { totalHeight = BasicRows * BasicConst + 2 * BasicGap; int rows = 0, cols = 1; foreach (PopupMenuItem item in items) { rows++; //SizeF:存储有序浮点数对,通常为矩形的宽度和高度。 SizeF sizeF = g.MeasureString(item.ItemText, SystemInformation.MenuFont); maxTextWidth = Math.Max(maxTextWidth, sizeF.Width); if (cols < totality) { //1..[totality-1]列 if (rows == BasicRows) { totalWidth += (int)Math.Ceiling((double)maxTextWidth) + BasicConst; eachWidth[cols - 1] = (int)Math.Ceiling((double)maxTextWidth) + BasicConst; maxTextWidth = 0; cols++; rows = 0; } } else { //totality列 if ((cols - 1) * BasicRows + rows == items.Count) { totalWidth += (int)Math.Ceiling((double)maxTextWidth) + BasicConst + 2 * BasicGap; eachWidth[cols - 1] = (int)Math.Ceiling((double)maxTextWidth) + BasicConst; } } } } //图像初始化 bitmap = new Bitmap(totalWidth, totalHeight); graphics = Graphics.FromImage(bitmap); } /// <summary> /// 绘制菜单 /// </summary> /// <param name="g"></param> public void Draw(Graphics g) { Rectangle area = new Rectangle(0, 0, bitmap.Width, bitmap.Height); graphics.Clear(SystemColors.Menu); DrawBackground(graphics, area); DrawItems(graphics); g.DrawImage(bitmap, area, area, GraphicsUnit.Pixel); } /// <summary> /// 绘制菜单背景 /// </summary> /// <param name="g"></param> /// <param name="area"></param> private void DrawBackground(Graphics g, Rectangle area) { //描边 using (Pen borderPen = new Pen(Color.FromArgb(112, 112, 112))) g.DrawRectangle(borderPen, area); //Image及Text int left = BasicGap, top = BasicGap; if (totality == 1) { Rectangle imageArea = new Rectangle(left, top, BasicConst, items.Count * BasicConst); using (Brush backBrush = new SolidBrush(Color.FromArgb(240, 240, 240))) g.FillRectangle(backBrush, imageArea); Rectangle textArea = new Rectangle(left + BasicConst, top, eachWidth[0], items.Count * BasicConst); using (Brush backBrush = new SolidBrush(Color.FromArgb(255, 255, 255))) g.FillRectangle(backBrush, textArea); } else { for (int i = 0; i < totality; i++) { Rectangle imageArea = new Rectangle(left, top, BasicConst, BasicRows * BasicConst); using (Brush backBrush = new SolidBrush(Color.FromArgb(240, 240, 240))) g.FillRectangle(backBrush, imageArea); Rectangle textArea = new Rectangle(left + BasicConst, top, eachWidth[i], BasicRows * BasicConst); using (Brush backBrush = new SolidBrush(Color.FromArgb(255, 255, 255))) g.FillRectangle(backBrush, textArea); left += eachWidth[i]; } } } /// <summary> /// 绘制所有菜单Item /// </summary> /// <param name="g">图像</param> private void DrawItems(Graphics g) { int left = BasicGap, top = BasicGap; int rows = 0, cols = 1; foreach (PopupMenuItem item in items) { if (totality == 1) { DrawSingleItem(g, left, ref top, eachWidth[0], item, item == hotItem); } else { rows++; DrawSingleItem(g, left, ref top, eachWidth[cols - 1], item, item == hotItem); //1..[totality-1]列 if (rows % BasicRows == 0) { left += eachWidth[cols - 1]; top = BasicGap; cols++; rows = 0; } } } } /// <summary> /// 绘制单个菜单Item /// </summary> /// <param name="g">图像</param> /// <param name="top">图像Top</param> /// <param name="item">菜单Item</param> /// <param name="isHotItem">是否为当前菜单Item</param> private void DrawSingleItem(Graphics g, int left, ref int top,int width, PopupMenuItem item, bool isHotItem) { //Item区域 Rectangle drawRect = new Rectangle(left, top, width, BasicConst); top += BasicConst; //Text区域 Rectangle itemTextArea = new Rectangle ( drawRect.Left + BasicConst, drawRect.Top, drawRect.Width - BasicConst, drawRect.Height ); //背景色及描边色 if (isHotItem) { //HotItem Rectangle hotItemArea = new Rectangle(drawRect.Left, drawRect.Top, drawRect.Width, drawRect.Height); using (SolidBrush backBrush = new SolidBrush(Color.FromArgb(214, 235, 255))) g.FillRectangle(backBrush, hotItemArea); using (Pen borderPen = new Pen(Color.FromArgb(51, 153, 255))) g.DrawRectangle(borderPen, hotItemArea); } //Text处理 StringFormat itemTextFormat = new StringFormat(); //NoClip:允许显示字形符号的伸出部分和延伸到矩形外的未换行文本。 //NoWrap:在矩形内设置格式时,禁用自动换行功能。 itemTextFormat.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap; //Near:指定文本靠近布局对齐。 itemTextFormat.Alignment = StringAlignment.Near; //Center:指定文本在布局矩形中居中对齐(呃,感觉不是很垂直居中,偏上了一些)。 itemTextFormat.LineAlignment = StringAlignment.Center; //Show:显示热键前缀。 itemTextFormat.HotkeyPrefix = HotkeyPrefix.Show; SolidBrush textBrush = new SolidBrush(SystemColors.MenuText); g.DrawString(item.ItemText, SystemInformation.MenuFont, textBrush, itemTextArea, itemTextFormat); //Checkbox处理 if (item.IsChecked) { int checkBoxGap = (int)((drawRect.Height - BasicSide) / 2); int checkBoxLeft = drawRect.Left + checkBoxGap; int checkBoxTop = drawRect.Top + checkBoxGap; //将checkBoxArea的Top减1,与文本的对齐效果稍微好一些。 Rectangle checkBoxArea = new Rectangle(checkBoxLeft, checkBoxTop - 1, BasicSide, BasicSide); using (Brush checkBoxBrush = new SolidBrush(Color.FromArgb(214, 235, 255))) g.FillRectangle(checkBoxBrush, checkBoxArea); using (Pen checkBoxPen = new Pen(Color.FromArgb(51, 153, 255))) g.DrawRectangle(checkBoxPen, checkBoxArea); using (Pen checkBoxTick = new Pen(Color.FromArgb(51, 153, 255))) { g.DrawLine(checkBoxTick, new Point(checkBoxLeft, checkBoxTop - 1 + (int)(BasicSide / 2)), new Point(checkBoxLeft + (int)(BasicSide / 2), checkBoxTop - 1 + BasicSide)); g.DrawLine(checkBoxTick, new Point(checkBoxLeft + (int)(BasicSide / 2), checkBoxTop - 1 + BasicSide), new Point(checkBoxLeft + BasicSide + BasicGap, checkBoxTop - 1 - BasicGap)); } } } /// <summary> /// 点击测试 /// </summary> /// <param name="X">X坐标</param> /// <param name="Y">Y坐标</param> /// <returns></returns> private PopupMenuItem HitTest(int X, int Y) { if (X < 0 || X > Width || Y < 0 || Y > Height) { return null; } int left = BasicGap, top = BasicGap; int rows = 0, cols = 1; foreach (PopupMenuItem item in items) { if (totality == 1) { rows++; if (X > left && X < left + eachWidth[0] && Y > top + (rows - 1) * BasicConst && Y < top + rows * BasicConst) { return item; } } else { rows++; if (X > left && X < left + eachWidth[cols - 1] && Y > top + (rows - 1) * BasicConst && Y < top + rows * BasicConst) { return item; } //1..[totality-1]列 if (rows % BasicRows == 0) { left += eachWidth[cols - 1]; top = BasicGap; cols++; rows = 0; } } } return null; } /// <summary> /// 是否是鼠标移过 /// </summary> /// <param name="X">X坐标</param> /// <param name="Y">Y坐标</param> /// <returns></returns> public bool IsMouseMove(int X, int Y) { PopupMenuItem popupMenuItem = HitTest(X, Y); if (popupMenuItem != hotItem) { hotItem = popupMenuItem; return true; } else { return false; } } /// <summary> /// 是否是鼠标按下 /// </summary> /// <param name="X">X坐标</param> /// <param name="Y">Y坐标</param> /// <returns></returns> public bool IsMouseDown(int X, int Y) { PopupMenuItem popupMenuItem = HitTest(X, Y); return popupMenuItem != null; } } |
这个类实现了多菜单页面的功能:即如果DataGridView字段非常的多,可通过产生多列菜单来显示,程序是通过BasicRows变量来控制。
5、新建一个DataGridViewColumnSelector类,此类的功能主要是衔接DataGridView与PopupMenuControl,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | /// <summary> /// DataGridView右键菜单自定义显示及隐藏列 /// </summary> class DataGridViewColumnSelector { private DataGridView dgvTarget = null; //待处理的DataGridView对象 private ToolStripDropDown dropDown; //用于加载PopupMenu控件 PopupMenuControl popupMenuControl = new PopupMenuControl(); //PopupMenu控件 //无参构造函数 public DataGridViewColumnSelector() { //注册PopupMenu控件事件 popupMenuControl.CheckedChangedEvent += new PopupMenuControl.CheckedChanged(OnCheckedChanged); //使用容器承载PopupMenu控件(相当于容器类型的ToolStripItem) ToolStripControlHost controlHost = new ToolStripControlHost(popupMenuControl); controlHost.Padding = Padding.Empty; controlHost.Margin = Padding.Empty; controlHost.AutoSize = false; //加载PopupMenu控件 dropDown = new ToolStripDropDown(); dropDown.Padding = Padding.Empty; dropDown.AutoClose = true; dropDown.Items.Add(controlHost); } //有参构造函数 public DataGridViewColumnSelector(DataGridView dataGridView) : this() { DataGridView = dataGridView; } //DataGridView属性 public DataGridView DataGridView { get { return dgvTarget; } set { //去除单元格点击事件 if (dgvTarget != null) { dgvTarget.CellMouseClick -= new DataGridViewCellMouseEventHandler(DataGridView_CellMouseClick); } dgvTarget = value; //注册单元格点击事件 if (dgvTarget != null) { dgvTarget.CellMouseClick += new DataGridViewCellMouseEventHandler(DataGridView_CellMouseClick); } } } /// <summary> /// 右键点击标题栏弹出菜单 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DataGridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e) { if (e.Button == MouseButtons.Right && e.RowIndex == -1) { popupMenuControl.Initialize(dgvTarget); //将菜单显示在光标位置 dropDown.Show(Cursor.Position); } } /// <summary> /// 勾选事件执行方法 /// </summary> /// <param name="hitIndex"></param> /// <param name="isCheck"></param> private void OnCheckedChanged(int hitIndex, bool isChecked) { dgvTarget.Columns[hitIndex].Visible = isChecked; } } |
6、以上这些,已经实现了全部的功能。下面开始建一个WinForm程序来测试结果,为方便测试将DataGridView的数据源由xml文件读取。
从SQL Server数据库随便找张数据表生成XML,文件保存为Test.xml。(请将Test.xml文件拷贝到Debug文件夹下面)
1 2 3 4 5 | SELECT TOP 10 MO_NO,MRP_NO,QTY,BIL_NO FROM MF_MO WHERE MO_DD='2019-11-07' ORDER BY MO_NO FOR XML PATH ('Category'),TYPE,ROOT('DocumentElement') |
7、新建一个WinForm程序,命名为Main,并拖入一个DataGridView控件,Main_Load方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private void Main_Load(object sender, EventArgs e) { try { //xml文件路径 string path = @"Test.xml"; //读取文件 DataSet ds = new DataSet(); if (File.Exists(path)) { ds.ReadXml(path); } dataGridView1.DataSource = ds.Tables.Count > 0 ? ds.Tables[0] : null; //加工dataGridView1 #region 加列标题测试 dataGridView1.Columns[0].HeaderText = "制令单号"; dataGridView1.Columns[1].HeaderText = "成品编号"; dataGridView1.Columns[2].HeaderText = "生产数量"; dataGridView1.Columns[3].HeaderText = "来源单号"; #endregion DataGridViewColumnSelector columnSelector = new DataGridViewColumnSelector(dataGridView1); } catch (Exception ex) { MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } |
8、执行程序,在任意DataGridView标题栏右击,即可弹出菜单:
总结
以上所述是小编给大家介绍的DataGridView右键菜单自定义显示及隐藏列功能,希望对大家有所帮助