menú contextual del botón derecho del ratón para datagridview

116

Tengo una vista de cuadrícula de datos en una aplicación .NET winform. Me gustaría hacer clic derecho en una fila y que aparezca un menú. Entonces me gustaría seleccionar cosas como copiar, validar, etc.

¿Cómo hago A) un menú emergente B) encuentre en qué fila se hizo clic derecho. Sé que podría usar selectedIndex, pero debería poder hacer clic derecho sin cambiar lo que está seleccionado. en este momento podría usar el índice seleccionado, pero si hay una manera de obtener los datos sin cambiar lo que está seleccionado, sería útil.

kodkod
fuente

Respuestas:

143

Puede utilizar CellMouseEnter y CellMouseLeave para realizar un seguimiento del número de fila sobre el que se encuentra actualmente el mouse.

Luego use un objeto ContextMenu para mostrar su menú emergente, personalizado para la fila actual.

Aquí hay un ejemplo rápido y sucio de lo que quiero decir ...

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

    }
}
Stuart Helwig
fuente
6
¡Correcto! y una nota para ti, var r = dataGridView1.HitTest (eX, eY); r.RowIndex funciona MUCHO MEJOR que usar el mouse o
3
usar .ToString () en string.Format es innecesario.
MS
19
Este método es antiguo: una vista de cuadrícula de datos tiene una propiedad: ContextMenu. El menú contextual se abrirá tan pronto como el operador haga clic derecho. El evento ContextMenuOpening correspondiente le brinda la oportunidad de decidir qué mostrar según la celda actual o las celdas seleccionadas. Vea una de las otras respuestas
Harald Coppoolse
4
Para obtener las coordinadas de pantalla correctas, debe abrir el menú contextual como este:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
¿Cómo agrego una función a los elementos del menú?
Alpha Gabriel V.Timbol
89

Si bien esta pregunta es antigua, las respuestas no son adecuadas. Los menús contextuales tienen sus propios eventos en DataGridView. Hay un evento para el menú contextual de la fila y el menú contextual de la celda.

La razón por la cual estas respuestas no son adecuadas es que no tienen en cuenta los diferentes esquemas de operación. Es posible que las opciones de accesibilidad, las conexiones remotas o la migración de Metro / Mono / Web / WPF no funcionen y los atajos de teclado fallarán hacia la derecha (Mayús + F10 o la tecla Menú contextual).

La selección de celda al hacer clic con el botón derecho del mouse debe manejarse manualmente. Mostrar el menú contextual no necesita ser manejado ya que esto lo maneja la IU.

Esto imita completamente el enfoque utilizado por Microsoft Excel. Si una celda es parte de un rango seleccionado, la selección de celda no cambia ni tampoco CurrentCell. Si no es así, el rango anterior se borra y la celda se selecciona y se convierte en CurrentCell.

Si no tiene claro esto, CurrentCelles donde el teclado se enfoca cuando presiona las teclas de flecha. Selectedes si es parte de SelectedCells. El menú contextual se mostrará al hacer clic derecho según lo maneja la interfaz de usuario.

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

Los atajos de teclado no muestran el menú contextual de forma predeterminada, por lo que debemos agregarlos.

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

He reelaborado este código para que funcione de forma estática, por lo que puede copiarlos y pegarlos en cualquier evento.

La clave es usar, CellContextMenuStripNeededya que esto le dará el menú contextual.

Aquí hay un ejemplo CellContextMenuStripNeededen el que puede especificar qué menú contextual mostrar si desea tener diferentes por fila.

En este contexto MultiSelectes Truey SelectionModees FullRowSelect. Esto es solo un ejemplo y no una limitación.

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    
        }
    }
}
Mecha corta
fuente
5
+1 para una respuesta completa y para considerar la accesibilidad (y para responder una pregunta de hace 3 años)
gt
3
De acuerdo, esto es mucho mejor que lo aceptado (aunque no hay nada realmente malo en ninguno de ellos), e incluso más elogios por incluir soporte de teclado, algo en lo que muchas personas parecen no pensar.
Richard Moss
2
Gran respuesta, ofrece toda la flexibilidad: diferentes menús contextuales dependiendo de lo que se haga clic. Y exactamente el comportamiento de EXCEL
Harald Coppoolse
2
No soy fanático de este método porque con mi simple DataGridView no uso una fuente de datos o modo virtual. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Utilice el CellMouseDownevento en el DataGridView. A partir de los argumentos del controlador de eventos, puede determinar en qué celda se hizo clic. Usando el PointToClient()método en DataGridView, puede determinar la posición relativa del puntero al DataGridView, por lo que puede abrir el menú en la ubicación correcta.

(El DataGridViewCellMouseEventparámetro solo le da Xy Yrelativo a la celda en la que hizo clic, que no es tan fácil de usar para mostrar el menú contextual).

Este es el código que usé para obtener la posición del mouse, luego ajustar la posición de DataGridView:

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

Todo el controlador de eventos se ve así:

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);
        }
    }
}
Mate
fuente
1
También puede utilizar (sender as DataGridView)[e.ColumnIndex, e.RowIndex];para una llamada más sencilla al móvil.
Qsiris
La respuesta marcada no funciona correctamente en varias pantallas, pero esta respuesta funciona.
Furkan Ekinci
45
  • Ponga un menú contextual en su formulario, asígnele un nombre, establezca subtítulos, etc. utilizando el editor incorporado
  • Vincúlelo a su cuadrícula usando la propiedad de cuadrícula ContextMenuStrip
  • Para su cuadrícula, cree un evento para manejar CellContextMenuStripNeeded
  • El Args de eventos e tiene propiedades útiles e.ColumnIndex, e.RowIndex.

Creo que eso e.RowIndexes lo que estás pidiendo.

Sugerencia: cuando el usuario hace que se active su evento CellContextMenuStripNeeded, utilícelo e.RowIndexpara obtener datos de su cuadrícula, como el ID. Almacene el ID como elemento de etiqueta del evento de menú.

Ahora, cuando el usuario haga clic en el elemento del menú, utilice la propiedad Sender para recuperar la etiqueta. Utilice la etiqueta, que contiene su ID, para realizar la acción que necesita.

ActualRandy
fuente
5
No puedo votar esto lo suficiente. Las otras respuestas eran obvias para mí, pero podía decir que había más soporte integrado para menús contextuales (y no solo para DataGrid). Ésta es la respuesta correcta.
Jonathan Wood
1
@ActualRandy, ¿cómo obtengo la etiqueta cuando el usuario hace clic en el menú contextual real? bajo el evento CellcontexMenustripNeeded, tengo algo así como contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Esta respuesta casi está ahí, sin embargo, le sugiero que NO vincule el menú contextual a la propiedad de cuadrícula ContextMenuStrip. En su lugar, dentro del CellContextMenuStripNeededcontrolador de eventos, haga if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}esto significa que el menú solo se muestra al hacer clic con el botón derecho en una fila válida (es decir, no en un encabezado o área de cuadrícula vacía)
James S
Solo como un comentario a esta respuesta muy útil: CellContextMenuStripNeededsolo funciona si su DGV está vinculado a una fuente de datos o si su VirtualMode está configurado en verdadero. En otros casos, deberá establecer esa etiqueta en el CellMouseDownevento. Para estar seguro allí, realice un DataGridView.HitTestInfoen el controlador de eventos MouseDown para comprobar que está en una celda.
LocEngineer
6

Simplemente arrastre un componente ContextMenu o ContextMenuStrip a su formulario y diséñelo visualmente, luego asígnelo a la propiedad ContextMenu o ContextMenuStrip de su control deseado.

Capitán Comic
fuente
4

Sigue los pasos:

  1. Crea un menú contextual como: Ejemplo de menú contextual

  2. El usuario debe hacer clic derecho en la fila para obtener este menú. Necesitamos manejar el evento _MouseClick y el evento _CellMouseDown.

selectedBiodataid es la variable que contiene la información de la fila seleccionada.

Aquí está el código:

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

        }
    }
}

y la salida sería:

Salida final

Kshitij Jhangra
fuente
3

Para la posición del menú contextual, encontré el problema de que necesitaba que fuera relativo a DataGridView, y el evento que necesitaba usar da la posición relativa a la celda en la que se hizo clic. No he encontrado una mejor solución, así que implementé esta función en la clase commons, así que la llamo desde donde sea que necesite.

Está bastante probado y funciona bien. Espero que le sea útil.

    /// <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
fuente