Ошибка выбора CheckBox DataGridView

В нашем приложении есть список элементов, отображаемых в DataGridView. Первый столбец — это DataGridViewCheckBoxColumn. Мы хотим, чтобы наше приложение позволяло пользователю щелкнуть в любом месте строки, чтобы установить флажок в первом столбце.

Мы обнаружили, что если пользователь нажимает непосредственно на 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, так как я не получил там ответов.)


person Todd Hoatson    schedule 01.09.2020    source источник


Ответы (1)


Я предлагаю эти изменения (протестировано с .Net Framework 4.8):

  1. Не используйте событие CheckBox CheckedChanged: оно будет мешать событию CellValueChanged, когда оно попытается изменить состояние флажка Выбрать все. Вместо этого используйте событие Click.
    Это также позволит избавиться от всех этих вещей, связанных с добавлением/удалением обработчиков.

  2. Вызовите RefreshEdit() для обновления состояние ячейки флажка, как только ячейка будет нажата: это немедленно обновит значение флажка (это проблема, которую вы видите, когда щелкаете внутри области ячейки вместо содержимого флажка: элемент управления не обновляется сразу).
    Дополнительные сведения см. в примечаниях здесь:
    Программно проверьте флажок DataGridView. это было просто не проверено

  3. Удалите этот CommitEdit(DataGridViewDataErrorContexts.Commit);: если вам нужно немедленно обновить значение, вызовите DataGridView.EndEdit() (см. также примечания по этому поводу).

Вот как это работает сейчас:

Событие DataGridView BeginInvoke CellClick

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);
}
person Jimi    schedule 01.09.2020
comment
Спасибо, @Jimi, за продолжение... 1. Я думаю, это то, чему вы научились на собственном опыте, а не из документации... 2. Понятно 3. Я вижу вызов CommitEdit() в CurrentCellDirtyStateChanged() в примере кода, указанном для MS Docs CommitEdit() - person Todd Hoatson; 02.09.2020
comment
Добро пожаловать :) -- 3. EndEdit() можно использовать, как показано в коде, который я связал, когда нет режима редактирования для сохранения, напротив, мы хотим немедленно зафиксировать без дальнейших церемоний. -- Если у вас есть вопрос, связанный с использованием BeginInvoke() здесь, я могу написать об этом заметку. - person Jimi; 02.09.2020