nhấp chuột phải vào menu ngữ cảnh cho datagridview


116

Tôi có chế độ xem dữ liệu trong ứng dụng .NET winform. Tôi muốn nhấp chuột phải vào một hàng và có một menu bật lên. Sau đó, tôi muốn chọn những thứ như sao chép, xác thực, v.v.

Làm cách nào để tạo A) menu bật lên B) tìm hàng nào được nhấp chuột phải. Tôi biết tôi có thể sử dụng selectIndex nhưng tôi có thể nhấp chuột phải mà không thay đổi những gì được chọn? ngay bây giờ tôi có thể sử dụng chỉ mục đã chọn nhưng nếu có cách nào để lấy dữ liệu mà không thay đổi những gì được chọn thì điều đó sẽ hữu ích.

Câu trả lời:


143

Bạn có thể sử dụng CellMouseEnter và CellMouseLeave để theo dõi số hàng mà chuột hiện đang di chuột qua.

Sau đó, sử dụng đối tượng ContextMenu để hiển thị menu bật lên của bạn, được tùy chỉnh cho hàng hiện tại.

Đây là một ví dụ nhanh chóng và bẩn thỉu về ý tôi ...

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
Chính xác! và một lưu ý cho bạn, var r = dataGridView1.HitTest (eX, eY); r.RowIndex làm việc cách tốt hơn sau đó sử dụng chuột hoặc currentMouseOverRow

3
sử dụng .ToString () trong string.Format là không cần thiết.
MS

19
Phương thức này đã cũ: một datagridview có thuộc tính: ContextMenu. Menu ngữ cảnh sẽ được mở ngay sau khi người điều khiển nhấp chuột phải. Sự kiện ContextMenuOpening tương ứng mang đến cho bạn cơ hội quyết định những gì sẽ hiển thị tùy thuộc vào ô hiện tại hoặc các ô đã chọn. Xem một trong những câu trả lời khác
Harald Coppoolse.

4
Để có được coordiantes màn hình bên phải, bạn nên mở menu ngữ cảnh như thế này:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes

làm cách nào để thêm một chức năng vào menuitem?
Alpha Gabriel V. Timbol

89

Mặc dù câu hỏi này đã cũ, nhưng câu trả lời không phù hợp. Các menu ngữ cảnh có các sự kiện riêng trên DataGridView. Có một sự kiện cho menu ngữ cảnh hàng và menu ngữ cảnh ô.

Lý do mà những câu trả lời này không phù hợp là chúng không giải thích cho các chương trình hoạt động khác nhau. Các tùy chọn trợ năng, kết nối từ xa hoặc cổng Metro / Mono / Web / WPF có thể không hoạt động và các phím tắt sẽ không hoạt động (Shift + F10 hoặc phím Trình đơn ngữ cảnh).

Việc chọn ô khi nhấp chuột phải phải được xử lý thủ công. Việc hiển thị menu ngữ cảnh không cần được xử lý vì việc này do giao diện người dùng xử lý.

Điều này hoàn toàn bắt chước cách tiếp cận được sử dụng bởi Microsoft Excel. Nếu một ô là một phần của dải ô đã chọn, thì lựa chọn ô không thay đổi và cũng không thay đổi CurrentCell. Nếu không, phạm vi cũ sẽ bị xóa và ô được chọn và trở thành CurrentCell.

Nếu bạn không rõ về điều này, CurrentCellthì bàn phím có tiêu điểm khi bạn nhấn các phím mũi tên. Selectedlà liệu nó có phải là một phần của SelectedCells. Menu ngữ cảnh sẽ hiển thị khi nhấp chuột phải do giao diện người dùng xử lý.

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

Các phím tắt không hiển thị menu ngữ cảnh theo mặc định, vì vậy chúng tôi phải thêm chúng vào.

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

Tôi đã làm lại mã này để hoạt động tĩnh, vì vậy bạn có thể sao chép và dán chúng vào bất kỳ sự kiện nào.

Điều quan trọng là sử dụng CellContextMenuStripNeededvì nó sẽ cung cấp cho bạn menu ngữ cảnh.

Đây là một ví dụ sử dụng CellContextMenuStripNeedednơi bạn có thể chỉ định menu ngữ cảnh nào sẽ hiển thị nếu bạn muốn có các menu khác nhau trên mỗi hàng.

Trong bối cảnh MultiSelectnày là TrueSelectionModeđang FullRowSelect. Đây chỉ là một ví dụ và không phải là một giới hạn.

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 để có câu trả lời toàn diện và để xem xét khả năng tiếp nhận (và để trả lời một câu hỏi cũ 3 năm)
gt

3
Đồng ý, điều này tốt hơn nhiều so với điều được chấp nhận (mặc dù không có gì thực sự sai với bất kỳ thứ gì trong số chúng) - và thậm chí nhiều kudo hơn để bao gồm hỗ trợ bàn phím, điều mà nhiều người dường như không nghĩ đến.
Richard Moss

2
Câu trả lời tuyệt vời, mang lại sự linh hoạt: các menu ngữ cảnh khác nhau tùy thuộc vào những gì được nhấp vào. Và chính xác là hành vi EXCEL
Harald Coppoolse

2
Tôi không phải là người yêu thích phương pháp này vì với DataGridView đơn giản của tôi, tôi không sử dụng nguồn dữ liệu hoặc mã ảo. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Arvo Bowen,

47

Sử dụng CellMouseDownsự kiện trên DataGridView. Từ các đối số của trình xử lý sự kiện, bạn có thể xác định ô nào đã được nhấp. Sử dụng PointToClient()phương pháp trên DataGridView, bạn có thể xác định vị trí tương đối của con trỏ tới DataGridView, vì vậy bạn có thể bật lên menu ở vị trí chính xác.

( DataGridViewCellMouseEventTham số chỉ cung cấp cho bạn XYliên quan đến ô bạn đã nhấp vào, không dễ sử dụng để bật lên menu ngữ cảnh.)

Đây là mã tôi đã sử dụng để lấy vị trí chuột, sau đó điều chỉnh vị trí của DataGridView:

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

Toàn bộ trình xử lý sự kiện trông như thế này:

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
Bạn cũng có thể sử dụng (sender as DataGridView)[e.ColumnIndex, e.RowIndex];cho một cuộc gọi đơn giản hơn đến ô.
Qsiris

Câu trả lời đã chọn không hoạt động chính xác trên nhiều màn hình nhưng câu trả lời này hoạt động.
Furkan Ekinci

45
  • Đặt menu ngữ cảnh trên biểu mẫu của bạn, đặt tên, đặt chú thích, v.v. bằng trình chỉnh sửa tích hợp sẵn
  • Liên kết nó với lưới của bạn bằng cách sử dụng thuộc tính lưới ContextMenuStrip
  • Đối với lưới của bạn, hãy tạo một sự kiện để xử lý CellContextMenuStripNeeded
  • Các tổ chức sự kiện args e có các tính chất hữu ích e.ColumnIndex, e.RowIndex.

Tôi tin rằng đó e.RowIndexlà những gì bạn đang yêu cầu.

Đề xuất: khi người dùng kích hoạt sự kiện của bạn CellContextMenuStripNeeded, hãy sử dụng e.RowIndexđể lấy dữ liệu từ lưới của bạn, chẳng hạn như ID. Lưu trữ ID dưới dạng mục thẻ của sự kiện menu.

Bây giờ, khi người dùng thực sự nhấp vào mục menu của bạn, hãy sử dụng thuộc tính Người gửi để tìm nạp thẻ. Sử dụng thẻ, chứa ID của bạn, để thực hiện hành động bạn cần.


5
Tôi không thể ủng hộ điều này đủ. Các câu trả lời khác là rõ ràng đối với tôi nhưng tôi có thể nói rằng có nhiều hỗ trợ tích hợp hơn cho các menu ngữ cảnh (và không chỉ cho DataGrid). Đây là câu trả lời chính xác.
Jonathan Wood

1
@ActualRandy, làm cách nào để lấy thẻ khi người dùng nhấp vào menu ngữ cảnh thực? trong sự kiện CellcontexMenustripNeeded, tôi có một cái gì đó giống như vậy contextMenuStrip1.Tag = e.RowIndex;
Edwin Ikechukwu Okonkwo

2
Câu trả lời này gần như có, tuy nhiên tôi khuyên bạn KHÔNG nên liên kết menu ngữ cảnh với thuộc tính lưới ContextMenuStrip. Thay vào đó, bên trong CellContextMenuStripNeededtrình xử lý sự kiện, hãy làm if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Điều này có nghĩa là menu chỉ được hiển thị khi nhấp chuột phải vào một hàng hợp lệ, (tức là không trên tiêu đề hoặc vùng lưới trống)
James S

Cũng giống như một nhận xét cho câu trả lời rất hữu ích này: CellContextMenuStripNeededchỉ hoạt động nếu DGV của bạn được liên kết với một nguồn dữ liệu hoặc nếu VirtualMode của nó được đặt thành true. Trong các trường hợp khác, bạn sẽ cần đặt thẻ đó trong CellMouseDownsự kiện. Để ở bên an toàn ở đó, hãy thực hiện DataGridView.HitTestInfotrong trình xử lý sự kiện MouseDown để kiểm tra xem bạn đang ở trong một ô.
LocEngineer

6

Chỉ cần kéo thành phần ContextMenu hoặc ContextMenuStrip vào biểu mẫu của bạn và thiết kế trực quan nó, sau đó gán nó vào thuộc tính ContextMenu hoặc ContextMenuStrip của điều khiển bạn muốn.


4

Làm theo các bước:

  1. Tạo một menu ngữ cảnh như: Menu ngữ cảnh mẫu

  2. Người dùng cần nhấp chuột phải vào hàng để nhận menu này. Chúng ta cần xử lý sự kiện _MouseClick và sự kiện _CellMouseDown.

Biodataid được chọn là biến chứa thông tin hàng đã chọn.

Đây là mã:

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

        }
    }
}

và đầu ra sẽ là:

Đầu ra cuối cùng


3

Đối với vị trí cho menu ngữ cảnh, y đã tìm thấy vấn đề mà tôi cần đặt nó liên quan đến DataGridView và sự kiện tôi cần sử dụng đưa ra độc tố liên quan đến ô được nhấp. Tôi đã không tìm thấy giải pháp tốt hơn vì vậy tôi đã triển khai hàm này trong lớp commons, vì vậy tôi gọi nó từ bất cứ nơi nào tôi cần.

Nó khá được thử nghiệm và hoạt động tốt. Tôi hy vọng bạn thấy nó hữu dụng.

    /// <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);
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.