Ошибка выбора DataGridView CheckBox
В нашем приложении есть список элементов, отображаемых в DataGridView. Первый столбец - это DataGridViewCheckBoxColumn. Мы хотим, чтобы наше приложение позволяло пользователю щелкать в любом месте строки, чтобы выбрать CheckBox в первом столбце.
Мы обнаружили, что если пользователь нажимает непосредственно на CheckBox, выбор / отмена выбора работает хорошо. То же самое верно, если пользователь щелкает данные в других столбцах.
Однако, если пользователь щелкает только одну сторону флажка, мы получаем странное поведение. CheckBox в этой строке не выбран / не выбран, но часто выбирается другая строка. Чтобы получить более четкое представление о том, что происходит, вы можете посмотреть мое короткое видео с ошибками.
Я попытался установить некоторые точки останова в коде, например, в нашем обработчике SelectionChanged, нашем обработчике CellClick и нашем обработчике CellValueChanged. Я обнаружил, что эти точки останова используются по одному и тому же шаблону, независимо от того, нажимаю ли я на CheckBox, только с одной стороны от флажка или по данным в других столбцах.
Кто-нибудь видел подобное поведение? Есть идеи, что может происходить? Это ошибка в коде.NET DataGridView или есть что-то, что я должен искать в нашем коде?
Вот соответствующий код в соответствии с запросом (или вы можете загрузить ZIP-файл с полным решением)...
Из Form1.cs:
public Form1()
{
InitializeComponent();
dgsControl.SetUp();
}
Из Form1.Designer.cs:
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.dgsControl = new DGSelection();
this.Controls.Add(this.dgsControl);
//
// dgsControl
//
this.dgsControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgsControl.Location = new System.Drawing.Point(3, 3);
this.dgsControl.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.dgsControl.Name = "dgsControl";
this.dgsControl.Size = new System.Drawing.Size(689, 325);
this.dgsControl.TabIndex = 0;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "DataGridView Demo";
}
Из DGSelection.cs:
public partial class DGSelection : UserControl
{
#region Member variables
private class ListData
{
public string Option;
public string Description;
}
private static readonly List<ListData> TestData = new List<ListData>
{
new ListData { Option = "Option1", Description = "Description1" },
new ListData { Option = "Option2", Description = "Description2" },
new ListData { Option = "Option3", Description = "Description3" },
new ListData { Option = "Option4", Description = "Description4" }
};
public event EventHandler OptionsChanged;
#endregion
#region Constructor
public DGSelection()
{
InitializeComponent();
dgvTable.BackgroundColor = Color.DarkGray;
dgvTable.DefaultCellStyle.BackColor = Color.DarkGray;
dgvTable.DefaultCellStyle.ForeColor = Color.Black;
dgvTable.ColumnHeadersDefaultCellStyle.BackColor = Color.DarkGray;
dgvTable.ColumnHeadersDefaultCellStyle.ForeColor = Color.Black;
dgvTable.GridColor = Color.DarkGray;
cbxCheckAll.BackColor = Color.DarkGray;
// Move label where it belongs (moved elsewhere in Designer for ease of editing).
lbl_empty.Top = Top + 5;
}
#endregion
#region Public Methods
public void SetUp()
{
dgvTable.Rows.Clear();
cbxCheckAll.Checked = false;
bool anyRows = TestData.Any();
lbl_empty.Visible = !anyRows;
cbxCheckAll.Visible = anyRows;
dgvTable.ColumnHeadersVisible = anyRows;
foreach (ListData ld in TestData)
{
dgvTable.Rows.Add(false, ld.Option, ld.Description);
}
}
#endregion
#region Event Handlers
private void DGSelection_SelectionChanged(object sender, EventArgs e)
{
dgvTable.ClearSelection();
}
private void cbxCheckAll_CheckedChanged(object sender, EventArgs e)
{
try
{
dgvTable.CellValueChanged -= DgvTableCellValueChanged;
bool checkAll = cbxCheckAll.Checked;
foreach (DataGridViewRow row in dgvTable.Rows)
row.Cells[0].Value = checkAll;
}
finally
{
dgvTable.CellValueChanged += DgvTableCellValueChanged;
}
OptionsChanged?.Invoke(this, EventArgs.Empty);
}
private void DGSelection_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0)
return; // Ignore clicks in the header row
DataGridViewCell checkBoxCell = dgvTable.Rows[e.RowIndex].Cells[0];
checkBoxCell.Value = !(bool)checkBoxCell.Value;
}
private void DgvTableCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
try
{
cbxCheckAll.CheckedChanged -= cbxCheckAll_CheckedChanged;
cbxCheckAll.CheckedChanged -= cbxCheckAll_CheckedChanged;
// Not sure why, but sometimes subscribed twice
bool checkAll = dgvTable.Rows.Count > 0;
foreach (DataGridViewRow row in dgvTable.Rows)
checkAll &= row.Cells[0].Value.Equals(true);
cbxCheckAll.Checked = checkAll;
}
finally
{
cbxCheckAll.CheckedChanged += cbxCheckAll_CheckedChanged;
}
OptionsChanged?.Invoke(this, EventArgs.Empty);
}
private void DGSelection_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
dgvTable.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
#endregion
}
Из DGSelection.Designer.cs:
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
this.dgvTable = new System.Windows.Forms.DataGridView();
this.colCheckboxes = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.colText1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colText2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.cbxCheckAll = new System.Windows.Forms.CheckBox();
this.lbl_empty = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.dgvTable)).BeginInit();
this.SuspendLayout();
//
// dgvTable
//
this.dgvTable.AllowUserToAddRows = false;
this.dgvTable.AllowUserToDeleteRows = false;
this.dgvTable.AllowUserToResizeColumns = false;
this.dgvTable.AllowUserToResizeRows = false;
this.dgvTable.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;
this.dgvTable.BackgroundColor = System.Drawing.SystemColors.Window;
this.dgvTable.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.dgvTable.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None;
this.dgvTable.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.ControlDark;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle1.Padding = new System.Windows.Forms.Padding(3);
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dgvTable.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
this.dgvTable.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvTable.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colCheckboxes,
this.colText1,
this.colText2 });
this.dgvTable.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvTable.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter;
this.dgvTable.EnableHeadersVisualStyles = false;
this.dgvTable.Location = new System.Drawing.Point(0, 0);
this.dgvTable.Margin = new System.Windows.Forms.Padding(2);
this.dgvTable.MultiSelect = false;
this.dgvTable.Name = "dgvTable";
this.dgvTable.RowHeadersVisible = false;
this.dgvTable.RowTemplate.Height = 24;
this.dgvTable.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.dgvTable.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.CellSelect;
this.dgvTable.Size = new System.Drawing.Size(484, 318);
this.dgvTable.TabIndex = 0;
this.dgvTable.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DGSelection_CellClick);
this.dgvTable.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.DgvTableCellValueChanged);
this.dgvTable.CurrentCellDirtyStateChanged += new System.EventHandler(this.DGSelection_CurrentCellDirtyStateChanged);
this.dgvTable.SelectionChanged += new System.EventHandler(this.DGSelection_SelectionChanged);
//
// colCheckboxes
//
this.colCheckboxes.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
this.colCheckboxes.Frozen = true;
this.colCheckboxes.HeaderText = "";
this.colCheckboxes.Name = "colCheckboxes";
this.colCheckboxes.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.colCheckboxes.Width = 30;
//
// colText1
//
this.colText1.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
dataGridViewCellStyle2.Padding = new System.Windows.Forms.Padding(5, 0, 0, 0);
this.colText1.DefaultCellStyle = dataGridViewCellStyle2;
this.colText1.HeaderText = "Option";
this.colText1.Name = "colText1";
this.colText1.ReadOnly = true;
this.colText1.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.colText1.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.colText1.Width = 57;
//
// colText2
//
this.colText2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
dataGridViewCellStyle3.Padding = new System.Windows.Forms.Padding(5, 0, 0, 0);
this.colText2.DefaultCellStyle = dataGridViewCellStyle3;
this.colText2.HeaderText = "Description";
this.colText2.Name = "colText2";
this.colText2.ReadOnly = true;
this.colText2.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.colText2.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// cbxCheckAll
//
this.cbxCheckAll.AutoSize = true;
this.cbxCheckAll.BackColor = System.Drawing.SystemColors.ControlDark;
this.cbxCheckAll.Location = new System.Drawing.Point(8, 5);
this.cbxCheckAll.Margin = new System.Windows.Forms.Padding(2);
this.cbxCheckAll.Name = "cbxCheckAll";
this.cbxCheckAll.Size = new System.Drawing.Size(15, 14);
this.cbxCheckAll.TabIndex = 1;
this.cbxCheckAll.UseVisualStyleBackColor = false;
this.cbxCheckAll.CheckedChanged += new System.EventHandler(this.cbxCheckAll_CheckedChanged);
//
// lbl_empty
//
this.lbl_empty.Anchor = ((System.Windows.Forms.AnchorStyles)
(((System.Windows.Forms.AnchorStyles.Top |
System.Windows.Forms.AnchorStyles.Left) |
System.Windows.Forms.AnchorStyles.Right)));
this.lbl_empty.BackColor = System.Drawing.Color.Transparent;
this.lbl_empty.Location = new System.Drawing.Point(3, 25);
this.lbl_empty.Name = "lbl_empty";
this.lbl_empty.Size = new System.Drawing.Size(478, 44);
this.lbl_empty.TabIndex = 2;
this.lbl_empty.Text = "No data defined for the list";
this.lbl_empty.Visible = false;
//
// DGSelection
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.lbl_empty);
this.Controls.Add(this.cbxCheckAll);
this.Controls.Add(this.dgvTable);
this.Margin = new System.Windows.Forms.Padding(2);
this.Name = "DGSelectionControl";
this.Size = new System.Drawing.Size(484, 318);
((System.ComponentModel.ISupportInitialize)(this.dgvTable)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
Есть ли что-то в нашем коде, что вызывает такое поведение? Или это ошибка в реализации DataGridView? Я воспроизвел это с.NET Framework v.4.6 и v.4.8.
(Примечание: репост с форума вопросов и ответов Microsoft, так как я не получил там ответов.)
1 ответ
Я предлагаю эти изменения (протестировано с.Net Framework 4.8):
Не используйте CheckBox
CheckedChanged
событие: это будет мешатьCellValueChanged
при попытке изменить состояние флажка Select All. ИспользоватьClick
событие вместо этого.
Это также позволит избавиться от всего, что связано с добавлением обработчика / удалением обработчика.Вызовите RefreshEdit(), чтобы обновить состояние ячейки CheckBox, как только будет нажата ячейка: это немедленно обновит значение CheckBox (это проблема, которую вы видите при нажатии внутри области ячейки вместо содержимого CheckBox: элемент управления не обновляется сразу).
Для получения дополнительной информации см. Примечания здесь:
Программно проверьте DataGridView CheckBox, который только что был снятУдалить это
CommitEdit(DataGridViewDataErrorContexts.Commit);
: если вам нужно немедленно обновить значение, вызовите вместо этого метод DataGridView.EndEdit() (см. также эти примечания по этому поводу).
Вот как это работает сейчас:
private void cbxCheckAll_Click(object sender, EventArgs e)
{
if (dgvTable.Rows.Count == 0) return;
try {
bool checkAll = cbxCheckAll.Checked;
foreach (DataGridViewRow row in dgvTable.Rows) {
row.Cells[0].Value = checkAll;
}
}
finally {
OptionsChanged?.Invoke(this, EventArgs.Empty);
}
}
private void DGSelection_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0) return;
bool currentValue = (bool)dgvTable[0, e.RowIndex].Value;
dgvTable[0, e.RowIndex].Value = !currentValue;
}
private void DgvTableCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (!dgvTable.IsHandleCreated) return;
cbxCheckAll.Checked = dgvTable.Rows.OfType<DataGridViewRow>().All(r => (bool)r.Cells[0].Value == true);
dgvTable.BeginInvoke(new Action(() => dgvTable.RefreshEdit()));
OptionsChanged?.Invoke(this, EventArgs.Empty);
}