คลิกขวาที่เมนูบริบทสำหรับ datagridview


117

ฉันมี datagridview ในแอป. NET winform ฉันต้องการคลิกขวาที่แถวและมีเมนูปรากฏขึ้น จากนั้นฉันต้องการเลือกสิ่งต่างๆเช่นคัดลอกตรวจสอบความถูกต้อง ฯลฯ

ฉันจะสร้าง A) เมนูที่แสดงขึ้นได้อย่างไร B) ค้นหาว่าแถวใดถูกคลิกขวา ฉันรู้ว่าฉันสามารถใช้ selectedIndex ได้ แต่ฉันควรจะสามารถคลิกขวาได้โดยไม่ต้องเปลี่ยนสิ่งที่เลือก? ตอนนี้ฉันสามารถใช้ดัชนีที่เลือกได้ แต่ถ้ามีวิธีรับข้อมูลโดยไม่เปลี่ยนสิ่งที่เลือกนั่นก็จะเป็นประโยชน์

คำตอบ:


143

คุณสามารถใช้ CellMouseEnter และ CellMouseLeave เพื่อติดตามหมายเลขแถวที่เมาส์กำลังวางเมาส์เหนือ

จากนั้นใช้ออบเจ็กต์ ContextMenu เพื่อแสดงเมนูป๊อปอัพที่คุณกำหนดเองสำหรับแถวปัจจุบัน

นี่คือตัวอย่างที่รวดเร็วและสกปรกของสิ่งที่ฉันหมายถึง ...

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
แก้ไข! และหมายเหตุสำหรับคุณ var r = dataGridView1.HitTest (eX, eY); r RowIndex ใช้งานได้ดีขึ้นจากนั้นใช้เมาส์หรือ currentMouseOverRow

3
ใช้. toString () ในสตริงรูปแบบโดยไม่จำเป็น
MS

19
วิธีนี้เก่าแล้ว: datagridview มีคุณสมบัติ: ContextMenu เมนูบริบทจะเปิดขึ้นทันทีที่โอเปอเรเตอร์คลิกขวา เหตุการณ์ ContextMenuOpening ที่สอดคล้องกันช่วยให้คุณมีโอกาสตัดสินใจว่าจะแสดงอะไรขึ้นอยู่กับเซลล์ปัจจุบันหรือเซลล์ที่เลือก ดูคำตอบอื่น ๆ
Harald Coppoolse

4
เพื่อให้ได้ผู้ประสานงานหน้าจอที่ถูกต้องคุณควรเปิดเมนูบริบทดังนี้m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes

ฉันจะเพิ่มฟังก์ชันในเมนูได้อย่างไร?
Alpha Gabriel V. Timbol

89

แม้ว่าคำถามนี้จะเก่า แต่คำตอบก็ไม่ถูกต้อง เมนูบริบทมีเหตุการณ์ของตัวเองบน DataGridView มีเหตุการณ์สำหรับเมนูบริบทแถวและเมนูบริบทของเซลล์

เหตุผลที่คำตอบเหล่านี้ไม่ถูกต้องคือไม่ได้อธิบายถึงแผนการดำเนินการที่แตกต่างกัน ตัวเลือกการช่วยการเข้าถึงการเชื่อมต่อระยะไกลหรือการพอร์ต Metro / Mono / Web / WPF อาจไม่ทำงานและแป้นพิมพ์ลัดจะล้มเหลวทันที (Shift + F10 หรือปุ่มเมนูบริบท)

การเลือกเซลล์ด้วยการคลิกเมาส์ขวาจะต้องจัดการด้วยตนเอง การแสดงเมนูบริบทไม่จำเป็นต้องจัดการเนื่องจาก UI จัดการ

สิ่งนี้เลียนแบบวิธีการที่ใช้โดย Microsoft Excel อย่างสมบูรณ์ ถ้ามือถือเป็นส่วนหนึ่งของช่วงที่เลือก, CurrentCellการเลือกเซลล์ไม่เปลี่ยนแปลงและไม่ไม่ CurrentCellถ้ามันไม่ได้เป็นช่วงที่เก่าจะถูกล้างและเซลล์จะถูกเลือกและจะกลายเป็น

หากคุณไม่ชัดเจนในเรื่องนี้CurrentCellแป้นพิมพ์มีโฟกัสเมื่อคุณกดแป้นลูกศร ไม่ว่าจะเป็นส่วนหนึ่งของSelected SelectedCellsเมนูบริบทจะแสดงเมื่อคลิกขวาซึ่งจัดการโดย 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;
        }
    }
}

แป้นพิมพ์ลัดจะไม่แสดงเมนูบริบทตามค่าเริ่มต้นดังนั้นเราจึงต้องเพิ่มเข้าไป

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

ฉันได้ปรับรหัสนี้ใหม่ให้ทำงานแบบคงที่แล้วดังนั้นคุณสามารถคัดลอกและวางลงในเหตุการณ์ใดก็ได้

กุญแจสำคัญคือการใช้CellContextMenuStripNeededเนื่องจากจะทำให้คุณมีเมนูบริบท

นี่คือตัวอย่างการใช้CellContextMenuStripNeededที่คุณสามารถระบุว่าจะแสดงเมนูบริบทใดหากคุณต้องการให้มีรายการที่แตกต่างกันต่อแถว

ในบริบทนี้MultiSelectคือTrueและเป็นSelectionMode FullRowSelectนี่เป็นเพียงตัวอย่างเท่านั้นไม่ใช่ข้อ จำกัด

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 สำหรับคำตอบที่ครอบคลุมและเพื่อพิจารณาความเป็นไปได้ (และสำหรับการตอบคำถามเก่าอายุ 3 ปี)
gt

3
เห็นด้วยนี่เป็นสิ่งที่ดีกว่าที่ยอมรับ (แม้ว่าจะไม่มีอะไรผิดปกติกับสิ่งเหล่านี้ก็ตาม) - และความรุ่งโรจน์ที่มากขึ้นสำหรับการรองรับแป้นพิมพ์สิ่งที่หลายคนดูเหมือนจะไม่นึกถึง
Richard Moss

2
คำตอบที่ยอดเยี่ยมให้ความยืดหยุ่นทั้งหมด: เมนูบริบทที่แตกต่างกันขึ้นอยู่กับสิ่งที่คลิก และพฤติกรรมของ EXCEL
Harald Coppoolse

2
ฉันไม่ใช่แฟนของวิธีนี้เพราะด้วย DataGridView แบบธรรมดาของฉันฉันไม่ได้ใช้แหล่งข้อมูลหรือโหมดเสมือน The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen

47

ใช้CellMouseDownเหตุการณ์บนDataGridView. จากอาร์กิวเมนต์ตัวจัดการเหตุการณ์คุณสามารถระบุได้ว่าเซลล์ใดถูกคลิก การใช้PointToClient()วิธีการบน DataGridView คุณสามารถกำหนดตำแหน่งสัมพัทธ์ของตัวชี้ไปยัง DataGridView เพื่อให้คุณสามารถเปิดเมนูในตำแหน่งที่ถูกต้องได้

( DataGridViewCellMouseEventพารามิเตอร์จะให้XและYสัมพันธ์กับเซลล์ที่คุณคลิกซึ่งไม่ใช่เรื่องง่ายที่จะใช้เพื่อเปิดเมนูบริบท)

นี่คือรหัสที่ฉันใช้ในการรับตำแหน่งเมาส์จากนั้นปรับตำแหน่งของ DataGridView:

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

ตัวจัดการเหตุการณ์ทั้งหมดมีลักษณะดังนี้:

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
คุณยังสามารถใช้(sender as DataGridView)[e.ColumnIndex, e.RowIndex];สำหรับการโทรไปยังเซลล์ที่ง่ายขึ้น
Qsiris

คำตอบที่เลือกไม่ทำงานอย่างถูกต้องในหลายหน้าจอ แต่คำตอบนี้ใช้ได้
Furkan Ekinci

45
  • ใส่เมนูบริบทในแบบฟอร์มของคุณตั้งชื่อตั้งคำบรรยาย ฯลฯ โดยใช้โปรแกรมแก้ไขในตัว
  • เชื่อมโยงกับกริดของคุณโดยใช้คุณสมบัติกริด ContextMenuStrip
  • สำหรับกริดของคุณให้สร้างเหตุการณ์ที่จะจัดการ CellContextMenuStripNeeded
  • Event Args e มีคุณสมบัติที่เป็นประโยชน์e.ColumnIndex, e.RowIndex.

ฉันเชื่อว่านั่นe.RowIndexคือสิ่งที่คุณขอ

คำแนะนำ: เมื่อผู้ใช้ทำให้เหตุการณ์ของคุณCellContextMenuStripNeededเริ่มทำงานใช้e.RowIndexเพื่อรับข้อมูลจากกริดของคุณเช่น ID จัดเก็บ ID เป็นรายการแท็กของเหตุการณ์ในเมนู

ตอนนี้เมื่อผู้ใช้คลิกรายการเมนูของคุณจริงๆให้ใช้คุณสมบัติผู้ส่งเพื่อดึงแท็ก ใช้แท็กที่มี ID ของคุณเพื่อดำเนินการตามที่คุณต้องการ


5
ฉันไม่สามารถโหวตได้มากพอ คำตอบอื่น ๆ นั้นชัดเจนสำหรับฉัน แต่ฉันสามารถบอกได้ว่ามีการรองรับเมนูบริบทในตัวมากขึ้น (ไม่ใช่เฉพาะสำหรับ DataGrid) นี่คือคำตอบที่ถูกต้อง
Jonathan Wood

1
@ActualRandy ฉันจะรับแท็กได้อย่างไรเมื่อผู้ใช้คลิกเมนูบริบทจริง ภายใต้เหตุการณ์ CellcontexMenustripNeeded ฉันมีบางอย่างเช่นนั้น contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo

2
คำตอบนี้เกือบจะอยู่ที่นั่นแล้วอย่างไรก็ตามฉันขอแนะนำให้คุณอย่าเชื่อมโยงเมนูบริบทกับคุณสมบัติกริด ContextMenuStrip แทนที่จะCellContextMenuStripNeededทำในตัวจัดการเหตุการณ์if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}ซึ่งหมายความว่าเมนูจะแสดงเฉพาะเมื่อคลิกขวาที่แถวที่ถูกต้องเท่านั้น (กล่าวคือไม่อยู่ในส่วนหัวหรือพื้นที่ตารางว่าง)
James S

เช่นเดียวกับความคิดเห็นสำหรับคำตอบที่เป็นประโยชน์นี้: CellContextMenuStripNeededใช้ได้เฉพาะเมื่อ DGV ของคุณถูกผูกไว้กับแหล่งข้อมูลหรือถ้า VirtualMode ถูกตั้งค่าเป็นจริง ในกรณีอื่นคุณจะต้องตั้งค่าแท็กนั้นในCellMouseDownเหตุการณ์ หากต้องการอยู่ในด้านที่ปลอดภัยให้ดำเนินการDataGridView.HitTestInfoในตัวจัดการเหตุการณ์ MouseDown เพื่อตรวจสอบว่าคุณอยู่ในเซลล์
LocEngineer

6

เพียงลากคอมโพเนนต์ ContextMenu หรือ ContextMenuStrip ลงในแบบฟอร์มของคุณและออกแบบด้วยสายตาจากนั้นกำหนดให้กับคุณสมบัติ ContextMenu หรือ ContextMenuStrip ของตัวควบคุมที่คุณต้องการ


5

ทำตามขั้นตอน:

  1. สร้างเมนูตามบริบทเช่น: เมนูบริบทตัวอย่าง

  2. ผู้ใช้ต้องคลิกขวาที่แถวเพื่อรับเมนูนี้ เราจำเป็นต้องจัดการเหตุการณ์ _MouseClick และเหตุการณ์ _CellMouseDown

selectedBiodataid เป็นตัวแปรที่มีข้อมูลแถวที่เลือก

นี่คือรหัส:

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

        }
    }
}

และผลลัพธ์จะเป็น:

ผลลัพธ์สุดท้าย


3

สำหรับตำแหน่งสำหรับเมนูบริบท y พบปัญหาที่ฉันต้องการให้มันสัมพันธ์กับ DataGridView และเหตุการณ์ที่ฉันต้องใช้จะให้ poistion ที่สัมพันธ์กับเซลล์ที่คลิก ฉันไม่พบวิธีแก้ปัญหาที่ดีกว่าดังนั้นฉันจึงใช้ฟังก์ชันนี้ในคลาสคอมมอนส์ดังนั้นฉันจึงเรียกใช้ฟังก์ชันนี้จากทุกที่ที่ต้องการ

มันค่อนข้างทดสอบและใช้งานได้ดี ฉันหวังว่าคุณพบว่ามีประโยชน์.

    /// <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);
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.