контекстне меню правою кнопкою миші для перегляду даних


116

У мене є перегляд даних у додатку .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 працює WAY BETTER потім за допомогою миші або currentMouseOverRow

3
використання .ToString () у string.Format є без необхідності.
MS

19
Цей метод є старим: datagridview має властивість: ContextMenu. Контекстне меню буде відкрито, як тільки оператор натисне правою кнопкою миші. Відповідна подія ContextMenuOpening дає вам можливість вирішити, що показати, залежно від поточної комірки або вибраних комірок. Дивіться одну з інших відповідей
Харальд Коппулз

4
Для того, щоб отримати потрібний координатор екрану, вам слід відкрити таке контекстне меню:m.Show(dataGridView1.PointToScreen(e.Location));
Олів'є Якот-Дескомб

як додати функцію до меню?
Альфа Габріель В. Тимбол

89

Хоча це питання старе, відповіді не є правильними. У контекстних меню є свої події в DataGridView. Існує подія для контекстного меню рядків та контекстного меню комірок.

Причина, по якій ці відповіді не відповідають правильності, полягає в тому, що вони не враховують різних операційних схем. Параметри доступності, віддалене підключення або перенесення Metro / Mono / Web / WPF можуть не працювати, і комбінації клавіш знижуватимуться правою помилкою (Shift + F10 або клавіша контекстного меню).

Вибір клітини правою кнопкою миші повинен оброблятися вручну. Показ контекстного меню не потрібно обробляти, оскільки цим керує інтерфейс користувача.

Це повністю імітує підхід, який використовує Microsoft Excel. Якщо комірка є частиною вибраного діапазону, вибір клітини не змінюється і не змінюється CurrentCell. Якщо це не так, старий діапазон очищається, а комірка вибирається та стає CurrentCell.

Якщо вам це не зрозуміло, це місце, CurrentCellде клавіатура фокусується при натисканні клавіш зі стрілками. Selectedчи є вона частиною SelectedCells. Контекстне меню відобразиться правою кнопкою миші, як обробляється користувальницьким інтерфейсом.

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
Погоджено, це набагато краще, ніж прийнято (хоча з жодним із них нічого поганого немає) - і навіть більше кудо для включення підтримки клавіатури, щось, про що багато людей, здається, просто не думають.
Річард Мосс

2
Чудова відповідь, дає всю гнучкість: різні контекстні меню залежно від того, що натиснуто. І саме поведінка EXCEL
Харальд Коппулз

2
Я не прихильник цього методу, тому що в моєму простому DataGridView я не використовую джерело даних або virtualmode. The CellContextMenuStripNeeded event occurs only when the DataGridView control DataSource property is set or its VirtualMode property is true.
Арво Боуен

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
  • Args подій е має корисні властивості e.ColumnIndex, e.RowIndex.

Я вважаю, що e.RowIndexсаме цього ви просите.

Рекомендація: коли користувач спричиняє вашу подію CellContextMenuStripNeeded, використовуйте e.RowIndexдля отримання даних із вашої сітки, наприклад ідентифікатора. Збережіть ідентифікатор як елемент тегу події меню.

Тепер, коли користувач фактично натискає ваш пункт меню, використовуйте властивість Відправник, щоб отримати тег. Використовуйте тег, що містить ваш ідентифікатор, для виконання необхідних дій.


5
Я не можу це досить схвалити. Інші відповіді були очевидними для мене, але я міг сказати, що вбудована підтримка контекстних меню (і не тільки для DataGrid). Це правильна відповідь.
Джонатан Вуд

1
@ActualRandy, як мені отримати тег, коли користувач натискає фактичне контекстне меню? під подією CellcontexMenustripNeeded у мене є щось на кшталт цього контекстуMenuStrip1.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 потрібного елемента управління.


4

Виконайте дії:

  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

Щодо позиції контекстного меню, ви знайшли проблему, що мені потрібно, щоб воно було відносно DataGridView, а подія, яку мені потрібно було використовувати, дає отруєння щодо клітини, на яку натиснули. Я не знайшов кращого рішення, тому я реалізував цю функцію в класі загального користування, тому я називаю її там, де мені потрібно.

Це досить перевірено і працює добре. Сподіваюся, вам це стане в нагоді.

    /// <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);
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.