右键单击datagridview的上下文菜单


116

我在.NET winform应用程序中有一个datagridview。我想右键单击一行,然后弹出一个菜单。然后我想选择诸如复制,验证等内容

我如何使A)弹出菜单B)找到右键单击的行。我知道我可以使用selectedIndex,但是我应该能够右键单击而不更改选择的内容?现在,我可以使用选择的索引,但是如果有一种方法可以在不更改选择的内容的情况下获取数据,那将很有用。

Answers:


143

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

然后使用ContextMenu对象显示为当前行定制的弹出菜单。

这是我的意思的一个简单而肮脏的例子...

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(e.X,e.Y).RowIndex;

        if (currentMouseOverRow >= 0)
        {
            m.MenuItems.Add(new MenuItem(string.Format("Do something to row {0}", currentMouseOverRow.ToString())));
        }

        m.Show(dataGridView1, new Point(e.X, e.Y));

    }
}

6
正确!并为您提供注释,var r = dataGridView1.HitTest(eX,eY); r.RowIndex工作得更好,然后使用鼠标或currentMouseOverRow

3
在string.Format中使用.ToString()是不必要的。
MS

19
此方法很旧:datagridview具有一个属性:ContextMenu。操作员单击鼠标右键,将打开上下文菜单。相应的ContextMenuOpening事件使您有机会根据当前单元格或选定的单元格决定显示什么。查看其他答案之一
Harald Coppoolse 2014年

4
为了获得正确的屏幕坐标,您应该打开如下上下文菜单:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes 2014年

如何为菜单项添加功能?
Alpha Gabriel V. Timbol '16

89

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

这些答案不正确的原因是它们没有考虑不同的操作方案。辅助功能选项,远程连接或Metro / Mono / Web / WPF移植可能不起作用,并且键盘快捷键将无法正常显示(Shift + F10或上下文菜单键)。

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

这完全模仿了Microsoft Excel使用的方法。如果某个单元格是所选范围的一部分,则该单元格的选择不会更改,也不会更改CurrentCell。如果不是,则清除旧范围,并选择该单元格并变为CurrentCell

如果您对此不清楚,CurrentCell则当您按箭头键时是键盘的焦点所在。Selected是否是的一部分SelectedCells。右键单击将显示上下文菜单,由UI处理。

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(r.X + r.Width, r.Y + r.Height);
                cms.Show(currentCell.DataGridView, p);
            }
        }
    }
}

我已经对该代码进行了重新设计以使其能够静态工作,因此您可以将其复制并粘贴到任何事件中。

关键是要使用,CellContextMenuStripNeeded因为这将为您提供上下文菜单。

这是一个使用示例CellContextMenuStripNeeded,您可以在其中指定要在每行中使用不同的上下文菜单来显示。

在这种情况下,MultiSelectis TrueSelectionModeis FullRowSelect。这仅是示例,而不是限制。

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    
        }
    }
}

5
+1以获得全面答案并考虑可访问性(并回答3年之久的问题)
gt

3
同意,这比公认的要好得多(尽管它们之间没有任何真正的错误)-甚至包括键盘支持在内的更多赞誉,很多人似乎都没有想到。
理查德·莫斯

2
好的答案,提供了所有的灵活性:取决于单击内容的不同上下文菜单。而正是EXCEL行为
Harald Coppoolse 2014年

2
我不喜欢这种方法,因为使用我简单的DataGridView时,我不会使用数据源或虚拟模式。 The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen

47

CellMouseDown上使用事件DataGridView。从事件处理程序参数中,您可以确定单击了哪个单元格。使用PointToClient()DataGridView上的方法,您可以确定指向DataGridView的指针的相对位置,因此可以在正确的位置弹出菜单。

(该DataGridViewCellMouseEvent参数仅提供相对于您单击的单元格的XY,这并不容易使用来弹出上下文菜单。)

这是我用来获取鼠标位置,然后调整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);
        }
    }
}

1
您也可以使用(sender as DataGridView)[e.ColumnIndex, e.RowIndex];更简单的方式调用该单元格。
Qsiris

选中的答案无法在多个屏幕上正常工作,但是此答案有效。
Furkan Ekinci

45
  • 使用内置编辑器将上下文菜单放在表单上,​​命名,设置标题等
  • 使用grid属性将其链接到网格 ContextMenuStrip
  • 对于您的网格,创建一个事件来处理 CellContextMenuStripNeeded
  • 事件Args e具有有用的属性e.ColumnIndexe.RowIndex

我相信这e.RowIndex就是您要的。

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

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


5
我不能对此表示足够的赞同。其他答案对我来说很明显,但我可以断言,它对上下文菜单有更多内置支持(而不仅仅是对DataGrid)。是正确的答案。
乔纳森·伍德

1
@ActualRandy,当用户单击实际的上下文菜单时如何获取标签?在CellcontexMenustripNeeded事件下,我有类似的内容,例如contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo 2014年

2
这个答案几乎存在,但是我建议您不要将上下文菜单链接到网格属性ContextMenuStrip。取而代之的是内部CellContextMenuStripNeeded处理事件做if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}这将意味着菜单仅在右击显示有效行(即不是在标题或空的网格区域)
詹姆斯小号

就像对这个非常有用的答案的评论:CellContextMenuStripNeeded仅当DGV绑定到数据源或其VirtualMode设置为true时,该方法才有效。在其他情况下,您将需要在CellMouseDown事件中设置该标签。为了安全起见,请DataGridView.HitTestInfo在MouseDown事件处理程序中执行,以检查您是否在单元格上。
LocEngineer

6

只需将ContextMenu或ContextMenuStrip组件拖到窗体中并进行可视化设计,然后将其分配给所需控件的ContextMenu或ContextMenuStrip属性即可。


4

按照步骤:

  1. 创建一个上下文菜单,如: 样本上下文菜单

  2. 用户需要右键单击该行才能获得此菜单。我们需要处理_MouseClick事件和_CellMouseDown事件。

selectedBiodataid是包含所选行信息的变量。

这是代码:

private void dgrdResults_MouseClick(object sender, MouseEventArgs e)
{   
    if (e.Button == System.Windows.Forms.MouseButtons.Right)
    {                      
        contextMenuStrip1.Show(Cursor.Position.X, Cursor.Position.Y);
    }   
}

private void dgrdResults_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
    //handle the row selection on right click
    if (e.Button == MouseButtons.Right)
    {
        try
        {
            dgrdResults.CurrentCell = dgrdResults.Rows[e.RowIndex].Cells[e.ColumnIndex];
            // Can leave these here - doesn't hurt
            dgrdResults.Rows[e.RowIndex].Selected = true;
            dgrdResults.Focus();

            selectedBiodataId = Convert.ToInt32(dgrdResults.Rows[e.RowIndex].Cells[1].Value);
        }
        catch (Exception)
        {

        }
    }
}

输出将是:

最终输出


3

对于上下文菜单的位置,y发现了一个问题,我需要将其相对于DataGridView,并且我需要使用的事件给出了相对于所单击的单元格的位置。我没有找到更好的解决方案,因此我在commons类中实现了此功能,因此我可以从任何需要的地方调用它。

它已经过测试并且运行良好。希望对你有帮助。

    /// <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 = e.X;
        int y = e.Y;
        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);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.