定制DataGridView快捷菜单ContextMenuStrip的关联事件

来源:互联网 发布:命令行进入linux mysql 编辑:程序博客网 时间:2024/05/01 04:30


前言

经常使用表格控件DataGridView的行关联快捷菜单(也称为上下文弹出菜单)ContextMenuStrip,基本步骤如下:

  • 在窗体上设计ContextMenuStrip快捷菜单控件;
  • 设置DataGridView.RowTemplate.ContextMenuStrip属性为指定的快捷菜单;
  • 在菜单弹出前捕获关联事件DataGridView.RowContextMenuStripNeeded,获得当前行与快捷菜单,并做适当处理。

但是,使用其关联事件DataGridView.RowContextMenuStripNeeded有一个重要的前提:“RowContextMenuStripNeeded 事件仅在设置了DataGridView控件的DataSource属性或者该控件的VirtualMode属性为 true 时发生。”(参考MSDN:RowContextMenuStripNeeded 事件)。

此外,上述方法还有一个不足之处:在非数据行的地方(如:表格列头或行头)不能使用RowTemplate.ContextMenuStrip快捷菜单,也捕获不到事件DataGridView.RowContextMenuStripNeeded事件。当然,还可以使用其它类似事件,如:CellContextMenuStripNeeded,等等。但它们均受到同样的约束。

事实上,DataGridView.ContextMenuStrip是控件本身的快捷菜单。本文介绍的定制DataGridView控件,就是直接应用其ContextMenuStrip属性,定制一个快捷菜单关联事件,实现RowTemplate.ContextMenuStrip类似功能。基本思路如下:

  • 重写DataGridView.MouseDown(MouseEventArgs e)方法,捕获鼠标右击事件;
  • 根据事件参数MouseEventArgs的鼠标位置,计算DataGridView当前位置的行号与列号;
  • 定制关联事件ContextMenuStripNeeded,在快捷菜单弹出前获取行号、列号与快捷菜单对象对象。

关键技术

捕获鼠标右击位置(坐标),根据该位置计算当前行号与列号,并引发自定义关联事件。如下代码是捕获鼠标右击事件(定制DataGridView控件中的代码):

[c-sharp] view plaincopy
  1. protected override void OnMouseDown(MouseEventArgs e)  
  2. {  
  3.     base.OnMouseDown(e);  
  4.     if (e.Button == MouseButtons.Right)  
  5.     {  
  6.         if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  7.         {  
  8.             int rowIndex = this.GetRowIndexAt(e.Location);  // 计算行号  
  9.             int colIndex = this.GetColIndexAt(e.Location);  // 计算列号  
  10.             ContextMenuStripNeededEventArgs ee;  // 事件参数  
  11.             ee = new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  12.             this.OnContextMenuStripNeeded(ee);  // 引发自定义事件,执行事件方法  
  13.         }  
  14.     }  
  15. }  

只有在ContextMenuStrip属性对象非空,以及定制关联事件ContextMenuStripNeeded非空(即有事件注册者)时,才需要计算行列坐标,并由OnContextMenuStripNeeded引发调用事件处理方法。当前鼠标位置的行/列编号计算方法如下:

[c-sharp] view plaincopy
  1. private int GetColIndexAt(int mouseLocation_X)  
  2. {  
  3.     if (this.FirstDisplayedScrollingColumnIndex < 0)  
  4.     {  
  5.         return -1;  // no coumns.  
  6.     }  
  7.     if (this.RowHeadersVisible == true && mouseLocation_X <= this.RowHeadersWidth)  
  8.     {  
  9.         return -1;  
  10.     }  
  11.     int columnCount = this.Columns.Count;  
  12.     for(int index = 0; index < columnCount; index++)  // 搜索全部列,因为列可能ReOrder  
  13.     {  
  14.         if(this.Columns[index].Displayed == true)  // 必须是显示的  
  15.         {  
  16.             Rectangle rect = this.GetColumnDisplayRectangle(index, true);  // 取该列的显示部分区域  
  17.             if (rect.Left <= mouseLocation_X && mouseLocation_X < rect.Right)  
  18.             {  
  19.                 return index;  
  20.             }  
  21.         }  
  22.     }  
  23.     return -1;  
  24. }  
  25. private int GetRowIndexAt(int mouseLocation_Y)  
  26. {  
  27.     if (this.FirstDisplayedScrollingRowIndex < 0)  
  28.     {  
  29.         return -1;  // no rows.  
  30.     }  
  31.     if (this.ColumnHeadersVisible == true && mouseLocation_Y <= this.ColumnHeadersHeight)  
  32.     {  
  33.         return -1;  
  34.     }  
  35.     int index = this.FirstDisplayedScrollingRowIndex;  
  36.     int displayedCount = this.DisplayedRowCount(true);  
  37.     for (int k = 1; k <= displayedCount; )  // 因为行不能ReOrder,故只需要搜索显示的行  
  38.     {  
  39.         if (this.Rows[index].Visible == true)  
  40.         {  
  41.             Rectangle rect = this.GetRowDisplayRectangle(index, true);  // 取该区域的显示部分区域  
  42.             if (rect.Top <= mouseLocation_Y && mouseLocation_Y < rect.Bottom)  
  43.             {  
  44.                 return index;  
  45.             }  
  46.             k++;  // 只计数显示的行;  
  47.         }  
  48.         index++;  
  49.     }  
  50.     return -1;  
  51. }  

代码中,参数mouseLocation来自MouseEventArgs的Location属性,this.FirstDisplayedScrollingRowIndex表示当前显示的第一行的行号,this.DisplayedRowCount(true)获取显示的全部行数,参数true表示要包括最后部分显示的行。

按照惯例,-1表示当前鼠标位置位于所有行或列之外,如:表的列头、行头等地方。

源码与演示程序

自定义关联事件的参数类ContextMenuStripNeededEventArgs、完整的定制CustomDataGridView及演示窗体,请参考 全部源码与示例(C#2005)。需要说明,为便于测试比较当前行号与列号,示例代码中的快捷菜单在其Opening事件中设置e.Cancel = true,具体测试时可以去掉该设置。另外,可以不定制DataGridView控件,直接在窗体和DataGridView上应用本文介绍的方法,此时只需要稍稍修改代码即可。

补记:一个更好的解决方案

上述方法的核心是重写DataGraidView的鼠标事件处理函数OnMouseDown(),更好的处理方法如下:

  • 重写WndProc(ref m)消息处理函数,根据消息参数m.LParam计算出鼠标坐标位置,再计算该位置的DataGridView的行号与列号;
  • 根据消息(WM_CONTEXTMENU=0x007b)可以屏幕快捷菜单,完成ContextMenuStrip.Opening事件中设置e.Cancel=true的类似功能。

参考如下代码:

[c-sharp] view plaincopy
  1. protected override void WndProc(ref Message m)  
  2. {  
  3.     if (m.Msg == WM_CONTEXTMENU)  // 处理菜单弹出消息, 注意:该消息前必然有鼠标右击消息  
  4.     {  
  5.         if (m_cancelPopup == true)  // 取消快捷菜单  
  6.         {  
  7.             m_cancelPopup = false;  
  8.         }  
  9.         else  
  10.         {  
  11.             base.WndProc(ref m);  
  12.         }  
  13.     }  
  14.     else  
  15.     {  
  16.         base.WndProc(ref m);  
  17.         // 先处理鼠标右击消息计算行/列号,并处理定制事件  
  18.         if (m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONDBLCLK)    
  19.         {  
  20.             if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  21.             {  
  22.                 int v = m.LParam.ToInt32();  
  23.                 int x = v & 0xffff;          // LParam 低16位是x坐标  
  24.                 int y = (v >> 16) & 0xffff;  // LParam 高16位是y坐标  
  25.                 int rowIndex = this.GetRowIndexAt(y);  
  26.                 int colIndex = this.GetColIndexAt(x);  
  27.                 ContextMenuStripNeededEventArgs e =  
  28.                     new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  29.                 this.OnContextMenuStripNeeded(e);  // 调用事件方法  
  30.                 m_cancelPopup = e.Cancel;  // 获取事件返回参数  
  31.             }  
  32.         }  
  33.     }  
  34. }  

在消息处理函数WndProc(ref m)中,首先捕获鼠标右击消息(WM_RBUTTONDOWN=0x0204),计算鼠标当前位置,从而算出DataGridView当前的行号与列号,最后处理定制的关联事件ContextMenuStripNeeded。关联事件处理程序中可以设置参数Cancel。当Cancel=true时表示禁止快捷菜单消息,从而屏蔽了该菜单弹出事件。显然,还需要修改相应的行号和列号计算函数GetRowIndexAt()与GetColIndexAt()的参数类型为int。

下面是定制关联事件的参数类ContextMenuStripNeededEventArgs:

[c-sharp] view plaincopy
  1. public class ContextMenuStripNeededEventArgs : EventArgs  
  2. {  
  3.     private bool m_cancel = false;  
  4.     private int m_rowIndex = -1;  
  5.     private int m_colIndex = -1;  
  6.     private ContextMenuStrip m_contextMenuStrip;  
  7.     public ContextMenuStripNeededEventArgs(ContextMenuStrip cms, int rowIndex, int colIndex)  
  8.     {  
  9.         m_contextMenuStrip = cms;  
  10.         m_rowIndex = rowIndex;  
  11.         m_colIndex = colIndex;  
  12.     }  
  13.     public ContextMenuStrip ContextMenuStrip  
  14.     {  
  15.         get { return m_contextMenuStrip; }  
  16.     }  
  17.     public int RowIndex  
  18.     {  
  19.         get { return m_rowIndex; }  
  20.     }  
  21.     public int ColIndex  
  22.     {  
  23.         get { return m_colIndex; }  
  24.     }  
  25.     public bool Cancel  
  26.     {  
  27.         get { return m_cancel; }  
  28.         set { m_cancel = value; }  
  29.     }  
  30. }  

参数类ContextMenuStripNeededEventArgs的属性Cancel是可以读写的,其它参数则只能读。如果在事件处理方法中设置Cancel=true,表示取消快捷菜单弹出。

为保证完整性,下面给出修改后的定制控件CustomDataGridView的全部代码,也可到前面链接下载更新后的源码与示例:

[c-sharp] view plaincopy
  1. public class CustomDataGridView : DataGridView  
  2. {  
  3.     private const int WM_RBUTTONDOWN = 0x0204;  
  4.     private const int WM_RBUTTONDBLCLK = 0x0206;  
  5.     private const int WM_CONTEXTMENU = 0x007b;  
  6.     private bool m_cancelPopup = false;  
  7.     public event EventHandler<ContextMenuStripNeededEventArgs> ContextMenuStripNeeded;  
  8.     protected override void WndProc(ref Message m)  
  9.     {  
  10.         if (m.Msg == WM_CONTEXTMENU)  // 处理菜单弹出消息, 注意:该消息前必然有鼠标右击消息  
  11.         {  
  12.             if (m_cancelPopup == true)  // 取消快捷菜单  
  13.             {  
  14.                 m_cancelPopup = false;  
  15.             }  
  16.             else  
  17.             {  
  18.                 base.WndProc(ref m);  
  19.             }  
  20.         }  
  21.         else  
  22.         {  
  23.             base.WndProc(ref m);  
  24.             // 先处理鼠标右击消息计算行/列号,并处理定制事件  
  25.             if (m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONDBLCLK)    
  26.             {  
  27.                 if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  28.                 {  
  29.                     int v = m.LParam.ToInt32();  
  30.                     int x = v & 0xffff;          // LParam 低16位是x坐标  
  31.                     int y = (v >> 16) & 0xffff;  // LParam 高16位是y坐标  
  32.                     int rowIndex = this.GetRowIndexAt(y);  
  33.                     int colIndex = this.GetColIndexAt(x);  
  34.                     ContextMenuStripNeededEventArgs e =  
  35.                         new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  36.                     this.OnContextMenuStripNeeded(e);  // 调用事件方法  
  37.                     m_cancelPopup = e.Cancel;  // 获取事件返回参数  
  38.                 }  
  39.             }  
  40.         }  
  41.     }  
  42.     protected virtual void OnContextMenuStripNeeded(ContextMenuStripNeededEventArgs e)  
  43.     {  
  44.         if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  45.         {  
  46.             this.ContextMenuStripNeeded(this, e);  
  47.         }  
  48.     }  
  49.       
  50.     private int GetColIndexAt(int mouseLocation_X)  
  51.     {  
  52.         if (this.FirstDisplayedScrollingColumnIndex < 0)  
  53.         {  
  54.             return -1;  // no coumns.  
  55.         }  
  56.         if (this.RowHeadersVisible == true && mouseLocation_X <= this.RowHeadersWidth)  
  57.         {  
  58.             return -1;  
  59.         }  
  60.         int columnCount = this.Columns.Count;  
  61.         for(int index = 0; index < columnCount; index++)  // 搜索全部列,因为列可能ReOrder  
  62.         {  
  63.             if(this.Columns[index].Displayed == true)  // 必须是显示的  
  64.             {  
  65.                 Rectangle rect = this.GetColumnDisplayRectangle(index, true);  // 取该列的显示部分区域  
  66.                 if (rect.Left <= mouseLocation_X && mouseLocation_X < rect.Right)  
  67.                 {  
  68.                     return index;  
  69.                 }  
  70.             }  
  71.         }  
  72.         return -1;  
  73.     }  
  74.     private int GetRowIndexAt(int mouseLocation_Y)  
  75.     {  
  76.         if (this.FirstDisplayedScrollingRowIndex < 0)  
  77.         {  
  78.             return -1;  // no rows.  
  79.         }  
  80.         if (this.ColumnHeadersVisible == true && mouseLocation_Y <= this.ColumnHeadersHeight)  
  81.         {  
  82.             return -1;  
  83.         }  
  84.         int index = this.FirstDisplayedScrollingRowIndex;  
  85.         int displayedCount = this.DisplayedRowCount(true);  
  86.         for (int k = 1; k <= displayedCount; )  // 因为行不能ReOrder,故只需要搜索显示的行  
  87.         {  
  88.             if (this.Rows[index].Visible == true)  
  89.             {  
  90.                 Rectangle rect = this.GetRowDisplayRectangle(index, true);  // 取该区域的显示部分区域  
  91.                 if (rect.Top <= mouseLocation_Y && mouseLocation_Y < rect.Bottom)  
  92.                 {  
  93.                     return index;  
  94.                 }  
  95.                 k++;  // add when visible;  
  96.             }  
  97.             index++;  
  98.         }  
  99.         return -1;  
  100.     }  
  101. }  
  102. public class ContextMenuStripNeededEventArgs : EventArgs  
  103. {  
  104.     private bool m_cancel = false;  
  105.     private int m_rowIndex = -1;  
  106.     private int m_colIndex = -1;  
  107.     private ContextMenuStrip m_contextMenuStrip;  
  108.     public ContextMenuStripNeededEventArgs(ContextMenuStrip cms, int rowIndex, int colIndex)  
  109.     {  
  110.         m_contextMenuStrip = cms;  
  111.         m_rowIndex = rowIndex;  
  112.         m_colIndex = colIndex;  
  113.     }  
  114.     public ContextMenuStrip ContextMenuStrip  
  115.     {  
  116.         get { return m_contextMenuStrip; }  
  117.     }  
  118.     public int RowIndex  
  119.     {  
  120.         get { return m_rowIndex; }  
  121.     }  
  122.     public int ColIndex  
  123.     {  
  124.         get { return m_colIndex; }  
  125.     }  
  126.     public bool Cancel  
  127.     {  
  128.         get { return m_cancel; }  
  129.         set { m_cancel = value; }  
  130.     }  
  131. }  

更正说明

测试中还发现如下一些问题,本文的代码已做了修改。全新的源码(有部分变化)和示例请参考 全部源码与示例(C#2005,2009-2-14),特声明。

  • 不仅需要捕获鼠标右单击消息,还必须捕获鼠标右双击消息(WM_RBUTTONDBLCLK = 0x0206);
  • 计算列号时,必须考虑第一列有部分隐藏的宽度,累加时必须减去这部分;
  • 计算行/列号前,必须判断当前显示的行/列号是否为-1。这是DataGridView无行/列定义的情况;
  • 直接应用GetColumnDisplayRectangle(index, true)、GetRowDisplayRectangle(index, true)获取行或列单元的坐标;

前言

经常使用表格控件DataGridView的行关联快捷菜单(也称为上下文弹出菜单)ContextMenuStrip,基本步骤如下:

  • 在窗体上设计ContextMenuStrip快捷菜单控件;
  • 设置DataGridView.RowTemplate.ContextMenuStrip属性为指定的快捷菜单;
  • 在菜单弹出前捕获关联事件DataGridView.RowContextMenuStripNeeded,获得当前行与快捷菜单,并做适当处理。

但是,使用其关联事件DataGridView.RowContextMenuStripNeeded有一个重要的前提:“RowContextMenuStripNeeded 事件仅在设置了DataGridView控件的DataSource属性或者该控件的VirtualMode属性为 true 时发生。”(参考MSDN:RowContextMenuStripNeeded 事件)。

此外,上述方法还有一个不足之处:在非数据行的地方(如:表格列头或行头)不能使用RowTemplate.ContextMenuStrip快捷菜单,也捕获不到事件DataGridView.RowContextMenuStripNeeded事件。当然,还可以使用其它类似事件,如:CellContextMenuStripNeeded,等等。但它们均受到同样的约束。

事实上,DataGridView.ContextMenuStrip是控件本身的快捷菜单。本文介绍的定制DataGridView控件,就是直接应用其ContextMenuStrip属性,定制一个快捷菜单关联事件,实现RowTemplate.ContextMenuStrip类似功能。基本思路如下:

  • 重写DataGridView.MouseDown(MouseEventArgs e)方法,捕获鼠标右击事件;
  • 根据事件参数MouseEventArgs的鼠标位置,计算DataGridView当前位置的行号与列号;
  • 定制关联事件ContextMenuStripNeeded,在快捷菜单弹出前获取行号、列号与快捷菜单对象对象。

关键技术

捕获鼠标右击位置(坐标),根据该位置计算当前行号与列号,并引发自定义关联事件。如下代码是捕获鼠标右击事件(定制DataGridView控件中的代码):

[c-sharp] view plaincopy
  1. protected override void OnMouseDown(MouseEventArgs e)  
  2. {  
  3.     base.OnMouseDown(e);  
  4.     if (e.Button == MouseButtons.Right)  
  5.     {  
  6.         if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  7.         {  
  8.             int rowIndex = this.GetRowIndexAt(e.Location);  // 计算行号  
  9.             int colIndex = this.GetColIndexAt(e.Location);  // 计算列号  
  10.             ContextMenuStripNeededEventArgs ee;  // 事件参数  
  11.             ee = new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  12.             this.OnContextMenuStripNeeded(ee);  // 引发自定义事件,执行事件方法  
  13.         }  
  14.     }  
  15. }  

只有在ContextMenuStrip属性对象非空,以及定制关联事件ContextMenuStripNeeded非空(即有事件注册者)时,才需要计算行列坐标,并由OnContextMenuStripNeeded引发调用事件处理方法。当前鼠标位置的行/列编号计算方法如下:

[c-sharp] view plaincopy
  1. private int GetColIndexAt(int mouseLocation_X)  
  2. {  
  3.     if (this.FirstDisplayedScrollingColumnIndex < 0)  
  4.     {  
  5.         return -1;  // no coumns.  
  6.     }  
  7.     if (this.RowHeadersVisible == true && mouseLocation_X <= this.RowHeadersWidth)  
  8.     {  
  9.         return -1;  
  10.     }  
  11.     int columnCount = this.Columns.Count;  
  12.     for(int index = 0; index < columnCount; index++)  // 搜索全部列,因为列可能ReOrder  
  13.     {  
  14.         if(this.Columns[index].Displayed == true)  // 必须是显示的  
  15.         {  
  16.             Rectangle rect = this.GetColumnDisplayRectangle(index, true);  // 取该列的显示部分区域  
  17.             if (rect.Left <= mouseLocation_X && mouseLocation_X < rect.Right)  
  18.             {  
  19.                 return index;  
  20.             }  
  21.         }  
  22.     }  
  23.     return -1;  
  24. }  
  25. private int GetRowIndexAt(int mouseLocation_Y)  
  26. {  
  27.     if (this.FirstDisplayedScrollingRowIndex < 0)  
  28.     {  
  29.         return -1;  // no rows.  
  30.     }  
  31.     if (this.ColumnHeadersVisible == true && mouseLocation_Y <= this.ColumnHeadersHeight)  
  32.     {  
  33.         return -1;  
  34.     }  
  35.     int index = this.FirstDisplayedScrollingRowIndex;  
  36.     int displayedCount = this.DisplayedRowCount(true);  
  37.     for (int k = 1; k <= displayedCount; )  // 因为行不能ReOrder,故只需要搜索显示的行  
  38.     {  
  39.         if (this.Rows[index].Visible == true)  
  40.         {  
  41.             Rectangle rect = this.GetRowDisplayRectangle(index, true);  // 取该区域的显示部分区域  
  42.             if (rect.Top <= mouseLocation_Y && mouseLocation_Y < rect.Bottom)  
  43.             {  
  44.                 return index;  
  45.             }  
  46.             k++;  // 只计数显示的行;  
  47.         }  
  48.         index++;  
  49.     }  
  50.     return -1;  
  51. }  

代码中,参数mouseLocation来自MouseEventArgs的Location属性,this.FirstDisplayedScrollingRowIndex表示当前显示的第一行的行号,this.DisplayedRowCount(true)获取显示的全部行数,参数true表示要包括最后部分显示的行。

按照惯例,-1表示当前鼠标位置位于所有行或列之外,如:表的列头、行头等地方。

源码与演示程序

自定义关联事件的参数类ContextMenuStripNeededEventArgs、完整的定制CustomDataGridView及演示窗体,请参考 全部源码与示例(C#2005)。需要说明,为便于测试比较当前行号与列号,示例代码中的快捷菜单在其Opening事件中设置e.Cancel = true,具体测试时可以去掉该设置。另外,可以不定制DataGridView控件,直接在窗体和DataGridView上应用本文介绍的方法,此时只需要稍稍修改代码即可。

补记:一个更好的解决方案

上述方法的核心是重写DataGraidView的鼠标事件处理函数OnMouseDown(),更好的处理方法如下:

  • 重写WndProc(ref m)消息处理函数,根据消息参数m.LParam计算出鼠标坐标位置,再计算该位置的DataGridView的行号与列号;
  • 根据消息(WM_CONTEXTMENU=0x007b)可以屏幕快捷菜单,完成ContextMenuStrip.Opening事件中设置e.Cancel=true的类似功能。

参考如下代码:

[c-sharp] view plaincopy
  1. protected override void WndProc(ref Message m)  
  2. {  
  3.     if (m.Msg == WM_CONTEXTMENU)  // 处理菜单弹出消息, 注意:该消息前必然有鼠标右击消息  
  4.     {  
  5.         if (m_cancelPopup == true)  // 取消快捷菜单  
  6.         {  
  7.             m_cancelPopup = false;  
  8.         }  
  9.         else  
  10.         {  
  11.             base.WndProc(ref m);  
  12.         }  
  13.     }  
  14.     else  
  15.     {  
  16.         base.WndProc(ref m);  
  17.         // 先处理鼠标右击消息计算行/列号,并处理定制事件  
  18.         if (m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONDBLCLK)    
  19.         {  
  20.             if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  21.             {  
  22.                 int v = m.LParam.ToInt32();  
  23.                 int x = v & 0xffff;          // LParam 低16位是x坐标  
  24.                 int y = (v >> 16) & 0xffff;  // LParam 高16位是y坐标  
  25.                 int rowIndex = this.GetRowIndexAt(y);  
  26.                 int colIndex = this.GetColIndexAt(x);  
  27.                 ContextMenuStripNeededEventArgs e =  
  28.                     new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  29.                 this.OnContextMenuStripNeeded(e);  // 调用事件方法  
  30.                 m_cancelPopup = e.Cancel;  // 获取事件返回参数  
  31.             }  
  32.         }  
  33.     }  
  34. }  

在消息处理函数WndProc(ref m)中,首先捕获鼠标右击消息(WM_RBUTTONDOWN=0x0204),计算鼠标当前位置,从而算出DataGridView当前的行号与列号,最后处理定制的关联事件ContextMenuStripNeeded。关联事件处理程序中可以设置参数Cancel。当Cancel=true时表示禁止快捷菜单消息,从而屏蔽了该菜单弹出事件。显然,还需要修改相应的行号和列号计算函数GetRowIndexAt()与GetColIndexAt()的参数类型为int。

下面是定制关联事件的参数类ContextMenuStripNeededEventArgs:

[c-sharp] view plaincopy
  1. public class ContextMenuStripNeededEventArgs : EventArgs  
  2. {  
  3.     private bool m_cancel = false;  
  4.     private int m_rowIndex = -1;  
  5.     private int m_colIndex = -1;  
  6.     private ContextMenuStrip m_contextMenuStrip;  
  7.     public ContextMenuStripNeededEventArgs(ContextMenuStrip cms, int rowIndex, int colIndex)  
  8.     {  
  9.         m_contextMenuStrip = cms;  
  10.         m_rowIndex = rowIndex;  
  11.         m_colIndex = colIndex;  
  12.     }  
  13.     public ContextMenuStrip ContextMenuStrip  
  14.     {  
  15.         get { return m_contextMenuStrip; }  
  16.     }  
  17.     public int RowIndex  
  18.     {  
  19.         get { return m_rowIndex; }  
  20.     }  
  21.     public int ColIndex  
  22.     {  
  23.         get { return m_colIndex; }  
  24.     }  
  25.     public bool Cancel  
  26.     {  
  27.         get { return m_cancel; }  
  28.         set { m_cancel = value; }  
  29.     }  
  30. }  

参数类ContextMenuStripNeededEventArgs的属性Cancel是可以读写的,其它参数则只能读。如果在事件处理方法中设置Cancel=true,表示取消快捷菜单弹出。

为保证完整性,下面给出修改后的定制控件CustomDataGridView的全部代码,也可到前面链接下载更新后的源码与示例:

[c-sharp] view plaincopy
  1. public class CustomDataGridView : DataGridView  
  2. {  
  3.     private const int WM_RBUTTONDOWN = 0x0204;  
  4.     private const int WM_RBUTTONDBLCLK = 0x0206;  
  5.     private const int WM_CONTEXTMENU = 0x007b;  
  6.     private bool m_cancelPopup = false;  
  7.     public event EventHandler<ContextMenuStripNeededEventArgs> ContextMenuStripNeeded;  
  8.     protected override void WndProc(ref Message m)  
  9.     {  
  10.         if (m.Msg == WM_CONTEXTMENU)  // 处理菜单弹出消息, 注意:该消息前必然有鼠标右击消息  
  11.         {  
  12.             if (m_cancelPopup == true)  // 取消快捷菜单  
  13.             {  
  14.                 m_cancelPopup = false;  
  15.             }  
  16.             else  
  17.             {  
  18.                 base.WndProc(ref m);  
  19.             }  
  20.         }  
  21.         else  
  22.         {  
  23.             base.WndProc(ref m);  
  24.             // 先处理鼠标右击消息计算行/列号,并处理定制事件  
  25.             if (m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONDBLCLK)    
  26.             {  
  27.                 if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  28.                 {  
  29.                     int v = m.LParam.ToInt32();  
  30.                     int x = v & 0xffff;          // LParam 低16位是x坐标  
  31.                     int y = (v >> 16) & 0xffff;  // LParam 高16位是y坐标  
  32.                     int rowIndex = this.GetRowIndexAt(y);  
  33.                     int colIndex = this.GetColIndexAt(x);  
  34.                     ContextMenuStripNeededEventArgs e =  
  35.                         new ContextMenuStripNeededEventArgs(this.ContextMenuStrip, rowIndex, colIndex);  
  36.                     this.OnContextMenuStripNeeded(e);  // 调用事件方法  
  37.                     m_cancelPopup = e.Cancel;  // 获取事件返回参数  
  38.                 }  
  39.             }  
  40.         }  
  41.     }  
  42.     protected virtual void OnContextMenuStripNeeded(ContextMenuStripNeededEventArgs e)  
  43.     {  
  44.         if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null)  
  45.         {  
  46.             this.ContextMenuStripNeeded(this, e);  
  47.         }  
  48.     }  
  49.       
  50.     private int GetColIndexAt(int mouseLocation_X)  
  51.     {  
  52.         if (this.FirstDisplayedScrollingColumnIndex < 0)  
  53.         {  
  54.             return -1;  // no coumns.  
  55.         }  
  56.         if (this.RowHeadersVisible == true && mouseLocation_X <= this.RowHeadersWidth)  
  57.         {  
  58.             return -1;  
  59.         }  
  60.         int columnCount = this.Columns.Count;  
  61.         for(int index = 0; index < columnCount; index++)  // 搜索全部列,因为列可能ReOrder  
  62.         {  
  63.             if(this.Columns[index].Displayed == true)  // 必须是显示的  
  64.             {  
  65.                 Rectangle rect = this.GetColumnDisplayRectangle(index, true);  // 取该列的显示部分区域  
  66.                 if (rect.Left <= mouseLocation_X && mouseLocation_X < rect.Right)  
  67.                 {  
  68.                     return index;  
  69.                 }  
  70.             }  
  71.         }  
  72.         return -1;  
  73.     }  
  74.     private int GetRowIndexAt(int mouseLocation_Y)  
  75.     {  
  76.         if (this.FirstDisplayedScrollingRowIndex < 0)  
  77.         {  
  78.             return -1;  // no rows.  
  79.         }  
  80.         if (this.ColumnHeadersVisible == true && mouseLocation_Y <= this.ColumnHeadersHeight)  
  81.         {  
  82.             return -1;  
  83.         }  
  84.         int index = this.FirstDisplayedScrollingRowIndex;  
  85.         int displayedCount = this.DisplayedRowCount(true);  
  86.         for (int k = 1; k <= displayedCount; )  // 因为行不能ReOrder,故只需要搜索显示的行  
  87.         {  
  88.             if (this.Rows[index].Visible == true)  
  89.             {  
  90.                 Rectangle rect = this.GetRowDisplayRectangle(index, true);  // 取该区域的显示部分区域  
  91.                 if (rect.Top <= mouseLocation_Y && mouseLocation_Y < rect.Bottom)  
  92.                 {  
  93.                     return index;  
  94.                 }  
  95.                 k++;  // add when visible;  
  96.             }  
  97.             index++;  
  98.         }  
  99.         return -1;  
  100.     }  
  101. }  
  102. public class ContextMenuStripNeededEventArgs : EventArgs  
  103. {  
  104.     private bool m_cancel = false;  
  105.     private int m_rowIndex = -1;  
  106.     private int m_colIndex = -1;  
  107.     private ContextMenuStrip m_contextMenuStrip;  
  108.     public ContextMenuStripNeededEventArgs(ContextMenuStrip cms, int rowIndex, int colIndex)  
  109.     {  
  110.         m_contextMenuStrip = cms;  
  111.         m_rowIndex = rowIndex;  
  112.         m_colIndex = colIndex;  
  113.     }  
  114.     public ContextMenuStrip ContextMenuStrip  
  115.     {  
  116.         get { return m_contextMenuStrip; }  
  117.     }  
  118.     public int RowIndex  
  119.     {  
  120.         get { return m_rowIndex; }  
  121.     }  
  122.     public int ColIndex  
  123.     {  
  124.         get { return m_colIndex; }  
  125.     }  
  126.     public bool Cancel  
  127.     {  
  128.         get { return m_cancel; }  
  129.         set { m_cancel = value; }  
  130.     }  
  131. }  

更正说明

测试中还发现如下一些问题,本文的代码已做了修改。全新的源码(有部分变化)和示例请参考 全部源码与示例(C#2005,2009-2-14),特声明。

  • 不仅需要捕获鼠标右单击消息,还必须捕获鼠标右双击消息(WM_RBUTTONDBLCLK = 0x0206);
  • 计算列号时,必须考虑第一列有部分隐藏的宽度,累加时必须减去这部分;
  • 计算行/列号前,必须判断当前显示的行/列号是否为-1。这是DataGridView无行/列定义的情况;
  • 直接应用GetColumnDisplayRectangle(index, true)、GetRowDisplayRectangle(index, true)获取行或列单元的坐标;
0 0
原创粉丝点击