Kliknij prawym przyciskiem myszy menu kontekstowe dla datagridview

117

Mam datagridview w aplikacji .NET winform. Chciałbym kliknąć prawym przyciskiem myszy wiersz i wyświetlić menu podręczne. Następnie chciałbym wybrać takie rzeczy, jak kopiowanie, weryfikacja itp

Jak zrobić A) wyskakujące menu B) znaleźć wiersz kliknięty prawym przyciskiem myszy. Wiem, że mogę użyć selectedIndex, ale powinienem mieć możliwość kliknięcia prawym przyciskiem myszy bez zmiany zaznaczenia? w tej chwili mógłbym użyć wybranego indeksu, ale jeśli istnieje sposób na uzyskanie danych bez zmiany tego, co jest wybrane, byłoby to przydatne.

kodkod
źródło

Odpowiedzi:

143

Możesz użyć CellMouseEnter i CellMouseLeave, aby śledzić numer wiersza, nad którym aktualnie znajduje się mysz.

Następnie użyj obiektu ContextMenu, aby wyświetlić menu podręczne, dostosowane do bieżącego wiersza.

Oto szybki i brudny przykład tego, co mam na myśli ...

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
źródło
6
Poprawny! i uwaga dla Ciebie, var r = dataGridView1.HitTest (eX, eY); r.RowIndex działa DUŻO LEPIEJ niż za pomocą myszy lub
3
użycie .ToString () w string.Format jest niepotrzebne.
MS
19
Ta metoda jest stara: datagridview ma właściwość: ContextMenu. Menu kontekstowe zostanie otwarte, gdy tylko operator kliknie prawym przyciskiem myszy. Odpowiednie zdarzenie ContextMenuOpening daje możliwość zdecydowania, co pokazać w zależności od bieżącej komórki lub wybranych komórek. Zobacz jedną z innych odpowiedzi
Harald Coppoolse
4
Aby uzyskać odpowiednie koordynaty ekranu, należy otworzyć menu kontekstowe w następujący sposób:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes.
jak dodać funkcję do elementów menu?
Alpha Gabriel V. Timbol
89

Chociaż to pytanie jest stare, odpowiedzi nie są poprawne. Menu kontekstowe mają własne zdarzenia w DataGridView. Istnieje zdarzenie dla menu kontekstowego wiersza i menu kontekstowego komórki.

Powodem, dla którego te odpowiedzi nie są właściwe, jest to, że nie uwzględniają one różnych schematów działania. Opcje ułatwień dostępu, połączenia zdalne lub portowanie Metro / Mono / Web / WPF mogą nie działać, a skróty klawiaturowe nie działają poprawnie (Shift + F10 lub klawisz Menu kontekstowe).

Wybór komórek prawym przyciskiem myszy musi być obsługiwany ręcznie. Wyświetlanie menu kontekstowego nie musi być obsługiwane, ponieważ obsługuje to interfejs użytkownika.

To całkowicie naśladuje podejście stosowane przez Microsoft Excel. Jeśli komórka należy do wybranego zakresu, zaznaczenie komórek nie zmienia się i nie CurrentCell. Jeśli tak nie jest, stary zakres jest czyszczony, a komórka jest zaznaczona i staje się CurrentCell.

Jeśli nie masz pewności co do tego, CurrentCelljest to miejsce, w którym klawiatura ma fokus po naciśnięciu klawiszy strzałek. Selectedjest to, czy jest częścią SelectedCells. Menu kontekstowe pojawi się po kliknięciu prawym przyciskiem myszy, tak jak jest obsługiwane przez interfejs użytkownika.

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

Skróty klawiaturowe domyślnie nie wyświetlają menu kontekstowego, więc musimy je dodać.

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

Przeprojektowałem ten kod, aby działał statycznie, więc możesz skopiować i wkleić go do dowolnego wydarzenia.

Kluczem jest użycie, CellContextMenuStripNeededponieważ da ci to menu kontekstowe.

Oto przykład, w CellContextMenuStripNeededktórym możesz określić, które menu kontekstowe ma być wyświetlane, jeśli chcesz mieć różne menu w wierszu.

W tym kontekście MultiSelectjest Truei SelectionModejest FullRowSelect. To tylko przykład, a nie ograniczenie.

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
źródło
5
+1 za wyczerpującą odpowiedź i za rozważenie dostępności (oraz za odpowiedź na pytanie sprzed 3 lat)
gt
3
Zgoda, jest to znacznie lepsze niż akceptowane (chociaż nie ma nic złego w żadnym z nich) - i jeszcze więcej pochwał za włączenie obsługi klawiatury, coś, o czym wielu ludzi po prostu nie myśli.
Richard Moss,
2
Świetna odpowiedź, daje pełną elastyczność: różne menu kontekstowe w zależności od tego, co kliknięto. I dokładnie zachowanie EXCEL
Harald Coppoolse,
2
Nie jestem fanem tej metody, ponieważ z moim prostym DataGridView nie używam źródła danych ani trybu wirtualnego. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Użyj CellMouseDownwydarzenia na DataGridView. Na podstawie argumentów obsługi zdarzeń można określić, która komórka została kliknięta. Za pomocą PointToClient()metody w DataGridView można określić względne położenie wskaźnika do DataGridView, dzięki czemu można wyświetlić menu w prawidłowej lokalizacji.

( DataGridViewCellMouseEventParametr podaje tylko Xi Ywzględem klikniętej komórki, co nie jest tak łatwe w użyciu, aby wyświetlić menu kontekstowe).

To jest kod, którego użyłem, aby uzyskać pozycję myszy, a następnie dostosuj położenie DataGridView:

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

Cały program obsługi zdarzeń wygląda następująco:

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
źródło
1
Możesz również użyć (sender as DataGridView)[e.ColumnIndex, e.RowIndex];do prostszego połączenia z komórką.
Qsiris,
Zaznaczona odpowiedź nie działa poprawnie na wielu ekranach, ale ta odpowiedź działa.
Furkan Ekinci
45
  • Umieść menu kontekstowe w formularzu, nazwij go, ustaw podpisy itp. Za pomocą wbudowanego edytora
  • Połącz go ze swoją siatką za pomocą właściwości grid ContextMenuStrip
  • Utwórz dla swojej siatki zdarzenie do obsłużenia CellContextMenuStripNeeded
  • Args imprez e posiada użyteczne właściwości e.ColumnIndex, e.RowIndex.

Wierzę, że o e.RowIndexto właśnie prosisz.

Sugestia: gdy użytkownik spowoduje CellContextMenuStripNeededuruchomienie zdarzenia , użyj go, e.RowIndexaby pobrać dane z sieci, takie jak identyfikator. Zapisz identyfikator jako element tagu zdarzenia menu.

Teraz, gdy użytkownik faktycznie kliknie pozycję menu, użyj właściwości Sender, aby pobrać tag. Użyj tagu zawierającego swój identyfikator, aby wykonać wymaganą czynność.

ActualRandy
źródło
5
Nie mogę tego wystarczająco głosować. Inne odpowiedzi były dla mnie oczywiste, ale mogłem powiedzieć, że było więcej wbudowanej obsługi menu kontekstowych (i nie tylko dla DataGrid). To jest poprawna odpowiedź.
Jonathan Wood
1
@ActualRandy, jak uzyskać tag, gdy użytkownik kliknie rzeczywiste menu kontekstowe? pod zdarzeniem CellcontexMenustripNeeded mam coś takiego contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Ta odpowiedź jest prawie gotowa, jednak sugerowałbym, aby NIE łączyć menu kontekstowego z właściwością siatki ContextMenuStrip. Zamiast tego wewnątrz CellContextMenuStripNeededprocedury obsługi zdarzenia if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}oznacza to, że menu jest wyświetlane tylko po kliknięciu prawym przyciskiem myszy prawidłowego wiersza (tj. Nie w nagłówku ani w pustym obszarze siatki)
James S.
Jako komentarz do tej bardzo CellContextMenuStripNeededpomocnej odpowiedzi: działa tylko wtedy, gdy twój DGV jest powiązany ze źródłem danych lub jeśli jego VirtualMode jest ustawiony na true. W innych przypadkach trzeba będzie ustawić ten tag w CellMouseDownzdarzeniu. Aby być po bezpiecznej stronie, wykonaj DataGridView.HitTestInfow programie obsługi zdarzeń MouseDown, aby sprawdzić, czy jesteś w komórce.
LocEngineer
6

Po prostu przeciągnij składnik ContextMenu lub ContextMenuStrip do formularza i wizualnie go zaprojektuj, a następnie przypisz go do właściwości ContextMenu lub ContextMenuStrip żądanej kontrolki.

Kapitan Komiks
źródło
4

Wykonaj kroki:

  1. Utwórz menu kontekstowe, takie jak: Przykładowe menu kontekstowe

  2. Aby wyświetlić to menu, użytkownik musi kliknąć wiersz prawym przyciskiem myszy. Musimy obsłużyć zdarzenie _MouseClick i zdarzenie _CellMouseDown.

selectedBiodataid to zmienna zawierająca wybrane informacje o wierszu.

Oto kod:

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

        }
    }
}

a wynikiem będzie:

Wynik końcowy

Kshitij Jhangra
źródło
3

Jeśli chodzi o pozycję menu kontekstowego, y znalazłem problem polegający na tym, że potrzebowałem, aby było ono względne w stosunku do DataGridView, a zdarzenie, którego potrzebowałem, daje punktację względem klikniętej komórki. Nie znalazłem lepszego rozwiązania, więc zaimplementowałem tę funkcję w klasie commons, więc dzwonię z dowolnego miejsca.

Jest dość przetestowany i działa dobrze. Mam nadzieję, że uznasz to za przydatne.

    /// <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
źródło