DataGridView CheckBox选择错误 [英] DataGridView CheckBox selection bug

查看:44
本文介绍了DataGridView CheckBox选择错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的应用程序具有在DataGridView中显示的项目列表.第一列是DataGridViewCheckBoxColumn.我们希望我们的应用程序允许用户单击行上的任何位置,以选择第一列中的CheckBox.

我们发现,如果用户直接单击CheckBox,则选择/取消选择效果很好.如果用户单击其他列中的数据,也是如此.

但是,如果用户仅单击复选框的一侧,则会出现奇怪的行为.该行中的CheckBox未被选中/取消选中,但通常会选择另一行.要更清楚地了解正在发生的事情,可以查看我的

  private void cbxCheckAll_Click(对象发送者,EventArgs e){如果(dgvTable.Rows.Count == 0)返回;尝试 {bool checkAll = cbxCheckAll.Checked;foreach(dgvTable.Rows中的DataGridViewRow行){row.Cells [0] .Value = checkAll;}}最后 {OptionsChanged?.Invoke(this,EventArgs.Empty);}}私有void DGSelection_CellClick(对象发送者,DataGridViewCellEventArgs e){如果(e.RowIndex< 0)返回;bool currentValue =(bool)dgvTable [0,e.RowIndex] .Value;dgvTable [0,e.RowIndex] .Value =!currentValue;}私有无效DgvTableCellValueChanged(对象发送者,DataGridViewCellEventArgs e){如果(!dgvTable.IsHandleCreated)返回;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);} 

Our app has a list of items displayed in a DataGridView. The first column is a DataGridViewCheckBoxColumn. We want our app to allow the user to click anywhere on the row as a way to select the CheckBox in the first column.

We find that if the user clicks directly on the CheckBox, selection/deselection works well. The same is true if the user clicks on the data in the other columns.

However, if the user clicks just to one side of the checkbox, we get strange behavior. The CheckBox in that row is not selected/deselected, but often another row is selected. To get a clearer picture of what is happening, you can check out my Short Video of the buggy behavior.

I tried setting some breakpoints in the code, for example, on our SelectionChanged handler, our CellClick handler and our CellValueChanged handler. I find these breakpoints are hit in the same pattern, regardless of whether I click on the CheckBox, just to one side of the checkbox or on the data in the other columns.

Has anyone seen behavior like this? Any ideas what may be going on? Is it a bug in the .NET DataGridView code, or is there something I should look for in our code?

Here is the relevant code, as requested (or you can download a ZIP file with the complete solution)...

From Form1.cs:

public Form1()
{
    InitializeComponent();
    dgsControl.SetUp();
}

From 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";
}

From 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
}

From 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();
}

Is there something in our code which is causing this behavior? Or is it a bug in the implementation of DataGridView? I have reproduced this with both .NET Framework v.4.6 & v.4.8.

(Note: reposted from Microsoft Q&A Forum, since I got no responses there.)

解决方案

I suggest these changes (tested with .Net Framework 4.8):

  1. Don't use the CheckBox CheckedChanged event: it will interfere with the CellValueChanged event when it tries to change the Select All CheckBox state. Use the Click event instead.
    This will also allow to get rid of all those add handler / remove handler things.

  2. Call RefreshEdit() to update the state of the CheckBox Cell as soon as the Cell is clicked: this will update the CheckBox value immediately (this is the problem you're seeing when clicking inside the Cell's area instead of the CheckBox content: the control is not updated right away).
    For more details, see the notes here:
    Programmatically check a DataGridView CheckBox that was just unchecked

  3. Remove that CommitEdit(DataGridViewDataErrorContexts.Commit);: if you need to update a value immediately, call the DataGridView.EndEdit() method instead (see those notes on this, too).

This is how it works now:

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);
}

这篇关于DataGridView CheckBox选择错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆