Custom an event for DataGridView.ContextMenuStrip

download source code and demo(C# 2005) -- 25.3KB
Custom an event for DataGridView.ContextMenuStrip_第1张图片

Introduction


DataGridView's RowContextMenuStripNeeded event is useful to display a shortcut menu determined by a row's current state or the values it contains, its basic usage steps are:

  • Design a ContextMenuStrip menu control, then assign it to DataGridView.RowTemplate.ContextMenuStrip.
  • Capture DataGridView's RowContextMenuStripNeeded event,obtain the RowIndex and ContextMenuStrip object, and then realize self-defined event handler.


However, RowContextMenuStripNeeded events occurs only when the DataGridView DataSource is set or its VirtualMode is true.(reference MSDN: DataGridView.RowContextMenuStripNeeded Event) The RowContextMenuStripNeeded event does not occur too if DataGridView has no rows/columns or the right click outside valid rows such as ColumnHeaders/RowHeaders, empty area, etc..The another event CellContextMenuStripNeeded has the similar limitations and shortcomings.

This article introduces a solution for custuming an event for DataGridView.ContextMenuStrip property, which has the features like xxxContextMenuStripNeeded events. The main ideas are:

  • Override DataGridView.WndProc(ref m) method, capture mouse right click or right double click messages.
  • Compute the RowIndex and ColumnIndex according to current mouse location.
  • Custom a ContextMenuStripNeeded event with args RowIndex, ColumnIndex and ContextMenuStrip.

Key points

1) Capture mouse right click messages in WndProc()


Messages associated with ContextMenuStrip must be captured, such as mouse right click and mouse right double click, then the mouse location will be calculated according to the click message.
protected override void WndProc(ref Message m) { base.WndProc(ref m); // handle mouse right click message, compute row/col index, call event handler. if (m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONDBLCLK) { if (this.ContextMenuStrip != null && this.ContextMenuStripNeeded != null) { // The low-order word specifies the horizontal position of the cursor. // The high-order word specifies the vertical position of the cursor. int v = m.LParam.ToInt32(); int x = v & 0xffff; // low-order word. int y = (v >> 16) & 0xffff; // high-order word. int rowIndex = this.GetRowIndexAt(y); int columnIndex = this.GetColumnIndexAt(x); DataGridViewCellContextMenuStripNeededEventArgs e = new DataGridViewCellContextMenuStripNeededEventArgs(columnIndex, rowIndex); e.ContextMenuStrip = this.ContextMenuStrip; // set ContextMenuStrip property this.OnContextMenuStripNeeded(e); // call event handlers subscribed by clients. } } } Here are some notes on the codes above:

  • Mouse rigth click message(WM_RBUTTONDOWN=0x0204) must be captured, and the right double click message(WM_RBUTTONDBLCL=0x0206) must be captuered too.
  • The location of mouse right click can be calculated according to WndProc's parameter m.LParam, whose low-order word is the x position and high-order word is the y position.(reference MSDN: WM_RBUTTONDOWN Notification)
  • The customized OnContextMenuStripNeeded() method will invoke the event handlers realized in client programs(applications).
  • Customized event ContextMenuStripNeeded has the same delegate as CellContextMenuStripNeeded, and they have the same event args class DataGridViewCellContextMenuStripNeededEventArgs. Obviously, the customized event is defined as below:

  • public event EventHandler<DataGridViewCellContextMenuStripNeededEventArgs> ContextMenuStripNeeded;

2) Compute ColumnIndex and RowIndex


Some cases must be considered carefully to compute ColumnIndex and RowIndex at current mouse click location whether columns/rows are invisible, columns are reordered, rows/columns are partially displayed, and so on. Below are codes to compute ColumnIndex:
private int GetColumnIndexAt(int mouseLocation_X) { if (this.FirstDisplayedScrollingColumnIndex < 0) { return -1; // no columns defined. } if (this.RowHeadersVisible == true && mouseLocation_X <= this.RowHeadersWidth) { return -1; // at rowheaders } int columnCount = this.Columns.Count; for(int index = 0; index < columnCount; index++) { if(this.Columns[index].Displayed == true) { Rectangle rect = this.GetColumnDisplayRectangle(index, true); // partial rectangle only. if (rect.Left <= mouseLocation_X && mouseLocation_X < rect.Right) { return index; } } } return -1; } Since DataGridViews.Columns can be reordered or be invisible, and DataGridViewColumn.Index may be different to its DisplayIndex, so it must scan all columns to find the displayed column and obtain its Left and Right margin to judge whether it contains the mouse click location.

The method GetColumnDisplayRectangle(int, bool) will get the rectangle of an assigned column from top to bottom of displayed region in DataGridView. Obviously, if the x position of mouse click location is between Left and Right of this region, the wanted Column.Index is got.

Below are codes to compute RowIndex:
private int GetRowIndexAt(int mouseLocation_Y) { if (this.FirstDisplayedScrollingRowIndex < 0) { return -1; // no rows defined. } if (this.ColumnHeadersVisible == true && mouseLocation_Y <= this.ColumnHeadersHeight) { return -1; // at columnheaders. } int index = this.FirstDisplayedScrollingRowIndex; int displayedCount = this.DisplayedRowCount(true); for (int k = 1; k <= displayedCount; ) { if (this.Rows[index].Visible == true) { Rectangle rect = this.GetRowDisplayRectangle(index, true); // partial rectangle only if (rect.Top <= mouseLocation_Y && mouseLocation_Y < rect.Bottom) { return index; } k++; // add when visible; } index++; } return -1; } It need only to scan the continuous displayed rows, and the method GetRowDisplayRectangle(int, bool) returns the rectangle of assigned row, which is used to judge whether y position of mouse location is in.

Usually, index value -1 denotes that location outside the valid rows and columns, such as columnheaders or rowheaders, empty region at the bottom or right.

Conclusion


DataGridView has two useful contextmenu events RowContextMenuStripNeeded and CellContextMenuStripNeeded, but it has no event for ContextMenuStrip property. The customized event ContextMenuStripNeeded introduced here behaves same as CellContextMenuStripNeeded, nevertheless it has no limitaions such as DataSource must be not set or VirtualMode must be true for DataGridView.

Usage, tests, comments and suggestions are welcome.

你可能感兴趣的:(Custom an event for DataGridView.ContextMenuStrip)