DataGridView的右键上下文菜单

134

我有一个.NET winform应用程序中的DataGridView。 我希望右键单击一行时弹出一个菜单,然后我可以选择复制、验证等事项。

如何做到:A) 弹出一个菜单;B) 找到右键单击的行。我知道可以使用selectedIndex,但我应该能够右键单击而不更改所选内容吗? 目前我可以使用selectedIndex,但如果有一种方法可以在不更改所选内容的情况下获取数据,则会很有用。

8个回答

159
您可以使用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(e.X, e.Y); 中的 r.RowIndex 要比使用鼠标或 currentMouseOverRow 更为有效。 - user34537
3
在string.Format中使用.ToString()是不必要的。 - ms_devel
27
该方法已经过时:一个datagridview有一个属性ContextMenu。操作员右键单击时,上下文菜单将被打开。相应的ContextMenuOpening事件让您有机会根据当前单元格或选择的单元格决定要显示什么。请参阅其他答案之一。 - Harald Coppoolse
6
为了获取正确的屏幕坐标,您应该像这样打开上下文菜单:m.Show(dataGridView1.PointToScreen(e.Location)); - Olivier Jacot-Descombes
如何将函数添加到菜单项? - Alpha Gabriel V. Timbol
显示剩余4条评论

110

虽然这个问题很旧,但回答并不正确。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的示例,您可以指定每行要显示哪个上下文菜单。

在此上下文中,MultiSelectTrue,而SelectionModeFullRowSelect。这只是示例,不是限制。

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

7
从英语翻译成中文。请只返回翻译后的文本:+1表示回答全面,考虑到易懂性(并为回答3年前的问题而加分) - g t
4
同意,这比已被接受的(虽然它们都没有什么问题)要好得多——并且更值得赞赏的是包括了键盘支持,这是许多人似乎只是没有考虑到的东西。 - Richard Moss
2
非常好的答案,提供了所有的灵活性:根据所点击的内容提供不同的上下文菜单。而且正是 EXCEL 的行为。 - Harald Coppoolse
3
我不喜欢这种方法,因为我使用简单的DataGridView,不使用数据源或虚拟模式。只有当设置了DataGridView控件的DataSource属性或其VirtualMode属性为true时,CellContextMenuStripNeeded事件才会发生。 - Arvo Bowen
如果有人正在使用 FullRowSelect 作为 SelectionMode,这里有一个小提示。如果你想要更改已选择行中的单元格,你必须不检查该单元格是否被选中。 - PetoMPP

51
  • 在你的表单上放置一个上下文菜单,命名它,使用内置编辑器设置标题等信息
  • 使用网格属性ContextMenuStrip将其链接到您的网格
  • 为您的网格创建一个事件来处理CellContextMenuStripNeeded
  • 事件参数e有有用的属性e.ColumnIndexe.RowIndex

我认为e.RowIndex是你所要求的。

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

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


6
我赞不绝口。其他回答对我来说很明显,但我可以看出对于上下文菜单(不仅限于DataGrid),有更多内置支持。这才是正确的答案。 - Jonathan Wood
1
@ActualRandy,当用户单击实际上下文菜单时,我该如何获取标签? 在CellcontexMenustripNeeded事件下,我有以下代码: contextMenuStrip1.Tag = e.RowIndex; - Edwin O.
4
这个答案差不多了,但我建议你不要将上下文菜单链接到网格属性ContextMenuStrip。相反,在CellContextMenuStripNeeded事件处理程序中,执行if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}。这意味着菜单只会在右键单击有效行时显示(即不会在标题或空网格区域上显示)。 - James S
作为对这个非常有帮助的答案的评论:CellContextMenuStripNeeded 只在你的 DGV 绑定了数据源或者它的 VirtualMode 被设置为 true 时才有效。在其他情况下,你需要在 CellMouseDown 事件中设置该标签。为了保险起见,在 MouseDown 事件处理程序中执行 DataGridView.HitTestInfo 来检查你是否在单元格上。 - LocEngineer

49
使用 DataGridView 上的 CellMouseDown 事件。通过事件处理程序参数可以确定所单击的单元格。使用 PointToClient() 方法,可以确定指针相对于 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

15
请按照以下步骤操作:
  1. 创建一个上下文菜单,如下图所示: Sample context menu

  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)
        {

        }
    }
}

并且输出将会是: 最终输出

这是目前为止最适合最新版本的Visual Studio的最佳解决方案。 - Red Magda
我如何知道点击了哪个菜单项? - Si8
请检查以下语句 -> selectedBiodataId = Convert.ToInt32(dgrdResults.Rows[e.RowIndex].Cells[1].Value); - Kshitij Jhangra
_MouseClick不是必需的,它的内容可以转移到_CellMouseDown中。更重要的是,为了检索已单击的项目,您必须使用属于StripMenu(Item)的事件处理程序,因此在获取datagridviewCell内容之前使用全局变量将非常有用。 - Fredy

7

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


3

在上下文菜单的位置问题上,我发现需要相对于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);
    }

1

这个主题有一个简单的答案,就是使用CellMouseDown

  1. 设计好你的ContextMenu
// somewhere in your code

ContextMenu cm = new ContextMenu();
cm.MenuItems.Add(new MenuItem("Option1"));
cm.MenuItems.Add(new MenuItem("Option2"));

将其分配给 DataGridView
myDataView.ContextMenu = cm;
  1. 在不改变Selected单元格的情况下,获取所点击单元格的数据
private void myDataView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
         string myData = myDataView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();

         // Now ADD the captured DATA to the ContextMenu
         cm.MenuItems.Add(new MenuItem("myData"));

         // OR if you are doing it by the designer and YOU ALREADY have an Item
         // you can simply change the name of it in the designer page to your
         // desired one and then just change the Text of it
         MenuItem_BTN.Text = "$Properties of {myData}";
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接