right click context menu for datagridview

C#WinformsDatagridviewContextmenuRight Click

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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

    }
}

Solution 2 - C#

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

Solution 3 - C#

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

Solution 4 - C#

  • 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.

Solution 5 - C#

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

Solution 6 - C#

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.

Solution 7 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser34537View Question on Stackoverflow
Solution 1 - C#Stuart HelwigView Answer on Stackoverflow
Solution 2 - C#ShortFuseView Answer on Stackoverflow
Solution 3 - C#MattView Answer on Stackoverflow
Solution 4 - C#ActualRandyView Answer on Stackoverflow
Solution 5 - C#Kshitij JhangraView Answer on Stackoverflow
Solution 6 - C#Captain ComicView Answer on Stackoverflow
Solution 7 - C#Gers0nView Answer on Stackoverflow