menu contextuel clic droit pour datagridview

116

J'ai un datagridview dans une application .NET winform. Je voudrais faire un clic droit sur une ligne et faire apparaître un menu. Ensuite, je voudrais sélectionner des éléments tels que copier, valider, etc.

Comment faire pour A) un menu contextuel B) trouver quelle ligne a été cliquée avec le bouton droit de la souris. Je sais que je pourrais utiliser selectedIndex mais je devrais pouvoir faire un clic droit sans changer ce qui est sélectionné? pour le moment, je pourrais utiliser l'index sélectionné, mais s'il existe un moyen d'obtenir les données sans changer ce qui est sélectionné, ce serait utile.

kodkod
la source

Réponses:

143

Vous pouvez utiliser CellMouseEnter et CellMouseLeave pour suivre le numéro de ligne sur lequel la souris survole actuellement.

Utilisez ensuite un objet ContextMenu pour afficher votre menu contextuel, personnalisé pour la ligne courante.

Voici un exemple rapide et sale de ce que je veux dire ...

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
la source
6
Correct! et une note pour vous, var r = dataGridView1.HitTest (eX, eY); r.RowIndex fonctionne MIEUX que l'utilisation de la souris ou de currentMouseOverRow
3
utiliser .ToString () dans string.Format est inutilement.
MS du
19
Cette méthode est ancienne: un datagridview a une propriété: ContextMenu. Le menu contextuel s'ouvre dès que l'opérateur clique avec le bouton droit de la souris. L'événement ContextMenuOpening correspondant vous donne la possibilité de décider quoi afficher en fonction de la cellule actuelle ou des cellules sélectionnées. Voir l'une des autres réponses
Harald Coppoolse
4
Afin d'obtenir le bon écran coordiantes vous devez ouvrir le menu contextuel comme ceci:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
comment ajouter une fonction aux menuitems?
Alpha Gabriel V.Timbol
89

Bien que cette question soit ancienne, les réponses ne sont pas correctes. Les menus contextuels ont leurs propres événements sur DataGridView. Il existe un événement pour le menu contextuel de ligne et le menu contextuel de cellule.

La raison pour laquelle ces réponses ne sont pas correctes est qu'elles ne tiennent pas compte des différents schémas d'exploitation. Les options d'accessibilité, les connexions à distance ou le portage Metro / Mono / Web / WPF peuvent ne pas fonctionner et les raccourcis clavier échoueront vers le bas à droite (Maj + F10 ou touche du menu contextuel).

La sélection de cellule par clic droit de la souris doit être gérée manuellement. L'affichage du menu contextuel n'a pas besoin d'être géré car cela est géré par l'interface utilisateur.

Cela imite complètement l'approche utilisée par Microsoft Excel. Si une cellule fait partie d'une plage sélectionnée, la sélection de cellule ne change pas et non plus CurrentCell. Si ce n'est pas le cas, l'ancienne plage est effacée et la cellule est sélectionnée et devient CurrentCell.

Si vous n'êtes pas clair à ce sujet, CurrentCellc'est là que le clavier a le focus lorsque vous appuyez sur les touches fléchées. Selectedest de savoir si cela fait partie de SelectedCells. Le menu contextuel s'affichera lors du clic droit comme géré par l'interface utilisateur.

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

Les raccourcis clavier n'affichent pas le menu contextuel par défaut, nous devons donc les ajouter.

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

J'ai retravaillé ce code pour qu'il fonctionne de manière statique, vous pouvez donc les copier et les coller dans n'importe quel événement.

La clé est à utiliser CellContextMenuStripNeededcar cela vous donnera le menu contextuel.

Voici un exemple d'utilisation CellContextMenuStripNeededoù vous pouvez spécifier le menu contextuel à afficher si vous souhaitez en avoir différents par ligne.

Dans ce contexte MultiSelectest Trueet SelectionModeest FullRowSelect. Ceci est juste pour l'exemple et non une 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    
        }
    }
}
Mèche courte
la source
5
+1 pour une réponse complète et pour considérer l'accessibilité (et pour répondre à une question vieille de 3 ans)
gt
3
D'accord, c'est bien mieux que l'accepté (bien qu'il n'y ait rien de vraiment mal avec aucun d'entre eux) - et encore plus de félicitations pour avoir inclus le support du clavier, ce à quoi tant de gens semblent ne pas penser.
Richard Moss
2
Excellente réponse, donne toute la flexibilité: différents menus contextuels en fonction de ce qui est cliqué. Et exactement le comportement EXCEL
Harald Coppoolse
2
Je ne suis pas fan de cette méthode car avec mon DataGridView simple, je n'utilise pas de source de données ou de mode virtuel. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Utilisez l' CellMouseDownévénement sur le DataGridView. À partir des arguments du gestionnaire d'événements, vous pouvez déterminer sur quelle cellule l'utilisateur a cliqué. À l'aide de la PointToClient()méthode sur le DataGridView, vous pouvez déterminer la position relative du pointeur vers le DataGridView, afin que vous puissiez afficher le menu à l'emplacement correct.

(Le DataGridViewCellMouseEventparamètre vous donne simplement le Xet Yrelatif à la cellule sur laquelle vous avez cliqué, ce qui n'est pas aussi facile à utiliser pour faire apparaître le menu contextuel.)

C'est le code que j'ai utilisé pour obtenir la position de la souris, puis ajuster la position du DataGridView:

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

L'ensemble du gestionnaire d'événements ressemble à ceci:

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);
        }
    }
}
Mat
la source
1
Vous pouvez également utiliser (sender as DataGridView)[e.ColumnIndex, e.RowIndex];pour un appel plus simple à la cellule.
Qsiris
La réponse cochée ne fonctionne pas correctement sur plusieurs écrans mais cette réponse fonctionne.
Furkan Ekinci
45
  • Mettez un menu contextuel sur votre formulaire, nommez-le, définissez des légendes, etc. à l'aide de l'éditeur intégré
  • Liez-le à votre grille en utilisant la propriété grid ContextMenuStrip
  • Pour votre grille, créez un événement à gérer CellContextMenuStripNeeded
  • L'événement Args e a des propriétés utiles e.ColumnIndex, e.RowIndex.

Je crois que e.RowIndexc'est ce que vous demandez.

Suggestion: lorsque l'utilisateur CellContextMenuStripNeededdéclenche votre événement , utilisez e.RowIndexpour obtenir des données de votre grille, telles que l'ID. Stockez l'ID comme élément de balise de l'événement de menu.

Désormais, lorsque l'utilisateur clique sur l'élément de menu, utilisez la propriété Sender pour récupérer la balise. Utilisez la balise contenant votre identifiant pour effectuer l'action dont vous avez besoin.

ActualRandy
la source
5
Je ne peux pas assez voter pour cela. Les autres réponses étaient évidentes pour moi, mais je pouvais dire qu'il y avait plus de support intégré pour les menus contextuels (et pas seulement pour DataGrid). C'est la bonne réponse.
Jonathan Wood
1
@ActualRandy, comment obtenir la balise lorsque l'utilisateur clique sur le menu contextuel réel? sous l'événement CellcontexMenustripNeeded, j'ai quelque chose comme contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Cette réponse est presque là, mais je vous suggère de NE PAS lier le menu contextuel à la propriété de grille ContextMenuStrip. Au lieu de cela, à l'intérieur du CellContextMenuStripNeededgestionnaire d'événements, cela if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}signifiera que le menu n'est affiché qu'en cliquant avec le bouton droit sur une ligne valide, (c'est-à-dire pas sur un en-tête ou une zone de grille vide)
James S
Juste un commentaire à cette réponse très utile: CellContextMenuStripNeededne fonctionne que si votre DGV est lié à une source de données ou si son VirtualMode est défini sur true. Dans d'autres cas, vous devrez définir cette balise dans l' CellMouseDownévénement. Pour être sûr, effectuez un DataGridView.HitTestInfodans le gestionnaire d'événements MouseDown pour vérifier que vous êtes sur une cellule.
LocEngineer
6

Faites simplement glisser un composant ContextMenu ou ContextMenuStrip dans votre formulaire et concevez-le visuellement, puis affectez-le à la propriété ContextMenu ou ContextMenuStrip du contrôle souhaité.

Capitaine Comic
la source
4

Suis les étapes:

  1. Créez un menu contextuel comme: Exemple de menu contextuel

  2. L'utilisateur doit faire un clic droit sur la ligne pour obtenir ce menu. Nous devons gérer l'événement _MouseClick et l'événement _CellMouseDown.

selectedBiodataid est la variable qui contient les informations de ligne sélectionnées.

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

        }
    }
}

et la sortie serait:

Sortie finale

Kshitij Jhangra
la source
3

Pour la position du menu contextuel, y a trouvé le problème dont j'avais besoin qu'il soit relatif au DataGridView, et l'événement que j'avais besoin d'utiliser donne la poistion par rapport à la cellule sur laquelle j'ai cliqué. Je n'ai pas trouvé de meilleure solution, j'ai donc implémenté cette fonction dans la classe commons, donc je l'appelle de partout où j'ai besoin.

C'est assez testé et fonctionne bien. J'espère que tu trouves cela 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);
    }
Gers0n
la source