欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-20 17:36:40
WinForm程序中表单的列可自定义显示及隐藏,是一种常见的功能,对于用户体验来说是非常好的。笔者经过一段时间的摸索,终于实现了自己想要的功能及效果,现记录一下过程: 1、新建一个自定义控件,命名为:PopupMenuControl。 2、在PopupMenuControl.Designet文件中的 ......

    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标题栏右击,即可弹出菜单:

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

 

    好了,分享就到此结束了,希望对有此需要的人有一些帮助。