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 != -)
{
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 = ; //Item:高度、Image宽度
private static readonly int BasicGap = ; //四周间距
private static readonly int BasicRows = ; //最大行数
private static readonly int BasicSide = ; //Item:CheckBox边长(建议用偶数)
private int totality = ; //分割总数
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 = , totalHeight = ;
double maxTextWidth = ;
if (totality == )
{
totalHeight = items.Count * BasicConst + * 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 + * BasicGap;
eachWidth[] = (int)Math.Ceiling((double)maxTextWidth) + BasicConst;
}
else
{
totalHeight = BasicRows * BasicConst + * BasicGap;
int rows = , cols = ;
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 - ] = (int)Math.Ceiling((double)maxTextWidth) + BasicConst;
maxTextWidth = ;
cols++;
rows = ;
}
}
else
{
//totality列
if ((cols - ) * BasicRows + rows == items.Count)
{
totalWidth += (int)Math.Ceiling((double)maxTextWidth) + BasicConst + * BasicGap;
eachWidth[cols - ] = (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(, , 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(, , )))
g.DrawRectangle(borderPen, area); //Image及Text
int left = BasicGap, top = BasicGap;
if (totality == )
{
Rectangle imageArea = new Rectangle(left, top, BasicConst, items.Count * BasicConst);
using (Brush backBrush = new SolidBrush(Color.FromArgb(, , )))
g.FillRectangle(backBrush, imageArea); Rectangle textArea = new Rectangle(left + BasicConst, top, eachWidth[], items.Count * BasicConst);
using (Brush backBrush = new SolidBrush(Color.FromArgb(, , )))
g.FillRectangle(backBrush, textArea);
}
else
{
for (int i = ; i < totality; i++)
{
Rectangle imageArea = new Rectangle(left, top, BasicConst, BasicRows * BasicConst);
using (Brush backBrush = new SolidBrush(Color.FromArgb(, , )))
g.FillRectangle(backBrush, imageArea); Rectangle textArea = new Rectangle(left + BasicConst, top, eachWidth[i], BasicRows * BasicConst);
using (Brush backBrush = new SolidBrush(Color.FromArgb(, , )))
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 = , cols = ;
foreach (PopupMenuItem item in items)
{
if (totality == )
{
DrawSingleItem(g, left, ref top, eachWidth[], item, item == hotItem);
}
else
{
rows++;
DrawSingleItem(g, left, ref top, eachWidth[cols - ], item, item == hotItem);
//1..[totality-1]列
if (rows % BasicRows == )
{
left += eachWidth[cols - ];
top = BasicGap;
cols++;
rows = ;
}
}
}
} /// <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(, , )))
g.FillRectangle(backBrush, hotItemArea);
using (Pen borderPen = new Pen(Color.FromArgb(, , )))
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) / );
int checkBoxLeft = drawRect.Left + checkBoxGap;
int checkBoxTop = drawRect.Top + checkBoxGap; //将checkBoxArea的Top减1,与文本的对齐效果稍微好一些。
Rectangle checkBoxArea = new Rectangle(checkBoxLeft, checkBoxTop - , BasicSide, BasicSide);
using (Brush checkBoxBrush = new SolidBrush(Color.FromArgb(, , )))
g.FillRectangle(checkBoxBrush, checkBoxArea);
using (Pen checkBoxPen = new Pen(Color.FromArgb(, , )))
g.DrawRectangle(checkBoxPen, checkBoxArea); using (Pen checkBoxTick = new Pen(Color.FromArgb(, , )))
{
g.DrawLine(checkBoxTick, new Point(checkBoxLeft, checkBoxTop - + (int)(BasicSide / )), new Point(checkBoxLeft + (int)(BasicSide / ), checkBoxTop - + BasicSide));
g.DrawLine(checkBoxTick, new Point(checkBoxLeft + (int)(BasicSide / ), checkBoxTop - + BasicSide), new Point(checkBoxLeft + BasicSide + BasicGap, checkBoxTop - - BasicGap));
}
}
} /// <summary>
/// 点击测试
/// </summary>
/// <param name="X">X坐标</param>
/// <param name="Y">Y坐标</param>
/// <returns></returns>
private PopupMenuItem HitTest(int X, int Y)
{
if (X < || X > Width || Y < || Y > Height)
{
return null;
} int left = BasicGap, top = BasicGap;
int rows = , cols = ;
foreach (PopupMenuItem item in items)
{
if (totality == )
{
rows++;
if (X > left && X < left + eachWidth[] && Y > top + (rows - ) * BasicConst && Y < top + rows * BasicConst)
{
return item;
}
}
else
{
rows++;
if (X > left && X < left + eachWidth[cols - ] && Y > top + (rows - ) * BasicConst && Y < top + rows * BasicConst)
{
return item;
}
//1..[totality-1]列
if (rows % BasicRows == )
{
left += eachWidth[cols - ];
top = BasicGap;
cols++;
rows = ;
}
}
}
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 == -)
{
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;
}
}

以上这些,已经实现了全部的功能。下面开始建一个WinForm程序来测试结果,为方便测试将DataGridView的数据源由xml文件读取。

6、从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 > ? ds.Tables[] : null;
//加工dataGridView1
#region 加列标题测试
dataGridView1.Columns[].HeaderText = "制令单号";
dataGridView1.Columns[].HeaderText = "成品编号";
dataGridView1.Columns[].HeaderText = "生产数量";
dataGridView1.Columns[].HeaderText = "来源单号";
#endregion
DataGridViewColumnSelector columnSelector = new DataGridViewColumnSelector(dataGridView1);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}

8、执行程序,在任意DataGridView标题栏右击,即可弹出菜单:

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

DataGridView右键菜单自定义显示及隐藏列的更多相关文章

  1. [Easyui - Grid]为easyui的datagrid、treegrid增加表头菜单,用于显示或隐藏列

    为easyui的datagrid.treegrid增加表头菜单,用于显示或隐藏列 /** * @author 孙宇 * * @requires jQuery,EasyUI * * 为datagrid. ...

  2. xml publisher根据条件显示或隐藏列

     xml publisher根据条件显示或隐藏列 <?if@column:condition? > -- <?end if?> 样例: 依据PROJECT_FLAG标签显示 ...

  3. C# 控制win7任务栏、开始菜单的显示与隐藏

    因为是做显示程序,故需要控制任务栏与开始菜单的显示与隐藏,这样就美观些.不啰嗦.直接上代码: using System; using System.Collections.Generic; using ...

  4. Vue实现二级菜单的显示与隐藏

    <html> <head> <title>Vue实现二级菜单的显示与隐藏</title> <script src="vue.js&quo ...

  5. 在 jQuery 中使用滑入滑出动画效果,实现二级下拉导航菜单的显示与隐藏效果

    查看本章节 查看作业目录 需求说明: 在 jQuery 中使用滑入滑出动画效果,实现二级下拉导航菜单的显示与隐藏效果 用户将光标移动到"最新动态页"或"帮助查询" ...

  6. 为datagrid、treegrid增加右键表头菜单,用于显示或隐藏列,注意:冻结列不在此菜单中

    var createGridHeaderContextMenu = function(e, field) { e.preventDefault(); var grid = $(this);/* gri ...

  7. win7系统的右键菜单只显示一个白色框不显示菜单项 解决办法

    如上图所示,桌面或其他大部分地方点击右键菜单,都只显示一个白色框,鼠标移上去才有菜单项看,并且效果很丑 解决办法: 计算机-右键-属性-高级-性能-设置-视觉效果-淡入淡出或滑动菜单到视图,将其前面的 ...

  8. Notepad++ 右键菜单自定义配置

    问:想在右键菜单里面多加几个功能,怎么加,比如区块注释 答:其实notepad++的配置文件存放路径不在自己的软件路径,而存在于 xp:C:\Documents and Settings\Admini ...

  9. 让windows10的右键菜单既显示传统cmd又显示powershell

    在windows10的资源管理器中,按住shift点击右键,只显示 open powershell window here,却没有传统的cmd 解决方法就是修改注册表: HKEY_LOCAL_MACH ...

随机推荐

  1. Vue+ElementUI项目使用webpack输出MPA【华为云分享】

    [摘要] Vue+ElementUI多页面打包改造 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目 ...

  2. 【开发者portal在线开发插件系列三】字符串 及 可变长度字符串

    基础篇 基础场景见上面两个帖子,这里单独说明字符串和可变长度字符串的用法. 话不多说,开始今天的演(表)示(演) Profile和插件开发 添加一个string类型的属性: 在插件里添加一条数据上报消 ...

  3. Python ——selenium报错 'chromedriver.exe' executable needs to be in PATH

    from selenium import webdriver dr = webdriver.Chrome() 运行时报错: 问题分析: 1.没有下载chromedriver.exe 2.chromed ...

  4. Spring AOP简介与底层实现机制——动态代理

    AOP简介 AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想.AOP 是 OOP(面向对象编程 Object Oriented Programmi ...

  5. 一篇文章教你轻松使用fastjson

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y JSON相信大家对他也不陌生了,前后端交互中常常 ...

  6. 谈架构设计中DDD思想的运用

    首先,描述一下我的业务场景及项目分层结构,非标准DDD(其实我不觉得有标准),只是思考的时候有带入DDD思想. 业务场景:这是一个ERP系统对中台提供的接口项目,仓储操作大多都是存储过程去完成的. 项 ...

  7. [TimLinux] CSS 计数功能实现递归目录

    内容引用自<css世界>: count-reset 与 counter 为父子关系,兄弟关系会导致序号混乱 调用一次 count-increment 将给序号进行一次报数,调用 count ...

  8. [TimLinux] JavaScript 中循环执行和定时执行

    1. 两对函数 // 循环执行 // 在每个毫秒数之后,调用函数 var timeid = window.setInterval(函数名, 毫秒数); window.clearInterval(tim ...

  9. 教你们学习一个最简单又企业最需要的服务-crond

    第13章 定时任务的介绍 13.1 定时任务的分类 13.1.1 系统实现定时任务的配置 [root@oldboyedu ~] # cd /etc/cron. cron.d/ cron.daily/ ...

  10. 基于cyusb3014的usb3.0双目摄像头开发测试小结(使用mt9m001c12stm)

    测试图像 摄像头分辨率为1280*1024,双目分辨率为2560*1024 ps:时钟频率太高,时序约束还得进一步细化,图像偶尔会出现部分雪花,下一步完善