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 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控件实现菜单绘制的功能,其代码如下:
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,其代码如下:
/// <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文件夹下面)
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方法如下:
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标题栏右击,即可弹出菜单:
好了,分享就到此结束了,希望对有此需要的人有一些帮助。
上一篇: 嘉士伯啤酒产地,带你了解丹麦啤酒巨人