clique com o botão direito no menu de contexto para o datagridview

116

Eu tenho um datagridview em um aplicativo WinForm .NET. Eu gostaria de clicar com o botão direito em uma linha e ter um menu pop-up. Então eu gostaria de selecionar coisas como copiar, validar, etc.

Como faço para A) um menu pop-up B) localizar qual linha foi clicada com o botão direito. Eu sei que poderia usar o selectedIndex, mas devo conseguir clicar com o botão direito do mouse sem alterar o que está selecionado. agora eu poderia usar o índice selecionado, mas se houver uma maneira de obter os dados sem alterar o que está selecionado, isso seria útil.

Kodkod
fonte

Respostas:

143

Você pode usar CellMouseEnter e CellMouseLeave para rastrear o número da linha sobre a qual o mouse está passando.

Em seguida, use um objeto ContextMenu para exibir o menu pop-up, personalizado para a linha atual.

Aqui está um exemplo rápido e sujo do que quero dizer ...

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
fonte
6
Corrigir! e uma nota para você, var r = dataGridView1.HitTest (eX, eY); r.RowIndex funciona MUITO MELHOR do que usando o mouse ou currentMouseOverRow
3
usando .ToString () em string.Format é desnecessariamente.
MS
19
Este método é antigo: um datagridview tem uma propriedade: ContextMenu. O menu de contexto será aberto assim que o operador clicar com o botão direito. O evento ContextMenuOpening correspondente oferece a oportunidade de decidir o que mostrar, dependendo da célula atual ou das células selecionadas. Veja uma das outras respostas
Harald Coppoolse
4
A fim de obter as coordenadas de tela corretas, você deve abrir o menu de contexto assim:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
como adiciono uma função aos itens do menu?
Alpha Gabriel V. Timbol
89

Embora essa pergunta seja antiga, as respostas não são adequadas. Os menus de contexto têm seus próprios eventos em DataGridView. Há um evento para o menu de contexto da linha e o menu de contexto da célula.

A razão pela qual essas respostas não são adequadas é que elas não levam em consideração os diferentes esquemas de operação. As opções de acessibilidade, conexões remotas ou portabilidade Metro / Mono / Web / WPF podem não funcionar e os atalhos do teclado falharão à direita (Shift + F10 ou tecla do Menu de Contexto).

A seleção de células com o clique direito do mouse deve ser feita manualmente. Mostrar o menu de contexto não precisa ser feito, pois isso é feito pela IU.

Isso imita completamente a abordagem usada pelo Microsoft Excel. Se uma célula fizer parte de um intervalo selecionado, a seleção da célula não muda e nem muda CurrentCell. Se não for, o intervalo antigo é limpo e a célula é selecionada e se torna CurrentCell.

Se você não tem certeza sobre isso, CurrentCellé onde o teclado tem foco quando você pressiona as teclas de seta.Selectedé se faz parte de SelectedCells. O menu de contexto será exibido ao clicar com o botão direito conforme gerenciado pela IU.

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

Os atalhos do teclado não mostram o menu de contexto por padrão, então temos que adicioná-los.

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

Eu retrabalhei esse código para funcionar estaticamente, então você pode copiá-lo e colá-lo em qualquer evento.

A chave é usar CellContextMenuStripNeeded pois isso fornecerá o menu de contexto.

Aqui está um exemplo usando CellContextMenuStripNeeded onde você pode especificar qual menu de contexto mostrar se deseja ter diferentes por linha.

Neste contexto MultiSelecté Truee SelectionModeé FullRowSelect. Isso é apenas um exemplo e não uma limitação.

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    
        }
    }
}
Pavio curto
fonte
5
+1 para uma resposta abrangente e para considerar a acessibilidade (e para responder a uma pergunta antiga de 3 anos)
gt
3
Concordo, isso é muito melhor do que o aceito (embora não haja nada realmente errado com nenhum deles) - e ainda mais elogios por incluir suporte para teclado, algo em que muitas pessoas parecem simplesmente não pensar.
Richard Moss,
2
Ótima resposta, dá toda a flexibilidade: menus de contexto diferentes dependendo do que for clicado. E exatamente o comportamento do EXCEL
Harald Coppoolse
2
Não sou fã desse método porque com meu DataGridView simples não uso uma fonte de dados ou modo virtual. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Use o CellMouseDownevento no DataGridView. A partir dos argumentos do manipulador de eventos, você pode determinar qual célula foi clicada. Usando o PointToClient()método no DataGridView, você pode determinar a posição relativa do ponteiro para o DataGridView, para que possa abrir o menu no local correto.

(O DataGridViewCellMouseEventparâmetro apenas fornece o Xe em Yrelação à célula em que você clicou, o que não é tão fácil de usar para abrir o menu de contexto.)

Este é o código que usei para obter a posição do mouse e, em seguida, ajuste para a posição do DataGridView:

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

Todo o manipulador de eventos tem a seguinte aparência:

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
fonte
1
Você também pode usar (sender as DataGridView)[e.ColumnIndex, e.RowIndex];para uma chamada mais simples para o celular.
Qsiris de
A resposta marcada não funciona corretamente em várias telas, mas esta resposta funciona.
Furkan Ekinci
45
  • Coloque um menu de contexto em seu formulário, nomeie-o, defina legendas etc. usando o editor integrado
  • Vincule-o à sua grade usando a propriedade da grade ContextMenuStrip
  • Para sua grade, crie um evento para lidar com CellContextMenuStripNeeded
  • O Args Eventos e tem propriedades úteis e.ColumnIndex, e.RowIndex.

Eu acredito que e.RowIndexé isso que você está pedindo.

Sugestão: quando o usuário faz com que seu evento CellContextMenuStripNeededseja disparado, use e.RowIndexpara obter dados de sua grade, como o ID. Armazene o ID como o item de tag do evento de menu.

Agora, quando o usuário realmente clicar em seu item de menu, use a propriedade Sender para buscar a tag. Use a tag, contendo seu ID, para executar a ação que você precisa.

ActualRandy
fonte
5
Não consigo votar a favor disso o suficiente. As outras respostas eram óbvias para mim, mas percebi que havia mais suporte integrado para menus de contexto (e não apenas para DataGrid). Esta é a resposta correta.
Jonathan Wood
1
@ActualRandy, como obtenho a tag quando o usuário clica no menu de contexto real? no evento CellcontexMenustripNeeded, tenho algo como contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Esta resposta está quase lá, no entanto, sugiro que você NÃO vincule o menu de contexto à propriedade da grade ContextMenuStrip. Em vez disso, dentro do CellContextMenuStripNeededmanipulador de eventos, faça if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Isso significa que o menu é mostrado apenas ao clicar com o botão direito em uma linha válida (ou seja, não em um título ou área de grade vazia)
James S
Apenas como um comentário para esta resposta muito útil: CellContextMenuStripNeededsó funciona se seu DGV estiver vinculado a uma fonte de dados ou se seu VirtualMode estiver definido como verdadeiro. Em outros casos, você precisará definir essa tag no CellMouseDownevento. Para ficar no lado seguro, execute um DataGridView.HitTestInfono manipulador de eventos MouseDown para verificar se você está em uma célula.
LocEngineer de
6

Basta arrastar um componente ContextMenu ou ContextMenuStrip para o seu formulário e projetá-lo visualmente e, em seguida, atribuí-lo à propriedade ContextMenu ou ContextMenuStrip do controle desejado.

Capitão Comic
fonte
4

Siga os passos:

  1. Crie um menu de contexto como: Menu de contexto de amostra

  2. O usuário precisa clicar com o botão direito na linha para acessar este menu. Precisamos lidar com o evento _MouseClick e o evento _CellMouseDown.

selectedBiodataid é a variável que contém as informações da linha selecionada.

Aqui está o 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)
        {

        }
    }
}

e a saída seria:

Resultado final

Kshitij Jhangra
fonte
3

Para a posição do menu de contexto, y encontrei o problema de que precisava ser relativo ao DataGridView, e o evento que eu precisava usar fornece o poistion relativo à célula clicada. Não encontrei uma solução melhor, então implementei essa função na classe comum, então a chamo de onde preciso.

É bastante testado e funciona bem. Espero que seja ú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
fonte