Я создаю такое приложение:
В каждой строке таблицы:
Пользователь может отметить или снять отметку со значения в столбцах 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...
Попробуйте установить для свойства 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;
}
}
}
}
}
Я надеюсь в этом есть смысл.
Я решил свой комментарий выше, установив событие this.dataGrid.CellContentDoubleClick
тем же методом this.dataGrid_CellContentClick
, все работает.
Я не буду утверждать, что когда пользователь «быстро» нажимает на флажок несколько раз, это может привести к путанице. Я бы подумал, что проблема больше в быстром нажатии и неспособности компьютера успевать, а не в коде. Держу пари, что другие события (типа щелчка) будут иметь аналогичное нежелательное поведение. Я проверю решение, которое может решить эту проблему, быстро изменив флажок.
Привет @JohnG, я думаю, твое решение должно быть более простым, чем настройка
ThreeState = false
. Код проверил, вроде работает. Однако, когда мы очень быстро нажимаем наAll option
илиOption 1
илиOption 2
(становимся двойным щелчком), он не будет работать должным образом.