DataGridView右键菜单自定义显示及隐藏列功能

WinForm程序中表单的列可自定义显示及隐藏,是一种常见的功能,对于用户体验来说是非常好的。笔者经过一段时间的摸索,终于实现了自己想要的功能及效果,现记录一下过程:

1、新建一个自定义控件,命名为:PopupMenuControl。

2、在PopupMenuControl.Designet文件中的InitializeComponent()方法下面,注册以下事件:

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的代码:

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></param> /// <param></param> private void PopupMenuControl_Paint(object sender, PaintEventArgs e) { popupMenuHelper.Draw(e.Graphics); } /// <summary> /// 鼠标移过 /// </summary> /// <param></param> /// <param></param> private void PopupMenuControl_MouseMove(object sender, MouseEventArgs e) { if (popupMenuHelper.IsMouseMove(e.X, e.Y)) { popupMenuHelper.Draw(CreateGraphics()); } } /// <summary> /// 鼠标按下 /// </summary> /// <param></param> /// <param></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></param> /// <param></param> public virtual void OnCheckedChanged(int hitIndex, bool isChecked) { CheckedChangedEvent?.Invoke(hitIndex, isChecked); } }

4、这上面涉及到一个PopupMenuHelper的帮助类,此帮助类主要是为PopupMenuControl控件实现菜单绘制的功能,其代码如下:

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>被点击Item的Index</param> /// <param>图像</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>Item文本</param> /// <param>Item勾选状态</param> public void AddItem(string itemText, bool isChecked) { items.Add(new PopupMenuItem(itemText, isChecked)); } /// <summary> /// 绘制菜单准备 /// </summary> /// <param>图像</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></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></param> /// <param></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>图像</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>图像</param> /// <param>图像Top</param> /// <param>菜单Item</param> /// <param>是否为当前菜单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>X坐标</param> /// <param>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>X坐标</param> /// <param>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>X坐标</param> /// <param>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,其代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdswzj.html