0

I have a C# project (VS 2017) that uses a Data Grid View to show data in an object list. Using a contextMenuStrip I want to be able to right click on a row and be able to remove it from the datagridview and the underlying datasource.

I have the contextMenuStrip set in the Properties of the Datagridview with one item with the following to methods to handle the events.

    private void dgv_Test_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right)
        {
            var hti = dgv_Test.HitTest(e.X, e.Y);

            dgv_Test.ClearSelection();
            dgv_Test.Rows[hti.RowIndex].Selected = true;
        }
    }

    private void cms_DGV_Remove_Click(object sender, EventArgs e)
    {
        MessageBox.Show("Content Menu Clicked on Remove Option");

        PersonModel temp = (PersonModel)dgv_Test.CurrentRow.DataBoundItem;

        string msg = $"The index for the selected Person is {temp.Id}.";
        MessageBox.Show(msg);

    }

I expect this to sent the current row to the row that is right clicked on. This is not happening as the CurrentRow is staying on the top row. It does work if I first Left click on the row then right click the same row.

G.Glass
  • 3
  • 3

1 Answers1

0

The problem you are describing is coming from the cms_DGV_Remove_Click event. When the user right-clicks on the grid, this is NOT going to make the cell/row underneath the cursor the grid’s CurrentRow. Even though the code in the dgv_Test_MouseDown method sets the row to “selected”…. it isn’t necessarily going to be the “current” row. The grids CurrentRow property is read only and you cannot directly set it from your code.

Given this, it is clear that getting the mouse coordinates “in relation to the grid” FROM the context menu will take a little effort since its coordinates are global. You have apparently noticed this from wiring up of the grids MouseDown event. This event makes it easy to capture the mouse position in relation to the grid. Problem is… you are NOT saving this info. By the time the context menu fires, that info is LOST.

Solution: make the DataGridView.HitTest info global. Then, set it every time the user right clicks into the grid. With this global variable set, when the context menu fires it will know which row the cursor is under.

DataGridView.HitTestInfo HT_Info;   // <- Global variable

private void dgv_Test_MouseDown(object sender, MouseEventArgs e) {
  if (e.Button == MouseButtons.Right) {
    HT_Info = dgv_Test.HitTest(e.X, e.Y);
    if (HT_Info.RowIndex >= 0) {
      dgv_Test.ClearSelection();
      dgv_Test.Rows[HT_Info.RowIndex].Selected = true;
    }
  }
}

It does not appear that the posted code is actually removing the row, however below is how the context menu “remove” event may look…

Below should work for a non-data bound grid and also a grid with a data bound DataTable.

private void cms_DGV_Remove_Click(object sender, EventArgs e) {
  if (HT_Info.RowIndex >= 0) {
    dgv_Test.Rows.Remove(dgv_Test.Rows[HT_Info.RowIndex]);
  }
}

If you are using a List<T>, the method to remove may look something like below...

private void cms_DGV_Remove_Click(object sender, EventArgs e) {
  if (HT_Info.RowIndex >= 0) {
    PersonModel targetPerson = (PersonModel)dgv_Test.Rows[HT_Info.RowIndex].DataBoundItem;
    AllPersons.Remove(targetPerson);
    dgv_Test.DataSource = null;
    dgv_Test.DataSource = AllPersons;
  }
}

I am guessing this is the behavior you are looking for. The user right-clicks into the grid, the row under the cursor is selected and the context menu “remove” pops up. The user can either “select” remove to remove the row or click away from the context menu to cancel the remove.

Hope that makes sense.

JohnG
  • 9,259
  • 2
  • 20
  • 29
  • JohnG, thank you for the detailed explanation. That is exactly what I was in need of to understand the underlying issue. I will implement making the DataGridView.HitTest global. Again thank you. – G.Glass Jun 25 '19 at 09:06