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

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

2020年03月09日  | 移动技术网IT编程  | 我要评论

襄樊旅游,siro,福建飞美航线启航

 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右键菜单自定义显示及隐藏列功能,希望对大家有所帮助

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

相关文章:

验证码:
移动技术网