Klicken Sie mit der rechten Maustaste auf das Kontextmenü für die Datagrid-Ansicht

116

Ich habe eine Datagrid-Ansicht in einer .NET-Winform-App. Ich möchte mit der rechten Maustaste auf eine Zeile klicken und ein Menü anzeigen lassen. Dann möchte ich Dinge wie Kopieren, Validieren usw. Auswählen

Wie mache ich A) ein Menü-Popup B) finde heraus, auf welche Zeile mit der rechten Maustaste geklickt wurde. Ich weiß, dass ich selectedIndex verwenden könnte, aber ich sollte in der Lage sein, mit der rechten Maustaste zu klicken, ohne die Auswahl zu ändern. Im Moment könnte ich den ausgewählten Index verwenden, aber wenn es eine Möglichkeit gibt, die Daten abzurufen, ohne die Auswahl zu ändern, wäre dies nützlich.

Kodkod
quelle

Antworten:

143

Mit CellMouseEnter und CellMouseLeave können Sie die Zeilennummer verfolgen, über der sich die Maus gerade befindet.

Verwenden Sie dann ein ContextMenu-Objekt, um Ihr Popup-Menü anzuzeigen, das für die aktuelle Zeile angepasst ist.

Hier ist ein kurzes und schmutziges Beispiel dafür, was ich meine ...

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
quelle
6
Richtig! und eine Notiz für Sie, var r = dataGridView1.HitTest (eX, eY); r.RowIndex funktioniert viel besser als mit der Maus oder currentMouseOverRow
3
Die Verwendung von .ToString () in string.Format ist unnötig.
MS
19
Diese Methode ist alt: Eine Datagrid-Ansicht hat eine Eigenschaft: ContextMenu. Das Kontextmenü wird geöffnet, sobald der Bediener mit der rechten Maustaste klickt. Das entsprechende ContextMenuOpening-Ereignis gibt Ihnen die Möglichkeit zu entscheiden, was abhängig von der aktuellen Zelle oder ausgewählten Zellen angezeigt werden soll. Siehe eine der anderen Antworten
Harald Coppoolse
4
Um die richtigen Bildschirmkoordinaten zu erhalten, sollten Sie das Kontextmenü wie folgt öffnen:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
Wie füge ich den Menüs eine Funktion hinzu?
Alpha Gabriel V. Timbol
89

Obwohl diese Frage alt ist, sind die Antworten nicht richtig. Kontextmenüs haben ihre eigenen Ereignisse in DataGridView. Es gibt ein Ereignis für das Zeilenkontextmenü und das Zellenkontextmenü.

Der Grund, warum diese Antworten nicht richtig sind, ist, dass sie keine unterschiedlichen Betriebsschemata berücksichtigen. Eingabehilfen, Remoteverbindungen oder Metro / Mono / Web / WPF-Portierung funktionieren möglicherweise nicht und Tastaturkürzel schlagen fehl (Umschalt + F10 oder Kontextmenütaste).

Die Zellenauswahl per Rechtsklick muss manuell erfolgen. Das Anzeigen des Kontextmenüs muss nicht behandelt werden, da dies von der Benutzeroberfläche erledigt wird.

Dies ahmt den von Microsoft Excel verwendeten Ansatz vollständig nach. Wenn eine Zelle Teil eines ausgewählten Bereichs ist, ändert sich die Zellenauswahl nicht und auch nicht CurrentCell. Ist dies nicht der Fall, wird der alte Bereich gelöscht und die Zelle ausgewählt und wird CurrentCell.

Wenn Sie sich nicht sicher sind, CurrentCellhat die Tastatur beim Drücken der Pfeiltasten den Fokus. Selectedist, ob es ein Teil von ist SelectedCells. Das Kontextmenü wird beim Klicken mit der rechten Maustaste angezeigt, wie es von der Benutzeroberfläche verarbeitet wird.

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

Tastaturkürzel zeigen standardmäßig nicht das Kontextmenü an, daher müssen wir sie hinzufügen.

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

Ich habe diesen Code überarbeitet, damit er statisch funktioniert, sodass Sie ihn kopieren und in jedes Ereignis einfügen können.

Der Schlüssel ist zu verwenden, CellContextMenuStripNeededda dies Ihnen das Kontextmenü gibt.

In diesem Beispiel CellContextMenuStripNeededkönnen Sie angeben, welches Kontextmenü angezeigt werden soll, wenn Sie unterschiedliche Zeilen pro Zeile verwenden möchten.

In diesem Zusammenhang MultiSelectist Trueund SelectionModeist FullRowSelect. Dies ist nur ein Beispiel und keine Einschränkung.

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    
        }
    }
}
ShortFuse
quelle
5
+1 für eine umfassende Antwort und für die Prüfung der Zugänglichkeit (und für die Beantwortung einer 3 Jahre alten Frage)
gt
3
Einverstanden ist dies viel besser als das Akzeptierte (obwohl an keinem von ihnen wirklich etwas falsch ist) - und noch mehr Lob für die Einbeziehung der Tastaturunterstützung, an das so viele Leute einfach nicht zu denken scheinen.
Richard Moss
2
Gute Antwort, bietet alle Flexibilität: Unterschiedliche Kontextmenüs, je nachdem, auf was geklickt wird. Und genau das EXCEL-Verhalten
Harald Coppoolse
2
Ich bin kein Fan dieser Methode, da ich mit meinem einfachen DataGridView keine Datenquelle oder keinen virtuellen Modus verwende. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Verwenden Sie das CellMouseDownEreignis auf der DataGridView. Anhand der Event-Handler-Argumente können Sie bestimmen, auf welche Zelle geklickt wurde. Mit der PointToClient()Methode in DataGridView können Sie die relative Position des Zeigers auf DataGridView bestimmen, um das Menü an der richtigen Stelle aufzurufen.

(Der DataGridViewCellMouseEventParameter gibt nur das Xund Yrelativ zu der Zelle an, auf die Sie geklickt haben. Dies ist nicht so einfach, um das Kontextmenü aufzurufen.)

Dies ist der Code, mit dem ich die Mausposition ermittelt und dann die Position von DataGridView angepasst habe:

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

Der gesamte Event-Handler sieht folgendermaßen aus:

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
quelle
1
Sie können auch (sender as DataGridView)[e.ColumnIndex, e.RowIndex];einen einfacheren Aufruf der Zelle verwenden.
Qsiris
Die aktivierte Antwort funktioniert auf mehreren Bildschirmen nicht richtig, aber diese Antwort funktioniert.
Furkan Ekinci
45
  • Fügen Sie Ihrem Formular ein Kontextmenü hinzu, benennen Sie es, legen Sie Untertitel usw. mit dem integrierten Editor fest
  • Verknüpfen Sie es mithilfe der Eigenschaft grid mit Ihrem Raster ContextMenuStrip
  • Erstellen Sie für Ihr Raster ein zu behandelndes Ereignis CellContextMenuStripNeeded
  • Das Ereignis Args e hat nützliche Eigenschaften e.ColumnIndex, e.RowIndex.

Ich glaube, das e.RowIndexist es, wonach Sie fragen.

Vorschlag: Wenn der Benutzer ein Ereignis auslöst CellContextMenuStripNeeded, verwenden e.RowIndexSie diese Option, um Daten aus Ihrem Raster abzurufen, z. B. die ID. Speichern Sie die ID als Tag-Element des Menüereignisses.

Wenn der Benutzer nun tatsächlich auf Ihren Menüpunkt klickt, verwenden Sie die Sender-Eigenschaft, um das Tag abzurufen. Verwenden Sie das Tag mit Ihrer ID, um die gewünschte Aktion auszuführen.

ActualRandy
quelle
5
Ich kann das nicht genug unterstützen. Die anderen Antworten waren für mich offensichtlich, aber ich konnte feststellen, dass Kontextmenüs (und nicht nur DataGrid) stärker unterstützt wurden. Dies ist die richtige Antwort.
Jonathan Wood
1
@ActualRandy, wie erhalte ich das Tag, wenn der Benutzer auf das eigentliche Kontextmenü klickt? Unter dem CellcontexMenustripNeeded-Ereignis habe ich so etwas wie contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Diese Antwort ist fast da, aber ich würde vorschlagen, dass Sie das Kontextmenü NICHT mit der Rastereigenschaft ContextMenuStrip verknüpfen. Stattdessen innerhalb des CellContextMenuStripNeededEreignishandlers tun if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Dies bedeutet, dass das Menü nur angezeigt wird, wenn Sie mit der rechten Maustaste auf eine gültige Zeile klicken (dh nicht auf eine Überschrift oder einen leeren Rasterbereich)
James S
Nur als Kommentar zu dieser sehr hilfreichen Antwort: Funktioniert CellContextMenuStripNeedednur, wenn Ihr DGV an eine Datenquelle gebunden ist oder wenn sein VirtualMode auf true gesetzt ist. In anderen Fällen müssen Sie dieses Tag im CellMouseDownEreignis festlegen . Um dort auf der sicheren Seite zu sein, führen Sie DataGridView.HitTestInfoim MouseDown-Ereignishandler eine Überprüfung durch , um zu überprüfen, ob Sie sich in einer Zelle befinden.
LocEngineer
6

Ziehen Sie einfach eine ContextMenu- oder ContextMenuStrip-Komponente in Ihr Formular, entwerfen Sie sie visuell und weisen Sie sie dann der ContextMenu- oder ContextMenuStrip-Eigenschaft Ihres gewünschten Steuerelements zu.

Captain Comic
quelle
4

Folge den Schritten:

  1. Erstellen Sie ein Kontextmenü wie: Beispielkontextmenü

  2. Der Benutzer muss mit der rechten Maustaste auf die Zeile klicken, um dieses Menü aufzurufen. Wir müssen das _MouseClick-Ereignis und das _CellMouseDown-Ereignis behandeln.

selectedBiodataid ist die Variable, die die ausgewählten Zeileninformationen enthält.

Hier ist der 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)
        {

        }
    }
}

und die Ausgabe wäre:

Endgültige Ausgabe

Kshitij Jhangra
quelle
3

Für die Position für das Kontextmenü stellte y das Problem fest, dass es relativ zur DataGridView benötigt wurde, und das Ereignis, das ich verwenden musste, gibt die Position relativ zur angeklickten Zelle an. Ich habe keine bessere Lösung gefunden, deshalb habe ich diese Funktion in der Commons-Klasse implementiert und rufe sie von jedem Ort aus auf, an dem ich sie benötige.

Es ist ziemlich getestet und funktioniert gut. Ich hoffe, Sie finden es nützlich.

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