DataGridViewComboBoxCell с разными записями, выбранными в другом DataGridViewComboBoxCell

Я боролся с проблемой наличия двух столбцов DataGridViewComboBoxCell в сетке данных. При изменении элемента в столбце 1 в данной строке я хочу, чтобы соответствующая ячейка в столбце 2 изменила элементы выпадающего списка. Ниже я прикрепил очень простой скрипт для такого диалога, см. прикрепленное изображение.

По умолчанию в столбце 2 нет записей. Теперь, когда вы выполните следующую последовательность

  1. В столбце 1 выберите «1», это вызовет событие col1Changed и обновит элементы ячейки в столбце 2.
  2. В столбце 2 выберите «пункт 1».
  3. Попробуйте выбрать что-нибудь в столбце 1.

Это вызывает следующее исключение:

В DataGridView произошло следующее исключение: System.ArgumentException: значение DataGridViewComboBoxCell недопустимо. Чтобы заменить это диалоговое окно по умолчанию, обработайте событие DataError.

Как я могу избежать этого исключения?

Спасибо

Смотрите мой код для этой проблемы ниже:

void initaliseGrid()
{
    string[] itemsCol1 = new string[] { "1", "2", "3" };
    DataGridViewComboBoxColumn col1 = new DataGridViewComboBoxColumn();
    col1.DropDownWidth = 200;
    col1.Width = 200;
    col1.MaxDropDownItems = 3;
    col1.HeaderText = "col 1";
    col1.ReadOnly = false;
    dataGridView1.Columns.Insert(0, col1);
    col1.Items.AddRange(itemsCol1);

    DataGridViewComboBoxColumn col2 = new DataGridViewComboBoxColumn();
    col2.DropDownWidth = 200;
    col2.Width = 200;
    col2.MaxDropDownItems = 3;
    col2.HeaderText = "col 2";
    col2.ReadOnly = true;
    dataGridView1.Columns.Insert(1, col2);

}

int col1Idx = 0;
int col2Idx = 1;

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    // Check if the curent cell is combobox
    if (dataGridView1.CurrentCell.ColumnIndex == colqIdx && e.Control is ComboBox)
    {
        ComboBox comboBox = e.Control as ComboBox;
        // Add an event when combobox index is changed
        comboBox.SelectedIndexChanged += col1Changed;
    }
}

private void col1Changed(object sender, EventArgs e)
{
    var currentcell = dataGridView1.CurrentCellAddress;
    if (currentcell.X == col1Idx)
    {
        // Read the selected item
        string selectedItem = ((ComboBox)sender).SelectedItem.ToString();
        // Get the cellfrom column 2
        DataGridViewComboBoxCell comboBox = (DataGridViewComboBoxCell)dataGridView1.Rows[currentcell.X].Cells[col2Idx];
        if (selectedItem == "1")
        {
            comboBox.Items.Clear();
            comboBox.Items.Add("item1");
            comboBox.Items.Add("item2");
            comboBox.ReadOnly = false;
        }
        else if (selectedItem == "2")
        {
            comboBox.Items.Clear();
            comboBox.Items.Add("item3");
            comboBox.Items.Add("item4");
            comboBox.ReadOnly = false;
        }
        else
        {
            comboBox.Items.Clear();
            comboBox.ReadOnly = true;
        }
    }
}

В dataGridView1_EditingControlShowing есть переменная colqIdx без номера. Должно быть col1Idx. (Код не компилируется)

Olivier Jacot-Descombes 02.09.2024 16:54

Я также получаю исключение с нулевой ссылкой в ​​string selectedItem = ((ComboBox)sender).SelectedItem.ToString();. Измените его на (string)((ComboBox)sender).SelectedItem;. И замените X на Y здесь .Rows[currentcell.Y].

Olivier Jacot-Descombes 02.09.2024 17:06

В событии EditingControlShowing сетки необходимо удалить обработчик события ComboBox.SelectedIndexChanged, прежде чем добавлять его снова. Или назначьте элемент управления редактированием (ComboBox) полю класса, чтобы удалить обработчик в событии DGV.CellEndEdit (если оно не равно нулю) и установить для него значение null.

dr.null 02.09.2024 18:35

Кроме того, в качестве альтернативы реализуйте событие CellValueChanged (которое вызывается при изменении выбора CB), если e.ColumnIndex == 0, получите выбранный Value, приведите ячейку col2 текущей строки к DataGridViewComboBoxCell и заполните/привяжите ее Items/DataSource соответственно.

dr.null 02.09.2024 18:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
62
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я попытаюсь визуализировать проблему с отображением значений столбца 2 на каждом этапе.

  1. Вы выбираете значение 1 в ячейке [0,0]. Вторая ячейка [0,1] имеет значение {item1, item2}.
  2. Вы выбираете {item1} в ячейке. Список позади по-прежнему {item1, item2}
  3. Вы выбираете значение 2 в ячейке [0,0]. Вторая ячейка [0,1] имеет значение {item3, item4}. Однако значение ячейки [0,1] по-прежнему равно {item1}, что больше не является допустимым выбором!

Чтобы решить эту проблему, просто сбросьте значение ячейки во втором столбце, прежде чем изменять элементы поля со списком, установив comboBox.Value = null. Однако я бы посоветовал вам прочитать о привязке данных. Изменение структур данных вручную может привести к ошибкам.

private void col1Changed(object sender, EventArgs e)
{
    var currentcell = dataGridView1.CurrentCellAddress;
    if (currentcell.X == col1Idx)
    {
        // Read the selected item
        string selectedItem = ((ComboBox)sender).SelectedItem.ToString();
        // Get the cellfrom column 2
        DataGridViewComboBoxCell comboBox = (DataGridViewComboBoxCell)dataGridView1.Rows[currentcell.X].Cells[col2Idx];
        comboBox.Value = null;
        if (selectedItem == "1")
        {
            comboBox.Items.Clear();
            comboBox.Items.Add("item1");
            comboBox.Items.Add("item2");
            comboBox.ReadOnly = false;
        }
        else if (selectedItem == "2")
        {
            comboBox.Items.Clear();
            comboBox.Items.Add("item3");
            comboBox.Items.Add("item4");
            comboBox.ReadOnly = false;
        }
        else
        {
            comboBox.Items.Clear();
            comboBox.ReadOnly = true;
        }
    }
}

Спасибо, это действительно работает. Обратной стороной является то, что событие col1Changed инициируется на шаге 3, как только выбирается стрелка раскрывающегося списка и значение в столбце 2 очищается. Это означает, что если вы выберете что-то в ячейке [0, 1], а затем захотите что-то изменить в ячейке [0, 0], ваше значение в ячейке [0, 1] будет сброшено (установлено на ноль), прежде чем вы выберете что-либо в ячейке [0, 1]. ячейка [0, 0]. Я думал, что проблема может быть в привязке, есть ли другой способ добиться того же эффекта с помощью привязки?

kmrydlx 02.09.2024 16:59

Вместо этого привяжитесь к событию CellEndEdit. Это сработает только в том случае, если вы закончите редактирование в ячейке [0,0].

Aaron 02.09.2024 17:03
SelectedItem.ToString() бросает НРЕ. Вместо этого напишите (string)((ComboBox)sender).SelectedItem. Используйте Y вместо X для индекса Rows: Rows[currentcell.Y]. Кроме того, Clear(); является общим для всех трех случаев. Переместите его перед if.
Olivier Jacot-Descombes 02.09.2024 17:08

Как можно привязать CellEndEdit, если событие нельзя передать в ComboBox ComboBox comboBox = e.Control as ComboBox;? Извините, вместо Y должен был быть X.

kmrydlx 02.09.2024 17:16

Событие CellEndEdit не запускается сразу, когда я меняю выбранный индекс в ячейке [0, 0]

kmrydlx 02.09.2024 17:34

Мое грязное решение проблемы показано ниже для col1Changed. По сути, я сохраняю текущее значение ячейки во временной переменной, и, если возможно, это значение следует восстановить. Я не на 100% доволен этим, но это лучшее, что я мог придумать.

private void col1Changed(object sender, EventArgs e)
{
    var currentcell = dataGridView1.CurrentCellAddress;
    if (currentcell.X == col1Idx)
    {
        // Read the selected item
        string selectedItem = (string)((ComboBox)sender).SelectedItem;
        // Get the cellfrom column 2
        DataGridViewComboBoxCell comboBox = (DataGridViewComboBoxCell)dataGridView1.Rows[currentcell.Y].Cells[col2Idx];
        // Store the current value in the cell
        string tempValue = (string)dataGridView1.Rows[currentcell.Y].Cells[col2Idx].Value;
        comboBox.Value = null;
        comboBox.Items.Clear();
        if (selectedItem == "1")
        {
            comboBox.Items.Add("item1");
            comboBox.Items.Add("item2");
            comboBox.ReadOnly = false;
        }
        else if (selectedItem == "2")
        {
            comboBox.Items.Add("item3");
            comboBox.Items.Add("item4");
            comboBox.ReadOnly = false;
        }
        else
        {
            comboBox.ReadOnly = true;
        }
        if (tempValue != null && comboBox.Items.Contains(tempValue))
        {
            // Restore the current value in the cell
            comboBox.Value = tempValue;
        }
        else if (comboBox.Items.Count > 0)
        {
            // Set the cell value to the first item from the list
            comboBox.Value = (string)comboBox.Items[0];
        }
    }
}

Обновлено:

Еще более аккуратное решение — проверить, изменилось ли значение поля со списком, используя следующее: if (selectedItem == (string)dataGridView1.Rows[currentcell.Y].Cells[col1Idx].Value)

private void col1Changed(object sender, EventArgs e)
{
    var currentcell = dataGridView1.CurrentCellAddress;
    if (currentcell.X == col1Idx)
    {
        // Read the selected item
        string selectedItem = (string)((ComboBox)sender).SelectedItem;
        // Get the cell from column 2
        DataGridViewComboBoxCell comboBox = (DataGridViewComboBoxCell)dataGridView1.Rows[currentcell.Y].Cells[col2Idx];
        // Check if the combobox value has changed
        if (selectedItem == (string)dataGridView1.Rows[currentcell.Y].Cells[col1Idx].Value)
            return;
        comboBox.Value = null;
        comboBox.Items.Clear();
        if (selectedItem == "1")
        {
            comboBox.Items.Add("item1");
            comboBox.Items.Add("item2");
            comboBox.ReadOnly = false;
        }
        else if (selectedItem == "2")
        {
            comboBox.Items.Add("item3");
            comboBox.Items.Add("item4");
            comboBox.ReadOnly = false;
        }
        else
        {
            comboBox.ReadOnly = true;
        }
        if (comboBox.Items.Count > 0)
        {
            // Set the cell value to the first item from the list
            comboBox.Value = (string)comboBox.Items[0];
        }
    }
}
Ответ принят как подходящий

Мне удалось легко воспроизвести ваше исключение DataError, и основная причина в том, что при каждом обновлении DataGridView он пытается синхронизировать отображаемое значение с одним из доступных элементов в поле со списком. Но если вы измените доступные параметры в поле со списком, он больше не сможет найти отображаемое значение и выдаст исключение DataError.

Насколько я понимаю, вам хотелось бы иметь разные варианты для разных значений индекса, например, вот так:

    enum OptionsOne { Select, Bumble, Twinkle, }
    enum OptionsTwo { Select, Whisker, Quibble, }
    enum OptionsThree { Select, Wobble, Flutter, }


Чтобы решить проблему Refresh, вы можете поэкспериментировать с использованием привязки для подавления любых изменений значения Option, если входящее значение не является доступным в данный момент параметром. Вот пример связанного класса записи:

class Record : INotifyPropertyChanged
{
    static int _recordCount = 1;
    public string Description { get; set; } = $"Record {_recordCount++}";

    // By initially setting index to '1' and Available options
    // to `OptionsOne` every record is born in sync with itself.
    public int Index
    {
        get => _index;
        set
        {
            if (!Equals(_index, value))
            {
                _index = value;
                switch (Index)
                {
                    case 1: AvailableOptions = Enum.GetNames(typeof(OptionsOne)); break;
                    case 2: AvailableOptions = Enum.GetNames(typeof(OptionsTwo)); break;
                    case 3: AvailableOptions = Enum.GetNames(typeof(OptionsThree)); break;
                }
                if (Option is string currentOption && !AvailableOptions.Contains(currentOption))
                {
                    Option = AvailableOptions[0];
                    OnPropertyChanged(nameof(Option));
                }
                OnPropertyChanged();
            }
        }
    }
    int _index = 1;

    [Browsable(false)]
    public string[] AvailableOptions { get; set; } = Enum.GetNames(typeof(OptionsOne));
    public string? Option
    {
        get => _option;
        set
        {
            if (!Equals(_option, value))
            {
                if (Option is string currentOption && AvailableOptions.Contains(value))
                {
                    _option = value;
                    OnPropertyChanged();
                }
            }
        }
    }
    string? _option = $"{OptionsOne.Select}";

    public virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    public event PropertyChangedEventHandler? PropertyChanged;
}


Второстепенная проблема заключается в том, что вы можете изменить значение Index, и теперь выбранное в данный момент значение больше не является допустимым доступным выбором, поэтому другой аспект этой привязки заключается в том, что запись будет сброшена до безопасного «общего» значения Select, если это произойдет. .


Единственное, что осталось сделать, это убедиться, что доступные варианты выбора отслеживают выбранный элемент. Это можно сделать в обработчике события CurrentCellChanged, как показано в этой процедуре инициализации:

public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();
    protected override void OnLoad(EventArgs e)
    {
        int replaceIndex;
        DataGridViewComboBoxColumn cbCol;

        base.OnLoad(e);
        dataGridView.DataSource = Records; 
        dataGridView.Columns[nameof(Record.Description)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

        // Index column
        cbCol = new DataGridViewComboBoxColumn
        {
            Name = nameof(Record.Index),
            DataSource = new int[] {1,2,3},
            DataPropertyName = nameof(Record.Index),
        };
        replaceIndex = dataGridView.Columns[nameof(Record.Index)].Index;
        dataGridView.Columns.RemoveAt(replaceIndex);
        dataGridView.Columns.Insert(replaceIndex, cbCol);

        // Option column
        cbCol = new DataGridViewComboBoxColumn
        {
            Name = nameof(Record.Option),
            DataSource = Enum.GetNames<OptionsOne>(),
            DataPropertyName = nameof(Record.Option),
        };
        replaceIndex = dataGridView.Columns[nameof(Record.Option)].Index;
        dataGridView.Columns.RemoveAt(replaceIndex);
        dataGridView.Columns.Insert(replaceIndex, cbCol);
        dataGridView.CurrentCellChanged += (sender, e) =>
        {
            var cbCol = ((DataGridViewComboBoxColumn)dataGridView.Columns[nameof(Record.Option)]);
            if (dataGridView.CurrentCell is DataGridViewComboBoxCell cbCell && cbCell.ColumnIndex == cbCol.Index)
            {
                var record = Records[dataGridView.CurrentCell.RowIndex];
                cbCell.DataSource = record.AvailableOptions;
            }
        };
        dataGridView.DataError += (sender, e) =>
        {
            Debug.Fail("We don't expect this to happen anymore!");
        };
        // Consider 'not' allowing the record to be dirty after a CB select.
        dataGridView.CurrentCellDirtyStateChanged += (sender, e) =>
        {
            if (dataGridView.CurrentCell is DataGridViewComboBoxCell)
            {
                if (dataGridView.IsCurrentCellDirty)
                {
                    BeginInvoke(()=> dataGridView.EndEdit(DataGridViewDataErrorContexts.Commit));
                }
            }
        };
        Records.Add(new Record());
        Records.Add(new Record());
        Records.Add(new Record());
    }
    BindingList<Record> Records { get; } = new BindingList<Record>();
}

Еще одна тонкость. В исходном коде изменение выбора CB сделает запись грязной и незафиксированной. Обрабатывая событие CurrentCellDirtyStateChanged, вы можете сразу же выполнить коммит, если хотите.

Вот код, который я использовал для проверки этого ответа. Клон Конечно, дайте мне знать, если обнаружите какие-либо проблемы с ним.

IV. 03.09.2024 16:00

Это прекрасно, именно то, о чем я просил, огромное спасибо! В своем проекте я использую C# v 7.3, поэтому мне придется заменить некоторые использованные вами хаки, например, ссылочные типы, допускающие значение NULL, но это нормально.

kmrydlx 03.09.2024 18:11

Спасибо за отзыв! Дайте мне знать, если возникнут какие-либо проблемы с переносом на 7.3, и я также хотел сообщить вам о немного более удобном способе сделать это, который пришел мне в голову, поэтому я поместил его в репозиторий как альтернативную ветку с именем bind-when-list-changes vis-à-vis. вот этот bind-when-cell-selection-changes. Вопрос, который вы задаете в своем посте, хороший, по моему мнению ▲.

IV. 03.09.2024 18:18

Другие вопросы по теме