menu konteks klik kanan untuk datagridview

116

Saya memiliki datagridview dalam aplikasi winform NET. Saya ingin mengklik kanan pada baris dan menampilkan menu. Kemudian saya ingin memilih hal-hal seperti salin, validasi, dll

Bagaimana cara membuat A) menu pop up B) menemukan baris mana yang diklik kanan. Saya tahu saya bisa menggunakan selectedIndex tetapi saya harus bisa mengklik kanan tanpa mengubah apa yang dipilih? sekarang saya dapat menggunakan indeks yang dipilih tetapi jika ada cara untuk mendapatkan data tanpa mengubah apa yang dipilih maka itu akan berguna.

kodkod.dll
sumber

Jawaban:

143

Anda dapat menggunakan CellMouseEnter dan CellMouseLeave untuk melacak nomor baris yang sedang di-arahkan mouse.

Kemudian gunakan objek ContextMenu untuk menampilkan menu popup Anda, disesuaikan untuk baris saat ini.

Inilah contoh cepat dan kotor dari apa yang saya maksud ...

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
sumber
6
Benar! dan catatan untuk Anda, var r = dataGridView1.HitTest (eX, eY); r.RowIndex bekerja JAUH LEBIH BAIK daripada menggunakan mouse atau currentMouseOverRow
3
menggunakan .ToString () dalam string.Format tidak perlu.
MS
19
Metode ini sudah lama: datagridview memiliki properti: ContextMenu. Menu konteks akan dibuka segera setelah operator mengklik kanan. Peristiwa ContextMenuOpening yang sesuai memberi Anda kesempatan untuk memutuskan apa yang akan ditampilkan bergantung pada sel saat ini atau sel yang dipilih. Lihat salah satu jawaban lainnya
Harald Coppoolse
4
Untuk mendapatkan koordinat layar yang tepat, Anda harus membuka menu konteks seperti ini:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
bagaimana cara menambahkan fungsi ke menuitems?
Alpha Gabriel V. Timbol
89

Meskipun pertanyaan ini sudah tua, jawabannya tidak tepat. Menu konteks memiliki acara mereka sendiri di DataGridView. Ada acara untuk menu konteks baris dan menu konteks sel.

Alasan mengapa jawaban ini tidak tepat adalah karena tidak memperhitungkan skema operasi yang berbeda. Opsi aksesibilitas, koneksi jarak jauh, atau port Metro / Mono / Web / WPF mungkin tidak berfungsi dan pintasan keyboard turun ke kanan gagal (tombol Shift + F10 atau Menu Konteks).

Pemilihan sel pada klik kanan mouse harus ditangani secara manual. Menampilkan menu konteks tidak perlu ditangani karena ini ditangani oleh UI.

Ini sepenuhnya meniru pendekatan yang digunakan oleh Microsoft Excel. Jika sel adalah bagian dari rentang yang dipilih, pemilihan sel tidak berubah dan tidak juga CurrentCell. Jika tidak, rentang lama dihapus dan sel dipilih dan menjadi CurrentCell.

Jika Anda tidak jelas tentang ini, di CurrentCellmana keyboard memiliki fokus saat Anda menekan tombol panah. Selectedadalah apakah itu bagian dari SelectedCells. Menu konteks akan ditampilkan pada klik kanan seperti yang ditangani oleh UI.

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

Pintasan keyboard tidak menampilkan menu konteks secara default, jadi kita harus menambahkannya.

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

Saya telah mengerjakan ulang kode ini agar berfungsi secara statis, sehingga Anda dapat menyalin dan menempelkannya ke acara apa pun.

Kuncinya adalah menggunakan CellContextMenuStripNeededkarena ini akan memberi Anda menu konteks.

Berikut adalah contoh penggunaan di CellContextMenuStripNeededmana Anda dapat menentukan menu konteks mana yang akan ditampilkan jika Anda ingin memiliki menu yang berbeda per baris.

Dalam konteks ini MultiSelectadalah Truedan SelectionModeadalah FullRowSelect. Ini hanya untuk contoh dan bukan batasan.

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
sumber
5
+1 untuk jawaban komprehensif dan untuk mempertimbangkan aksesibilitas (dan untuk menjawab pertanyaan lama 3 tahun)
gt
3
Setuju, ini jauh lebih baik daripada yang diterima (meskipun tidak ada yang benar-benar salah dengan semua itu) - dan bahkan lebih banyak pujian untuk menyertakan dukungan keyboard, sesuatu yang tampaknya tidak terpikirkan oleh banyak orang.
Richard Moss
2
Jawaban bagus, memberikan semua fleksibilitas: menu konteks berbeda tergantung pada apa yang diklik. Dan tepatnya perilaku EXCEL
Harald Coppoolse
2
Saya bukan penggemar metode ini karena dengan DataGridView sederhana saya, saya tidak menggunakan sumber data atau mode virtual. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen
47

Gunakan CellMouseDownacara di DataGridView. Dari argumen penanganan peristiwa, Anda dapat menentukan sel mana yang diklik. Dengan menggunakan PointToClient()metode pada DataGridView, Anda dapat menentukan posisi relatif penunjuk ke DataGridView, sehingga Anda dapat memunculkan menu di lokasi yang benar.

( DataGridViewCellMouseEventParameter hanya memberi Anda Xdan Yrelatif terhadap sel yang Anda klik, yang tidak mudah digunakan untuk memunculkan menu konteks.)

Ini adalah kode yang saya gunakan untuk mendapatkan posisi mouse, lalu sesuaikan dengan posisi DataGridView:

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

Seluruh penangan acara terlihat seperti ini:

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
sumber
1
Anda juga bisa menggunakan (sender as DataGridView)[e.ColumnIndex, e.RowIndex];untuk panggilan sederhana ke sel.
Qsiris
Jawaban yang dicentang tidak berfungsi dengan benar di beberapa layar tetapi jawaban ini berfungsi.
Furkan Ekinci
45
  • Letakkan menu konteks pada formulir Anda, beri nama, setel teks, dll. Menggunakan editor bawaan
  • Tautkan ke kisi Anda menggunakan properti kisi ContextMenuStrip
  • Untuk kisi Anda, buat acara untuk ditangani CellContextMenuStripNeeded
  • Event Args e memiliki sifat yang berguna e.ColumnIndex, e.RowIndex.

Saya percaya itulah e.RowIndexyang Anda minta.

Saran: ketika pengguna menyebabkan acara Anda CellContextMenuStripNeededdiaktifkan, gunakan e.RowIndexuntuk mendapatkan data dari grid Anda, seperti ID. Simpan ID sebagai item tag acara menu.

Sekarang, ketika pengguna benar-benar mengklik item menu Anda, gunakan properti Sender untuk mengambil tag. Gunakan tag, yang berisi ID Anda, untuk melakukan tindakan yang Anda butuhkan.

ActualRandy
sumber
5
Saya tidak bisa memberi suara positif cukup. Jawaban lain sudah jelas bagi saya, tetapi saya tahu bahwa ada lebih banyak dukungan bawaan untuk menu konteks (dan bukan hanya untuk DataGrid). Ini jawaban yang benar.
Jonathan Wood
1
@ActualRandy, bagaimana cara mendapatkan tag saat pengguna mengklik menu konteks yang sebenarnya? di bawah acara CellcontexMenustripNeeded, saya memiliki sesuatu seperti jadi contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo
2
Jawaban ini hampir selesai, namun saya sarankan Anda JANGAN menautkan menu konteks ke properti grid ContextMenuStrip. Alih-alih di dalam CellContextMenuStripNeededevent handler lakukan if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Ini berarti menu hanya ditampilkan saat mengklik kanan baris yang valid, (yaitu tidak pada heading atau area grid kosong)
James S
Sama seperti komentar untuk jawaban yang sangat membantu ini: CellContextMenuStripNeededhanya berfungsi jika DGV Anda terikat ke sumber data atau jika VirtualMode-nya disetel ke true. Dalam kasus lain, Anda perlu menyetel tag itu dalam CellMouseDownacara tersebut. Untuk amannya, lakukan DataGridView.HitTestInfodi event handler MouseDown untuk memastikan Anda berada di sel.
LocEngineer
6

Cukup seret komponen ContextMenu atau ContextMenuStrip ke dalam formulir Anda dan rancang secara visual, lalu tetapkan ke properti ContextMenu atau ContextMenuStrip dari kontrol yang Anda inginkan.

Kapten Komik
sumber
4

Ikuti langkah-langkahnya:

  1. Buat menu konteks seperti: Contoh menu konteks

  2. Pengguna perlu mengklik kanan pada baris untuk mendapatkan menu ini. Kita perlu menangani event _MouseClick dan event _CellMouseDown.

selectedBiodataid adalah variabel yang berisi informasi baris yang dipilih.

Ini kodenya:

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

        }
    }
}

dan hasilnya adalah:

Hasil akhir

Kshitij Jhangra
sumber
3

Untuk posisi menu konteks, y menemukan masalah yang saya perlukan agar relatif terhadap DataGridView, dan acara yang perlu saya gunakan memberikan poistion relatif terhadap sel yang diklik. Saya belum menemukan solusi yang lebih baik jadi saya mengimplementasikan fungsi ini di kelas commons, jadi saya menyebutnya dari mana pun saya butuhkan.

Ini cukup teruji dan bekerja dengan baik. Semoga bermanfaat.

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