DataGridView User defined Display and Hide Column Function in Right click Menu

  • 2021-11-10 09:21:21
  • OfStack

WinForm program form columns can be customized display and hide, is a common function, for the user experience is very good. After a period of exploration, the author finally realized the function and effect he wanted. Now record the following process:

1. Create a new custom control named PopupMenuControl.

2. Under the InitializeComponent () method in the PopupMenuControl. Designet file, register the following events:


 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. Code of PopupMenuControl:


 public partial class PopupMenuControl : UserControl
 {  public delegate void CheckedChanged(int hitIndex, bool isChecked); // Check Change Delegation 
  public event CheckedChanged CheckedChangedEvent;     // Check Change Event 
  PopupMenuHelper popupMenuHelper = null;        // Menu help class, mainly responsible for menu drawing. 
  public PopupMenuControl()
  {
   InitializeComponent();
  }
  public void Initialize(DataGridView dgvTarget)
  {
   // Menu Help Class Instantiation 
   popupMenuHelper = new PopupMenuHelper();
   // Add column headers to the items
   foreach (DataGridViewColumn column in dgvTarget.Columns)
   {
    popupMenuHelper.AddItem(column.HeaderText, column.Visible);
   }
   // Menu drawing 
   popupMenuHelper.Prepare(CreateGraphics());
   Width = popupMenuHelper.Width;
   Height = popupMenuHelper.Height;
  }
  /// <summary>
  ///  Draw 
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void PopupMenuControl_Paint(object sender, PaintEventArgs e)
  {
   popupMenuHelper.Draw(e.Graphics);
  }
  /// <summary>
  ///  Mouse over 
  /// </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>
  ///  Mouse press 
  /// </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>
  ///  Check Change 
  /// </summary>
  /// <param name="iIndex"></param>
  /// <param name="bChecked"></param>
  public virtual void OnCheckedChanged(int hitIndex, bool isChecked)
  {
   CheckedChangedEvent?.Invoke(hitIndex, isChecked);
  }
 }

4. This involves a help class of PopupMenuHelper. This help class mainly realizes the function of menu drawing for PopupMenuControl controls. Its code is as follows:


 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;      //4周间距
  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:指定文本在布局矩形中居中对齐(呃,感觉不是很垂直居中,偏上了1些)。
   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,与文本的对齐效果稍微好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;
  }
 }

This class realizes the function of multi-menu page: that is, if DataGridView fields are very many, it can be displayed by producing multi-column menus, and the program is controlled by BasicRows variables.

5. Create a new DataGridViewColumnSelector class. The function of this class is to connect DataGridView with PopupMenuControl. Its code is as follows:


/// <summary>
 /// DataGridView Right-click menu to customize display and hide columns 
 /// </summary>
 class DataGridViewColumnSelector
 {
  private DataGridView dgvTarget = null;      // To be processed DataGridView Object 
  private ToolStripDropDown dropDown;       // Used to load PopupMenu Control 
  PopupMenuControl popupMenuControl = new PopupMenuControl(); //PopupMenu Control 
  // Parametric constructor 
  public DataGridViewColumnSelector()
  {
   // Registration PopupMenu Control event 
   popupMenuControl.CheckedChangedEvent += new PopupMenuControl.CheckedChanged(OnCheckedChanged);
   // Use container load PopupMenu Control ( Equivalent to the container type ToolStripItem)
   ToolStripControlHost controlHost = new ToolStripControlHost(popupMenuControl);
   controlHost.Padding = Padding.Empty;
   controlHost.Margin = Padding.Empty;
   controlHost.AutoSize = false;
   // Loading PopupMenu Control 
   dropDown = new ToolStripDropDown();
   dropDown.Padding = Padding.Empty;
   dropDown.AutoClose = true;
   dropDown.Items.Add(controlHost);
  }
  // Parametric constructor 
  public DataGridViewColumnSelector(DataGridView dataGridView) : this()
  {
   DataGridView = dataGridView;
  }
  //DataGridView Attribute 
  public DataGridView DataGridView
  {
   get { return dgvTarget; }
   set
   {
    // Remove cell click event 
    if (dgvTarget != null) { dgvTarget.CellMouseClick -= new DataGridViewCellMouseEventHandler(DataGridView_CellMouseClick); }
    dgvTarget = value;
    // Register cell click event 
    if (dgvTarget != null) { dgvTarget.CellMouseClick += new DataGridViewCellMouseEventHandler(DataGridView_CellMouseClick); }
   }
  }
  /// <summary>
  ///  Right-click the title bar pop-up menu 
  /// </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);
    // Display the menu at the cursor position 
    dropDown.Show(Cursor.Position);
   }
  }
  /// <summary>
  ///  Check the event execution method 
  /// </summary>
  /// <param name="hitIndex"></param>
  /// <param name="isCheck"></param>
  private void OnCheckedChanged(int hitIndex, bool isChecked)
  {
   dgvTarget.Columns[hitIndex].Visible = isChecked;
  }
 }

6. All the functions have been realized. The following start to build an WinForm program to test the results, for the convenience of testing the data source of DataGridView read by xml file.

From the SQL Server database to find a data table to generate XML, the file is saved as Test. xml. (Copy the Test. xml file under the Debug folder)


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. Create a new WinForm program, named Main, and drag in an DataGridView control. The Main_Load method is as follows:


private void Main_Load(object sender, EventArgs e)
  {
   try
   {
    //xml File path 
    string path = @"Test.xml";
    // Read a file 
    DataSet ds = new DataSet();
    if (File.Exists(path))
    {
     ds.ReadXml(path);
    }
    dataGridView1.DataSource = ds.Tables.Count > 0 ? ds.Tables[0] : null;
    // Machining dataGridView1
    #region  Add header test 
    dataGridView1.Columns[0].HeaderText = " Order number ";
    dataGridView1.Columns[1].HeaderText = " Finished product number ";
    dataGridView1.Columns[2].HeaderText = " Production quantity ";
    dataGridView1.Columns[3].HeaderText = " Source number ";
    #endregion
    DataGridViewColumnSelector columnSelector = new DataGridViewColumnSelector(dataGridView1);
   }
   catch (Exception ex)
   {
    MessageBox.Show(ex.Message, " Prompt ", MessageBoxButtons.OK, MessageBoxIcon.Information);
   }
  }

8. Execute the program and right-click on any DataGridView title bar to pop up the menu:

Summarize


Related articles: