menu contestuale del tasto destro per datagridview


116

Ho un datagridview in un'app .NET winform. Vorrei fare clic con il tasto destro su una riga e visualizzare un menu a comparsa. Quindi vorrei selezionare cose come copia, convalida, ecc

Come faccio a fare A) un menu a comparsa B) trova quale riga è stata cliccata con il tasto destro. So che potrei usare selectedIndex ma dovrei essere in grado di fare clic con il pulsante destro del mouse senza modificare ciò che è selezionato? in questo momento potrei usare l'indice selezionato, ma se c'è un modo per ottenere i dati senza modificare ciò che è selezionato, sarebbe utile.

Risposte:


143

È possibile utilizzare CellMouseEnter e CellMouseLeave per tenere traccia del numero di riga su cui si trova attualmente il mouse.

Quindi utilizza un oggetto ContextMenu per visualizzare il menu popup, personalizzato per la riga corrente.

Ecco un rapido e sporco esempio di cosa intendo ...

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

    }
}

6
Corretta! e una nota per te, var r = dataGridView1.HitTest (eX, eY); r.RowIndex funziona MEGLIO quindi utilizzando il mouse o currentMouseOverRow

3
l'utilizzo di .ToString () in string.Format è inutile.
MS

19
Questo metodo è vecchio: un datagridview ha una proprietà: ContextMenu. Il menu contestuale verrà aperto non appena l'operatore fa clic con il pulsante destro del mouse. L'evento ContextMenuOpening corrispondente ti dà l'opportunità di decidere cosa mostrare a seconda della cella corrente o delle celle selezionate. Vedi una delle altre risposte
Harald Coppoolse

4
Per ottenere le giuste coordinate dello schermo dovresti aprire il menu contestuale in questo modo:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes

come aggiungo una funzione al menuitems?
Alpha Gabriel V. Timbol

89

Anche se questa domanda è vecchia, le risposte non sono corrette. I menu contestuali hanno i propri eventi su DataGridView. C'è un evento per il menu contestuale della riga e il menu contestuale della cella.

Il motivo per cui queste risposte non sono corrette è che non tengono conto di schemi operativi diversi. Le opzioni di accessibilità, le connessioni remote o il porting Metro / Mono / Web / WPF potrebbero non funzionare e le scorciatoie da tastiera non funzioneranno correttamente (Maiusc + F10 o tasto Menu contestuale).

La selezione delle celle al clic destro del mouse deve essere gestita manualmente. La visualizzazione del menu contestuale non deve essere gestita poiché viene gestita dall'interfaccia utente.

Questo imita completamente l'approccio utilizzato da Microsoft Excel. Se una cella fa parte di un intervallo selezionato, la selezione della cella non cambia e nemmeno lo fa CurrentCell. In caso contrario, il vecchio intervallo viene cancellato e la cella viene selezionata e diventa CurrentCell.

Se non sei chiaro su questo, CurrentCellè dove la tastiera è attiva quando premi i tasti freccia. Selectedè se fa parte di SelectedCells. Il menu contestuale verrà visualizzato al clic destro come gestito dall'interfaccia utente.

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

Le scorciatoie da tastiera non mostrano il menu contestuale per impostazione predefinita, quindi dobbiamo aggiungerle.

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

Ho rielaborato questo codice in modo che funzioni in modo statico, quindi puoi copiarlo e incollarlo in qualsiasi evento.

La chiave è usare CellContextMenuStripNeededpoiché questo ti darà il menu contestuale.

Ecco un esempio in CellContextMenuStripNeededcui è possibile specificare quale menu di scelta rapida mostrare se si desidera averne di diversi per riga.

In questo contesto MultiSelectè Trueed SelectionModeè FullRowSelect. Questo è solo un esempio e non una limitazione.

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

5
+1 per la risposta completa e per considerare l'accessibilità (e per rispondere a una domanda vecchia di 3 anni)
gt

3
D'accordo, questo è molto meglio di quello accettato (anche se non c'è niente di veramente sbagliato in nessuno di essi) - e ancora più complimenti per aver incluso il supporto per la tastiera, qualcosa a cui così tante persone sembrano non pensare.
Richard Moss

2
Ottima risposta, offre tutta la flessibilità: diversi menu contestuali a seconda di cosa si fa clic. Ed esattamente il comportamento EXCEL
Harald Coppoolse

2
Non sono un fan di questo metodo perché con il mio semplice DataGridView non utilizzo un'origine dati o una modalità virtuale. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen

47

Usa l' CellMouseDownevento su DataGridView. Dagli argomenti del gestore eventi è possibile determinare su quale cella è stato fatto clic. Utilizzando il PointToClient()metodo su DataGridView è possibile determinare la posizione relativa del puntatore a DataGridView, in modo da poter visualizzare il menu nella posizione corretta.

(Il DataGridViewCellMouseEventparametro ti dà solo il Xe Yrelativo alla cella su cui hai fatto clic, che non è così facile da usare per far apparire il menu contestuale.)

Questo è il codice che ho usato per ottenere la posizione del mouse, quindi regolare la posizione di DataGridView:

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

L'intero gestore di eventi ha questo aspetto:

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

1
Potresti anche usare (sender as DataGridView)[e.ColumnIndex, e.RowIndex];per una chiamata più semplice al cellulare.
Qsiris

La risposta selezionata non funziona correttamente su più schermi ma questa risposta funziona.
Furkan Ekinci

45
  • Metti un menu contestuale sul tuo modulo, assegnagli un nome, imposta didascalie ecc. Usando l'editor integrato
  • Collegalo alla tua griglia usando la proprietà grid ContextMenuStrip
  • Per la tua griglia, crea un evento da gestire CellContextMenuStripNeeded
  • L'evento Args e ha proprietà utili e.ColumnIndex, e.RowIndex.

Credo che e.RowIndexsia quello che stai chiedendo.

Suggerimento: quando l'utente attiva il tuo evento CellContextMenuStripNeeded, utilizza e.RowIndexper ottenere dati dalla tua griglia, come l'ID. Memorizza l'ID come elemento del tag dell'evento del menu.

Ora, quando l'utente fa effettivamente clic sulla voce di menu, utilizza la proprietà Sender per recuperare il tag. Utilizza il tag, contenente il tuo ID, per eseguire l'azione di cui hai bisogno.


5
Non posso votare abbastanza. Le altre risposte erano ovvie per me, ma potevo dire che c'era più supporto integrato per i menu contestuali (e non solo per DataGrid). Questa è la risposta corretta.
Jonathan Wood,

1
@ ActualRandy, come ottengo il tag quando l'utente fa clic sul menu contestuale effettivo? sotto l'evento CellcontexMenustripNeeded, ho qualcosa di simile a contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo

2
Questa risposta è quasi arrivata, tuttavia ti suggerirei di NON collegare il menu di scelta rapida alla proprietà della griglia ContextMenuStrip. Invece all'interno del CellContextMenuStripNeededgestore di eventi fai if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Ciò significa che il menu viene mostrato solo facendo clic con il pulsante destro del mouse su una riga valida, (cioè non su un'intestazione o un'area vuota della griglia)
James S

Proprio come un commento a questa risposta molto utile: CellContextMenuStripNeededfunziona solo se il tuo DGV è associato a un'origine dati o se il suo VirtualMode è impostato su true. In altri casi dovrai impostare quel tag CellMouseDownnell'evento. Per essere al sicuro, eseguire un DataGridView.HitTestInfonel gestore di eventi MouseDown per verificare di essere su una cella.
LocEngineer

6

È sufficiente trascinare un componente ContextMenu o ContextMenuStrip nel modulo e progettarlo visivamente, quindi assegnarlo alla proprietà ContextMenu o ContextMenuStrip del controllo desiderato.


4

Segui i passi:

  1. Crea un menu contestuale come: Menu contestuale di esempio

  2. L'utente deve fare clic con il tasto destro sulla riga per ottenere questo menu. Dobbiamo gestire l'evento _MouseClick e l'evento _CellMouseDown.

selectedBiodataid è la variabile che contiene le informazioni sulla riga selezionata.

Ecco il codice:

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 l'output sarebbe:

Uscita finale


3

Per la posizione del menu contestuale, ho trovato il problema che mi serviva per essere relativo a DataGridView e l'evento che dovevo usare fornisce la posizione relativa alla cella su cui si è fatto clic. Non ho trovato una soluzione migliore, quindi ho implementato questa funzione nella classe commons, quindi la chiamo da dove ho bisogno.

È abbastanza testato e funziona bene. Spero che lo trovi utile.

    /// <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);
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.