контекстное меню правой кнопки мыши для просмотра данных

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 () в string.Format необязательно.
MS
19
Этот метод устарел: datagridview имеет свойство: ContextMenu. Контекстное меню откроется, как только оператор щелкнет правой кнопкой мыши. Соответствующее событие ContextMenuOpening дает вам возможность решить, что показывать в зависимости от текущей ячейки или выбранных ячеек. Посмотрите один из других ответов
Харальд Коппулс
4
Чтобы получить правильные координаты экрана, вы должны открыть контекстное меню следующим образом:m.Show(dataGridView1.PointToScreen(e.Location));
Olivier Jacot-Descombes
как добавить функцию к пунктам меню?
Альфа Габриэль В. Тимбол
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 за исчерпывающий ответ и за рассмотрение доступности (и за ответ на вопрос трехлетней давности)
gt
3
Согласны, это намного лучше, чем принято (хотя ни в одном из них нет ничего плохого) - и даже больше похвалы за включение поддержки клавиатуры, о чем многие люди, похоже, просто не думают.
Ричард Мосс
2
Отличный ответ, дает всю гибкость: разные контекстные меню в зависимости от того, что нажимается. И именно поведение EXCEL
Харальд Коппулс
2
Я не фанат этого метода, потому что в моем простом DataGridView я не использую источник данных или виртуальный режим. 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
  • Поместите контекстное меню в свою форму, назовите его, установите заголовки и т. Д. С помощью встроенного редактора
  • Свяжите его с сеткой, используя свойство grid ContextMenuStrip
  • Для вашей сетки создайте событие для обработки CellContextMenuStripNeeded
  • Args событий е имеет полезные свойства e.ColumnIndex, e.RowIndex.

Я считаю, что e.RowIndexэто то, о чем вы просите.

Предложение: когда пользователь запускает ваше событие CellContextMenuStripNeeded, используйте e.RowIndexдля получения данных из вашей сетки, таких как идентификатор. Сохраните идентификатор как элемент тега события меню.

Теперь, когда пользователь действительно щелкает ваш пункт меню, используйте свойство Sender для получения тега. Используйте тег, содержащий ваш ID, чтобы выполнить необходимое действие.

ActualRandy
источник
5
Я не могу проголосовать за это достаточно. Другие ответы были для меня очевидны, но я мог сказать, что было больше встроенной поддержки контекстных меню (и не только для DataGrid). Это правильный ответ.
Джонатан Вуд
1
@ActualRandy, как мне получить тег, когда пользователь щелкает фактическое контекстное меню? под событием CellcontexMenustripNeeded у меня есть что-то вроде этого contextMenuStrip1.Tag = e.RowIndex;
Эдвин Икечукву Оконкво
2
Этот ответ почти готов, однако я бы посоветовал вам НЕ связывать контекстное меню со свойством сетки ContextMenuStrip. Вместо этого внутри CellContextMenuStripNeededобработчика событий сделайте это. if(e.RowIndex >= 0){e.ContextMenuStrip = yourContextMenuInstance;}Это будет означать, что меню отображается только при щелчке правой кнопкой мыши по допустимой строке (т.е. не в заголовке или пустой области сетки)
Джеймс С.
Как комментарий к этому очень полезному ответу: CellContextMenuStripNeededработает только в том случае, если ваш DGV привязан к источнику данных или если его VirtualMode установлен в значение true. В других случаях вам нужно будет установить этот тег в 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, а событие, которое мне нужно было использовать, дает отравление по отношению к нажатой ячейке. Я не нашел лучшего решения, поэтому реализовал эту функцию в классе commons, поэтому вызываю ее откуда угодно.

Он достаточно проверен и работает хорошо. Надеюсь, вы сочтете это полезным.

    /// <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
источник