当前位置: 移动技术网 > IT编程>开发语言>.net > DataGridView右键菜单自定义显示及隐藏列

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

2019年11月12日  | 移动技术网IT编程  | 我要评论

莫娜卡,武汉大学研究生管理系统,400号码

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

 

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

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网