右键单击 datagridview 的上下文菜单

Posted

技术标签:

【中文标题】右键单击 datagridview 的上下文菜单【英文标题】:right click context menu for datagridview 【发布时间】:2010-12-15 15:49:52 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

您可以使用 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));

    

【讨论】:

正确!给你一个注释, var r = dataGridView1.HitTest(e.X, e.Y); r.RowIndex 的工作方式比使用鼠标或 currentMouseOverRow 更好 在 string.Format 中使用 .ToString() 是不必要的。 这个方法很旧:一个datagridview有一个属性:ContextMenu。操作员右键单击后,上下文菜单将立即打开。相应的 ContextMenuOpening 事件使您有机会根据当前单元格或选定的单元格来决定要显示的内容。查看其他答案之一 为了获得正确的屏幕坐标,您应该像这样打开上下文菜单:m.Show(dataGridView1.PointToScreen(e.Location)); 如何向菜单项添加功能?【参考方案2】:

虽然这个问题很老,但答案并不正确。上下文菜单在 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 的示例,如果您希望每行有不同的上下文菜单,您可以在其中指定显示哪个上下文菜单。

在这种情况下,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    
        
    

【讨论】:

+1 以获得全面的答案并考虑可访问性(以及回答 3 年前的问题) 同意,这比公认的要好得多(尽管它们中的任何一个都没有真正的问题)- 甚至更多的荣誉包括键盘支持,这是很多人似乎没有想到的。 很好的答案,提供了所有的灵活性:不同的上下文菜单取决于点击的内容。而正是 EXCEL 行为 我不喜欢这种方法,因为我的简单 DataGridView 不使用数据源或虚拟模式。 The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true. 如果有人将SelectionMode 用作FullRowSelect,则只是一个小提示。如果要更改已选择行中的单元格,则不必检查已选中的单元格。【参考方案3】:

DataGridView 上使用CellMouseDown 事件。从事件处理程序参数中,您可以确定单击了哪个单元格。使用DataGridView上的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);
        
    

【讨论】:

您也可以使用(sender as DataGridView)[e.ColumnIndex, e.RowIndex]; 更简单地调用单元格。 选中的答案在多个屏幕上无法正常工作,但此答案有效。【参考方案4】: 使用内置编辑器在表单上放置上下文菜单、命名、设置标题等 使用网格属性 ContextMenuStrip 将其链接到您的网格 为您的网格创建一个事件来处理CellContextMenuStripNeeded Event Args e 具有有用的属性e.ColumnIndexe.RowIndex

我相信e.RowIndex 是您想要的。

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

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

【讨论】:

我对此赞不绝口。其他答案对我来说是显而易见的,但我可以说有更多对上下文菜单的内置支持(而不仅仅是 DataGrid)。 这个是正确答案。 @ActualRandy,当用户点击实际的上下文菜单时,我如何获取标签?在 CellcontexMenustripNeeded 事件下,我有类似 contextMenuStrip1.Tag = e.RowIndex; 这个答案几乎就在那里,但是我建议您不要将上下文菜单链接到网格属性 ContextMenuStrip。而是在 CellContextMenuStripNeeded 事件处理程序中执行 if(e.RowIndex >= 0)e.ContextMenuStrip = yourContextMenuInstance; 这将意味着菜单仅在右键单击有效行时显示(即不在标题或空网格区域上) 就像对这个非常有用的答案的评论一样:CellContextMenuStripNeeded 仅在您的 DGV 绑定到数据源或其 VirtualMode 设置为 true 时才有效。在其他情况下,您需要在CellMouseDown 事件中设置该标签。为了安全起见,请在 MouseDown 事件处理程序中执行 DataGridView.HitTestInfo 以检查您是否在单元格上。【参考方案5】:

按照以下步骤操作:

    创建一个上下文菜单,如:

    用户需要右键单击该行才能获得此菜单。我们需要处理 _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 的最佳解决方案。 我如何知道点击了哪个菜单项? 检查此语句 -> selectedBiodataId = Convert.ToInt32(dgrdResults.Rows[e.RowIndex].Cells[1].Value);【参考方案6】:

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

【讨论】:

【参考方案7】:

对于上下文菜单的位置,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);
    

【讨论】:

以上是关于右键单击 datagridview 的上下文菜单的主要内容,如果未能解决你的问题,请参考以下文章

winform如何从DataGridView中从右键菜单获取一行数据

选择两个文件时,从右键单击 Windows 上下文菜单中隐藏一个选项

在窗口范围内限制/移动上下文菜单(右键单击)

右键单击之前不应用上下文菜单样式

如何在 Java Swing 中创建右键单击上下文菜单?

使用 PyGTK 的右键单击菜单(上下文菜单)