为datagridview右键单击上下文菜单

我在.NET Winform应用程序中有一个datagridview。 我想右键单击并popup一个菜单。 然后我想select复制,validation等东西

我如何让A)一个菜单popupB)find哪一行是右键单击。 我知道我可以使用selectedIndex,但我应该能够右键单击而不改变select什么? 现在我可以使用选定的索引,但是如果有一种方法来获取数据而不改变select的那么这将是有用的。

您可以使用CellMouseEnter和CellMouseLeave来跟踪鼠标当前hover的行号。

然后使用ContextMenu对象来显示为当前行定制的popup式菜单。

这是我的意思是一个快速和肮脏的例子…

private void dataGridView1_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { ContextMenu m = new ContextMenu(); m.MenuItems.Add(new MenuItem("Cut")); m.MenuItems.Add(new MenuItem("Copy")); m.MenuItems.Add(new MenuItem("Paste")); int currentMouseOverRow = dataGridView1.HitTest(eX,eY).RowIndex; if (currentMouseOverRow >= 0) { m.MenuItems.Add(new MenuItem(string.Format("Do something to row {0}", currentMouseOverRow.ToString()))); } m.Show(dataGridView1, new Point(eX, eY)); } } 

虽然这个问题很老,但答案并不恰当。 上下文菜单在DataGridView上有自己的事件。 行上下文菜单和单元格上下文菜单有一个事件。

这些答案不合适的原因是他们没有考虑到不同的操作scheme。 可访问性选项,远程连接或Metro / Mono / Web / WPF移植可能不起作用,并且键盘快捷方式将向右移动(Shift + F10或上下文菜单键)。

单击鼠标右键的单元格select必须手动处理。 显示上下文菜单不需要处理,因为这是由UI处理的。

这完全模仿了Microsoft Excel使用的方法。 如果一个单元格是选定范围的一部分,则单元格select不会改变, CurrentCell也不会改变。 如果不是,旧的范围被清除并且该单元被选中并变成CurrentCell

如果您对此不清楚, CurrentCell就是您按下箭头键时键盘所对焦的位置。 Selected是否它是SelectedCells一部分。 上下文菜单将显示在用户界面处理的右键单击。

 private void dgvAccount_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { if (e.ColumnIndex != -1 && e.RowIndex != -1 && e.Button == System.Windows.Forms.MouseButtons.Right) { DataGridViewCell c = (sender as DataGridView)[e.ColumnIndex, e.RowIndex]; if (!c.Selected) { c.DataGridView.ClearSelection(); c.DataGridView.CurrentCell = c; c.Selected = true; } } } 

键盘快捷键默认不显示上下文菜单,所以我们必须添加它们。

 private void dgvAccount_KeyDown(object sender, KeyEventArgs e) { if ((e.KeyCode == Keys.F10 && e.Shift) || e.KeyCode == Keys.Apps) { e.SuppressKeyPress = true; DataGridViewCell currentCell = (sender as DataGridView).CurrentCell; if (currentCell != null) { ContextMenuStrip cms = currentCell.ContextMenuStrip; if (cms != null) { Rectangle r = currentCell.DataGridView.GetCellDisplayRectangle(currentCell.ColumnIndex, currentCell.RowIndex, false); Point p = new Point(rX + r.Width, rY + r.Height); cms.Show(currentCell.DataGridView, p); } } } } 

我已经重新编写了这个代码来静态工作,所以你可以将它们复制并粘贴到任何事件中。

关键是使用CellContextMenuStripNeeded因为这会给你上下文菜单。

下面是一个使用CellContextMenuStripNeeded的例子,您可以指定要显示哪个上下文菜单,如果您想每行有不同的内容。

在这种情况下, MultiSelectTrueSelectionModeFullRowSelect 。 这仅仅是为了举例,而不是限制。

 private void dgvAccount_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e) { DataGridView dgv = (DataGridView)sender; if (e.RowIndex == -1 || e.ColumnIndex == -1) return; bool isPayment = true; bool isCharge = true; foreach (DataGridViewRow row in dgv.SelectedRows) { if ((string)row.Cells["P/C"].Value == "C") isPayment = false; else if ((string)row.Cells["P/C"].Value == "P") isCharge = false; } if (isPayment) e.ContextMenuStrip = cmsAccountPayment; else if (isCharge) e.ContextMenuStrip = cmsAccountCharge; } private void cmsAccountPayment_Opening(object sender, CancelEventArgs e) { int itemCount = dgvAccount.SelectedRows.Count; string voidPaymentText = "&Void Payment"; // to be localized if (itemCount > 1) voidPaymentText = "&Void Payments"; // to be localized if (tsmiVoidPayment.Text != voidPaymentText) // avoid possible flicker tsmiVoidPayment.Text = voidPaymentText; } private void cmsAccountCharge_Opening(object sender, CancelEventArgs e) { int itemCount = dgvAccount.SelectedRows.Count; string deleteChargeText = "&Delete Charge"; //to be localized if (itemCount > 1) deleteChargeText = "&Delete Charge"; //to be localized if (tsmiDeleteCharge.Text != deleteChargeText) // avoid possible flicker tsmiDeleteCharge.Text = deleteChargeText; } private void tsmiVoidPayment_Click(object sender, EventArgs e) { int paymentCount = dgvAccount.SelectedRows.Count; if (paymentCount == 0) return; bool voidPayments = false; string confirmText = "Are you sure you would like to void this payment?"; // to be localized if (paymentCount > 1) confirmText = "Are you sure you would like to void these payments?"; // to be localized voidPayments = (MessageBox.Show( confirmText, "Confirm", // to be localized MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2 ) == DialogResult.Yes); if (voidPayments) { // SQLTransaction Start foreach (DataGridViewRow row in dgvAccount.SelectedRows) { //do Work } } } private void tsmiDeleteCharge_Click(object sender, EventArgs e) { int chargeCount = dgvAccount.SelectedRows.Count; if (chargeCount == 0) return; bool deleteCharges = false; string confirmText = "Are you sure you would like to delete this charge?"; // to be localized if (chargeCount > 1) confirmText = "Are you sure you would like to delete these charges?"; // to be localized deleteCharges = (MessageBox.Show( confirmText, "Confirm", // to be localized MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2 ) == DialogResult.Yes); if (deleteCharges) { // SQLTransaction Start foreach (DataGridViewRow row in dgvAccount.SelectedRows) { //do Work } } } 

DataGridView上使用CellMouseDown事件。 从事件处理参数中,您可以确定哪个单元被点击。 使用DataGridView上的PointToClient()方法可以确定指向DataGridView的指针的相对位置,这样就可以在正确的位置popup菜单。

DataGridViewCellMouseEvent参数只是为您提供相对于您单击的单元格的XY ,这不便于用来popup上下文菜单。)

这是我用来获取鼠标位置的代码,然后调整DataGridView的位置:

 var relativeMousePosition = DataGridView1.PointToClient(Cursor.Position); this.ContextMenuStrip1.Show(DataGridView1, relativeMousePosition); 

整个事件处理程序如下所示:

 private void DataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { // Ignore if a column or row header is clicked if (e.RowIndex != -1 && e.ColumnIndex != -1) { if (e.Button == MouseButtons.Right) { DataGridViewCell clickedCell = (sender as DataGridView).Rows[e.RowIndex].Cells[e.ColumnIndex]; // Here you can do whatever you want with the cell this.DataGridView1.CurrentCell = clickedCell; // Select the clicked cell, for instance // Get mouse position relative to the vehicles grid var relativeMousePosition = DataGridView1.PointToClient(Cursor.Position); // Show the context menu this.ContextMenuStrip1.Show(DataGridView1, relativeMousePosition); } } } 
  • 在你的表单上放置一个上下文菜单,命名它,使用内置的编辑器设置字幕等
  • 使用网格属性ContextMenuStrip将其链接到您的网格
  • 对于你的网格,创build一个事件来处理CellContextMenuStripNeeded
  • 事件参数e具有有用的属性e.ColumnIndexe.RowIndex

我相信e.RowIndex就是你所要求的。

build议:当用户导致事件CellContextMenuStripNeeded触发时,使用e.RowIndex从网格中获取数据,比如ID。 将ID存储为菜单事件的标记项。

现在,当用户实际点击你的菜单项时,使用发送者属性来获取标签。 使用包含您的ID的标签来执行您所需的操作。

只需将一个ContextMenu或ContextMenuStrip组件拖到您的表单中,然后对其进行可视化devise,然后将其分配给所需控件的ContextMenu或ContextMenuStrip属性。

对于上下文菜单的位置,y发现我需要的是相对于DataGridView的问题,并且我需要使用的事件给出了相对于单击的单元格的位置。 我还没有find更好的解决scheme,所以我在commons类中实现了这个function,所以我从任何需要的地方调用它。

这是相当的testing和运作良好。 希望对你有帮助。

  /// <summary> /// When DataGridView_CellMouseClick ocurs, it gives the position relative to the cell clicked, but for context menus you need the position relative to the DataGridView /// </summary> /// <param name="dgv">DataGridView that produces the event</param> /// <param name="e">Event arguments produced</param> /// <returns>The Location of the click, relative to the DataGridView</returns> public static Point PositionRelativeToDataGridViewFromDataGridViewCellMouseEventArgs(DataGridView dgv, DataGridViewCellMouseEventArgs e) { int x = eX; int y = eY; if (dgv.RowHeadersVisible) x += dgv.RowHeadersWidth; if (dgv.ColumnHeadersVisible) y += dgv.ColumnHeadersHeight; for (int j = 0; j < e.ColumnIndex; j++) if (dgv.Columns[j].Visible) x += dgv.Columns[j].Width; for (int i = 0; i < e.RowIndex; i++) if (dgv.Rows[i].Visible) y += dgv.Rows[i].Height; return new Point(x, y); }