134

I have a datagridview in a .NET winform app. I would like to rightclick on a row and have a menu pop up. Then i would like to select things such as copy, validate, etc

How do i make A) a menu pop up B) find which row was right clicked. I know i could use selectedIndex but i should be able to right click without changing what is selected? right now i could use selected index but if there is a way to get the data without changing what is selected then that would be useful.

kodkod
  • 1,556
  • 4
  • 21
  • 43

8 Answers8

158

You can use the CellMouseEnter and CellMouseLeave to track the row number that the mouse is currently hovering over.

Then use a ContextMenu object to display you popup menu, customised for the current row.

Here's a quick and dirty example of what I mean...

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

    }
}
RLH
  • 15,230
  • 22
  • 98
  • 182
Stuart Helwig
  • 9,318
  • 8
  • 51
  • 67
  • 6
    Correct! and a note for you, var r = dataGridView1.HitTest(e.X, e.Y); r.RowIndex works WAY BETTER then using mouse or currentMouseOverRow –  Nov 12 '09 at 03:24
  • 3
    using .ToString() in string.Format is unnecessarily. – ms_devel Nov 21 '13 at 18:57
  • 27
    This method is old: a datagridview has a property: ContextMenu. The context menu will be opened as soon as the operator right clicks. The corresponding ContextMenuOpening event gives you the opportunity to decide what to show depending on the current cell or selected cells. See one of the other answers – Harald Coppoolse Nov 11 '14 at 08:43
  • 6
    In order to get the right screen coordiantes you should open the context menu like this: `m.Show(dataGridView1.PointToScreen(e.Location));` – Olivier Jacot-Descombes Nov 25 '14 at 17:53
  • how do i add a function to the menuitems? – Alpha Gabriel V. Timbol Apr 10 '16 at 11:49
  • @HaraldCoppoolse I tried adding a ContextMenu to a DataGridView, but it plain didn't do anything on right-click. Heck, a right-click didn't even select the right-clicked row, so I'd have to do that manually anyway. – Nyerguds Dec 10 '16 at 06:27
  • I didn't mean right click during design. During design you'll have to design a ContextMenuStrip via Toolbox - Menus; Then you can set property Control.ContextMenuStrip to this ContextMenuStrip, in fact you can do this with every Control. Now if during run time you right click on the Control you'll see the designed ContextMenuStrip. If you want you can initialize or change the appearance of the ContextMenuStrip before it is opened by registering to the event ToolStripDropDown.Opening (in designer: double click on the added contextmenu) – Harald Coppoolse Dec 12 '16 at 07:55
  • how can i get name of the column on which right click has been performed? I mean what will happen in case user perform right click over column names instead of cell? – Manish Jain May 19 '19 at 07:26
  • How do I know which menu item was clicked, since there are multiple menu items? – Si8 Mar 31 '21 at 16:45
110

While this question is old, the answers aren't proper. Context menus have their own events on DataGridView. There is an event for row context menu and cell context menu.

The reason for which these answers aren't proper is they do not account for different operation schemes. Accessibility options, remote connections, or Metro/Mono/Web/WPF porting might not work and keyboard shortcuts will down right fail (Shift+F10 or Context Menu key).

Cell selection on right mouse click has to be handled manually. Showing the context menu does not need to be handled as this is handled by the UI.

This completely mimics the approach used by Microsoft Excel. If a cell is part of a selected range, the cell selection doesn't change and neither does CurrentCell. If it isn't, the old range is cleared and the cell is selected and becomes CurrentCell.

If you are unclear on this, CurrentCell is where the keyboard has focus when you press the arrow keys. Selected is whether it is part of SelectedCells. The context menu will show on right click as handled by the 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;
        }
    }
}

Keyboard shortcuts do not show the context menu by default, so we have to add them in.

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

I've reworked this code to work statically, so you can copy and paste them into any event.

The key is to use CellContextMenuStripNeeded since this will give you the context menu.

Here's an example using CellContextMenuStripNeeded where you can specify which context menu to show if you want to have different ones per row.

In this context MultiSelect is True and SelectionMode is FullRowSelect. This is just for the example and not a limitation.

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    
        }
    }
}
Arvo Bowen
  • 4,524
  • 6
  • 51
  • 109
ShortFuse
  • 5,970
  • 3
  • 36
  • 36
  • 7
    +1 for comprehensive answer and for considering accesibility (and for answering a 3 yr old question) – g t Dec 13 '12 at 12:45
  • 4
    Agreed, this is much better than the accepted (although there's nothing really wrong with any of them) - and even more kudos for including keyboard support, something so many people seem to just not think of. – Richard Moss Oct 25 '14 at 09:22
  • 2
    Great answer, gives all flexibility: different context menus depending on what is clicked. And exactly the EXCEL behaviour – Harald Coppoolse Nov 11 '14 at 09:32
  • 3
    I'm not a fan of this method because with my simple DataGridView I don't use a datasource or virtualmode. `The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.` – Arvo Bowen Dec 31 '16 at 23:40
  • Just a small hint if someone is using `SelectionMode` as `FullRowSelect`. If you want to change cell in already selected row you have to not check the cell if its selected. – PetoMPP Jan 11 '22 at 19:53
50
  • Put a context menu on your form, name it, set captions etc. using the built-in editor
  • Link it to your grid using the grid property ContextMenuStrip
  • For your grid, create an event to handle CellContextMenuStripNeeded
  • The Event Args e has useful properties e.ColumnIndex, e.RowIndex.

I believe that e.RowIndex is what you are asking for.

Suggestion: when user causes your event CellContextMenuStripNeeded to fire, use e.RowIndex to get data from your grid, such as the ID. Store the ID as the menu event's tag item.

Now, when user actually clicks your menu item, use the Sender property to fetch the tag. Use the tag, containing your ID, to perform the action you need.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
ActualRandy
  • 501
  • 4
  • 3
  • 6
    I can't upvote this enough. The other answers were obvious to me but I could tell there was more built-in support for context menus (and not just for DataGrid). *This* is the correct answer. – Jonathan Wood Sep 21 '14 at 18:05
  • 1
    @ActualRandy, how do i get the tag when the user clicks the actual context menu? under the CellcontexMenustripNeeded event, i have something like so contextMenuStrip1.Tag = e.RowIndex; – Edwin O. Dec 16 '14 at 17:34
  • 4
    This answer is almost there, however I'd suggest you DO NOT link the context menu to the grid property ContextMenuStrip. Instead inside the `CellContextMenuStripNeeded` event handler do `if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}` This will mean the menu is only shown on right clicking a valid row, (ie not on a heading or empty grid area) – James S Jun 18 '15 at 15:50
  • Just as a comment to this very helpful answer: `CellContextMenuStripNeeded` only works if your DGV is bound to a datasource or if its VirtualMode is set to true. In other cases you will need to set that tag in the `CellMouseDown` event. To be on the safe side there, perform a `DataGridView.HitTestInfo` in the MouseDown event handler to check you are on a cell. – LocEngineer Oct 20 '17 at 15:13
49

Use the CellMouseDown event on the DataGridView. From the event handler arguments you can determine which cell was clicked. Using the PointToClient() method on the DataGridView you can determine the relative position of the pointer to the DataGridView, so you can pop up the menu in the correct location.

(The DataGridViewCellMouseEvent parameter just gives you the X and Y relative to the cell you clicked, which isn't as easy to use to pop up the context menu.)

This is the code I used to get the mouse position, then adjust for the position of the DataGridView:

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

The entire event handler looks like this:

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);
        }
    }
}
Matt
  • 3,676
  • 3
  • 34
  • 38
15

Follow the steps:

  1. Create a context menu like: Sample context menu

  2. User needs to right click on the row to get this menu. We need to handle the _MouseClick event and _CellMouseDown event.

selectedBiodataid is the variable that contains the selected row information.

Here is the code:

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

        }
    }
}

and the output would be:

Final output

Furkan Ekinci
  • 2,472
  • 3
  • 29
  • 39
Kshitij Jhangra
  • 577
  • 7
  • 14
  • This is the best solution so far for the latest version of Visual Studio. – Red Magda Feb 13 '21 at 03:06
  • How do I know which menu item entry was clicked? – Si8 Mar 31 '21 at 16:43
  • check this statement -> selectedBiodataId = Convert.ToInt32(dgrdResults.Rows[e.RowIndex].Cells[1].Value); – Kshitij Jhangra Apr 04 '21 at 15:44
  • The _MouseClick is not necessary, its contents can be transfered to the _CellMouseDown. More important, to retrieve what item hes been clicked, you must use an event handler belonging to the StripMenu(Item), thus a global variable will be useful to get the datagridviewCell content before. – Fredy Nov 15 '22 at 00:31
7

Simply drag a ContextMenu or ContextMenuStrip component into your form and visually design it, then assign it to the ContextMenu or ContextMenuStrip property of your desired control.

Captain Comic
  • 15,744
  • 43
  • 110
  • 148
3

For the position for the context menu, y found the problem that I needed a it to be relative to the DataGridView, and the event I needed to use gives the poistion relative to the cell clicked. I haven't found a better solution so I implemented this function in the commons class, so I call it from wherever I need.

It's quite tested and works well. I Hope you find it useful.

    /// <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);
    }
Gers0n
  • 31
  • 1
1

There is simple answer to this topic and that is to use CellMouseDown

  1. After designing your ContextMenu
// somewhere in your code

ContextMenu cm = new ContextMenu();
cm.MenuItems.Add(new MenuItem("Option1"));
cm.MenuItems.Add(new MenuItem("Option2"));
  1. Assign it to the DataGridView
myDataView.ContextMenu = cm;
  1. Get Data from the CLICKED cell without changing the Selected one
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}";
    }
}
Atrin Noori
  • 311
  • 3
  • 12