download source code and demo(C# 2005) -- 25.3KB
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:
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:
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:
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.
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.