Значение обновления winform datagridview во время CellValueChanged

Я создаю такое приложение:

В каждой строке таблицы:

  • Пользователь может отметить или снять отметку со значения в столбцах Option 1 и Option 2.
    После этого значение флажка в столбце All option будет обновлено до:
    - "Проверено": если оба Option 1 и Option 2 отмечены
    - «Не отмечено»: если обе Option 1 и Option 2 не отмечены
    - "Неопределенный": другие случаи

  • Пользователь может отметить или снять отметку со значения в столбце All option.
    После этого оба значения Option 1 и Option 2 будут обновлены на основе текущего значения All option.
    В этом случае пользователь не может изменить значение столбца All option на неопределенное (только переключаться между отмеченным и не отмеченным).

Я реализую вышеуказанное приложение, используя DataGridView в Winforms. Ниже приведен мой код:

public partial class Form1 : Form
    {
        DataTable dataTable;

        public Form1()
        {
            InitializeComponent();

            dataTable = new DataTable();
            dataTable.Columns.Add("Item");
            dataTable.Columns.Add("All option", typeof(CheckState));
            dataTable.Columns.Add("Option 1", typeof(bool));
            dataTable.Columns.Add("Option 2", typeof(bool));
            dataTable.Rows.Add("Item 1", CheckState.Unchecked, false, false);
            dataTable.Rows.Add("Item 2", CheckState.Unchecked, false, false);
            dataGrid.DataSource = dataTable;

        }

        private void dataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
            if (dataGrid.CurrentCell is DataGridViewCheckBoxCell)
            {
                dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
            }
        }

        bool isUnderUpdateAll = false;

        private void dataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            if ((dataGrid.CurrentCell is DataGridViewCheckBoxCell) == false) return;

            // This is column "All option"
            if (e.ColumnIndex == 1)
            {
                // Avoid stackoverflow exception
                if (isUnderUpdateAll) return;

                CheckState allOption = (CheckState)dataGrid["All option", e.RowIndex].Value;
                
                // Don't allow user select 'indeterminate' value
                if (allOption == CheckState.Indeterminate)
                {
                    allOption = CheckState.Unchecked;

                    // These code didn't work
                    dataTable.Rows[e.RowIndex]["All option"] = CheckState.Unchecked;
                    dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
                    dataGrid.UpdateCellValue(e.ColumnIndex, e.RowIndex);
                    dataGrid.NotifyCurrentCellDirty(true);
                    dataGrid.Refresh();
                    dataGrid.Update();

                }

                dataGrid["Option 1", e.RowIndex].Value = allOption;
                dataGrid["Option 2", e.RowIndex].Value = allOption;
            }

            // This is column "Option 1" or "Option 2"
            else
            {
                bool option1 = (bool)dataGrid["Option 1", e.RowIndex].Value;
                bool option2 = (bool)dataGrid["Option 2", e.RowIndex].Value;

                isUnderUpdateAll = true;
                dataGrid["All option", e.RowIndex].Value = (option1 && option2) ? CheckState.Checked 
                    : (!option1 && !option2 ? CheckState.Unchecked : CheckState.Indeterminate );
                isUnderUpdateAll = false;
            }
        }
    }

Код вроде работает, но есть один незавершенный момент: пользователь все еще может переключиться в неопределенное состояние в столбце All option. В моем коде я уже добавляю много вещей, например:

// Don't allow user select 'indeterminate' value
if (allOption == CheckState.Indeterminate)
{
    allOption = CheckState.Unchecked;

    // These code didn't work
    dataTable.Rows[e.RowIndex]["All option"] = CheckState.Unchecked;
    dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
    dataGrid.UpdateCellValue(e.ColumnIndex, e.RowIndex);
    dataGrid.NotifyCurrentCellDirty(true);
    dataGrid.Refresh();
    dataGrid.Update();

}

Но эти коды не работали. При нажатии на флажок All option состояние по-прежнему переключается между «Проверено» -> «Неопределенно» -> «Не отмечено» -> «Проверено» -> ...
Итак, может ли кто-нибудь помочь подсказать, как убрать неопределенное состояние сверху?
Я хочу, чтобы это было: Checked -> Unchecked -> Checked -> Unchecked...

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
114
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте установить для свойства CheckBox.ThreeState значение false.

((DataGridViewCheckBoxCell)(dataGrid.CurrentCell)).ThreeState = false;
Ответ принят как подходящий

Я не уверен, согласен ли я с решением @Kyle Wang. Когда вы устанавливаете ThreeState текущей ячейки на false, то в основном это НЕ допустит «неопределенного» состояния. Это поведение, которое вы хотите, когда пользователь устанавливает флажки «Все варианты», однако это НЕ то поведение, которое вы хотите, когда меняете флажки «Вариант 1» или «Вариант 2».

Допустим, пользователь нажимает на флажок «Все варианты» и меняет его значение на true. В этот момент для ячеек «ThreeState» установлено значение false, поэтому состояние Indeterminate не подходит. Затем позже пользователь изменяет флажок «Вариант 1» или «Вариант 2», чтобы один был true, а другой false… затем код должен будет изменить свойство ThreeState ячейки «Все варианты» обратно на true, чтобы установите его в состояние Indeterminate в соответствии с вашими требованиями. Я вижу кошмар, отслеживая состояние каждой ячейки «Все варианты».

Кроме того, вы можете быть осторожны, «какие» события вы выбираете для выполнения определенных действий. Например, похоже, что код связывает два события сетки: CurrentCellDirtyStateChanged и CellValueChanged. Это совершенно верно и будет работать, однако может помочь увидеть, как часто эти события срабатывают. Я не проверял это, так как уверен, что если вы покажете, сколько раз срабатывали события, вы обнаружите, что они срабатывают гораздо чаще, чем вы ожидаете. В моем примере ниже при тестировании событий полезно визуально увидеть, сколько раз события срабатывают. Я рекомендую вам попробовать это в вашем текущем коде.

Учитывая это, кажется очевидным, что «лучшим» событием для этого является событие сетки CellValueChanged. К сожалению, это событие не срабатывает, пока пользователь не «покинет» ячейку. С помощью флажка мы хотим, чтобы событие запускалось немедленно, когда пользователь нажимает на флажок. Одним из событий, которое может помочь в этом, является событие сетки CellContentClick. Это событие сработает, как только пользователь нажмет на «флажок». Имейте в виду, что он НЕ сработает, если пользователь нажмет на флажок «ячейка», но НЕ сам «флажок».

Одна из проблем с использованием этого события заключается в том, когда оно срабатывает. Он запускается ДО того, как флажок «Значение» фактически изменится в сетке. К счастью, поскольку мы знаем, что это ячейка с флажком, мы можем продолжить и «зафиксировать» изменения сетки. Тогда мы БУДЕМ иметь фактическое конечное значение ячейки.

Если вы создадите новый проект «winform», поместите DataGridView и многострочный TextBox в форму (как показано ниже), вы сможете протестировать приведенный ниже код.

Я переместил весь код инициализации в событие формы Load, однако код идентичен, за исключением добавления пары разных строк.

Событие обхода сеток CellContentClick: код добавляет текст в текстовое поле, чтобы показать, что событие было запущено (как описано ранее). Затем код «Зафиксирует» изменения в ячейке. Затем оператор if, чтобы проверить, является ли измененная ячейка столбцом «Все варианты» или одним из столбцов «Параметры». Если измененная ячейка находится в столбце «Все параметры», то переменной allOption присваивается значение CheckState ячейки «Все параметры», отмеченное, не отмеченное или неопределенное.

Затем следует оператор switch для переменной allOption для каждого из состояний: проверено, не проверено или неопределенно. Если состояние изменено на true или false, то код просто изменяет обе «опции» на одно и то же состояние. Очевидно, нет необходимости менять состояние «Все опции».

Если текущее состояние ячейки «Все варианты» равно Indeterminate, то это означает, что один вариант верен, а другой — нет. Это «неопределенное» состояние было установлено одним из флажков других опций. Неважно, какая ячейка true или false, код просто меняет все состояния на неотмеченные. Примечание; поскольку мы меняем ячейку «Все варианты» в коде, И мы знаем, что ячейка уже переведена в состояние Indeterminate (чего мы не хотим), нам нужно обновить редактирование в сетке, чтобы изменить ячейку на «Непроверено». ", а не оставлять значение "Неопределенный".

Затем в части else выполняется проверка, является ли измененная ячейка одной из ячеек «Параметры», и если да, то код просто устанавливает значение флажка «Все параметры» на основе значений параметров. Здесь код может установить для ячейки «Все варианты» состояние «Неопределенное».

private void Form1_Load(object sender, EventArgs e) {
  dataTable = new DataTable();
  dataTable.Columns.Add("Item");
  dataTable.Columns.Add("All option", typeof(CheckState));
  dataTable.Columns.Add("Option 1", typeof(bool));
  dataTable.Columns.Add("Option 2", typeof(bool));
  dataTable.Rows.Add("Item 1", CheckState.Unchecked, false, false);
  dataTable.Rows.Add("Item 2", CheckState.Checked, true, true);
  dataTable.Rows.Add("Item 3", CheckState.Indeterminate, false, true);
  dataTable.Rows.Add("Item 4", CheckState.Indeterminate, true, false);
  dataGrid.DataSource = dataTable;
}

private void dataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  textBox1.Text += "CellContentClick" + Environment.NewLine;
  dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
  if (dataGrid.Columns[e.ColumnIndex].Name == "All option") {
    var allOption = (CheckState)dataGrid["All option", e.RowIndex].Value;
    switch (allOption) {
      case CheckState.Unchecked:
        dataGrid["Option 1", e.RowIndex].Value = CheckState.Unchecked;
        dataGrid["Option 2", e.RowIndex].Value = CheckState.Unchecked;
        break;
      case CheckState.Checked:
        dataGrid["Option 1", e.RowIndex].Value = CheckState.Checked;
        dataGrid["Option 2", e.RowIndex].Value = CheckState.Checked;
        break;
      case CheckState.Indeterminate:
        dataGrid["Option 1", e.RowIndex].Value = CheckState.Unchecked;
        dataGrid["Option 2", e.RowIndex].Value = CheckState.Unchecked;
        dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
        // we changed the current All option cell so we need to refresh the edit
        dataGrid.RefreshEdit();
        break;
    }
  }
  else {
    if (dataGrid.Columns[e.ColumnIndex].Name == "Option 1" ||
        dataGrid.Columns[e.ColumnIndex].Name == "Option 2") {
      var op1Checked = (bool)dataGrid["Option 1", e.RowIndex].Value;
      var op2Checked = (bool)dataGrid["Option 2", e.RowIndex].Value;
      if (op1Checked && op2Checked) {
        dataGrid["All option", e.RowIndex].Value = CheckState.Checked;
      }
      else {
        if (!op1Checked && !op2Checked) {
          dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
        }
        else {
          dataGrid["All option", e.RowIndex].Value = CheckState.Indeterminate;
        }
      }
    }
  }
}

Я надеюсь в этом есть смысл.

Привет @JohnG, я думаю, твое решение должно быть более простым, чем настройка ThreeState = false. Код проверил, вроде работает. Однако, когда мы очень быстро нажимаем на All option или Option 1 или Option 2 (становимся двойным щелчком), он не будет работать должным образом.

phibao37 10.12.2020 10:26

Я решил свой комментарий выше, установив событие this.dataGrid.CellContentDoubleClick тем же методом this.dataGrid_CellContentClick, все работает.

phibao37 10.12.2020 10:30

Я не буду утверждать, что когда пользователь «быстро» нажимает на флажок несколько раз, это может привести к путанице. Я бы подумал, что проблема больше в быстром нажатии и неспособности компьютера успевать, а не в коде. Держу пари, что другие события (типа щелчка) будут иметь аналогичное нежелательное поведение. Я проверю решение, которое может решить эту проблему, быстро изменив флажок.

JohnG 10.12.2020 10:34

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