DataSource 中的更新重置了 CheckedListBox 复选框

Updates in the DataSource reset CheckedListBox checkboxes

我有一个 CheckedListBox,绑定到一个 BindingList:

private BindingList<string> list = new BindingList<string>();
public MyForm()
{
    InitializeComponent();

    list.Add("A");
    list.Add("B");
    list.Add("C");
    list.Add("D");

    checkedListBox.DataSource = collection;
}

单击某个按钮时,列表会更新:

private void Button_Click(object sender, EventArgs e)
{
    list.Insert(0, "Hello!");
}

它工作正常,CheckedListBox 已更新。但是,当某些项目被选中时,单击按钮不仅会更新列表,还会将所有项目重置为未选中状态。我该如何解决?

谢谢!

您需要自己跟踪检查状态。

作为一种选择,您可以为包含文本和检查状态的项目创建模型 class。然后在 ItemCheck event of the control, set check state value of the item model. Also in ListChenged event of your BindingList<T> 刷新项目的检查状态。

例子

创建 CheckedListBoxItem class:

public class CheckedListBoxItem
{
    public CheckedListBoxItem(string text)
    {
        Text = text;
    }
    public string Text { get; set; }
    public CheckState CheckState { get; set; }
    public override string ToString()
    {
        return Text;
    }
}

像这样设置 CheckedListBox

private BindingList<CheckedListBoxItem> list = new BindingList<CheckedListBoxItem>();
private void Form1_Load(object sender, EventArgs e)
{
    list.Add(new CheckedListBoxItem("A"));
    list.Add(new CheckedListBoxItem("B"));
    list.Add(new CheckedListBoxItem("C"));
    list.Add(new CheckedListBoxItem("D"));
    checkedListBox1.DataSource = list;
    checkedListBox1.ItemCheck += CheckedListBox1_ItemCheck;
    list.ListChanged += List_ListChanged;
}
private void CheckedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
    ((CheckedListBoxItem)checkedListBox1.Items[e.Index]).CheckState = e.NewValue;
}
private void List_ListChanged(object sender, ListChangedEventArgs e)
{
    for (var i = 0; i < checkedListBox1.Items.Count; i++)
    {
        checkedListBox1.SetItemCheckState(i,
            ((CheckedListBoxItem)checkedListBox1.Items[i]).CheckState);
    }
}

另一种方法。它不需要自定义 class 的支持(尽量不要)。

由于基础项目列表在本例中是非托管(在别处管理),因此必须手动处理项目的检查状态.
查看在 BindingList 中添加或删除项目时引发的事件顺序是什么可能很有趣(例如,在更新列表之前没有通知列表更改的事件,ListChangedEventArgs.OldIndex 永远不会设置,因此总是 -1,等等)。

由于CheckedListBox的来源是一个简单的List<string>,更新列表时,Item的CheckState丢失了。因此,需要存储这些状态并在需要时重新应用,调用 SetItemCheckedState 方法。

由于必须调整项目的状态以匹配新的列表组成(在删除或插入项目之后)并且此过程是同步的,ItemCheck 事件(用于更新所有项目CheckState) 被异常提出并需要延迟执行。这就是这里使用 BeginInvoke() 的原因。

总而言之,一个专门的 class 对象在内部存储这些状态,如 所示,是可行的方法。在这里,绑定受到基础 classes 中缺乏支持的困扰。并不是说它在任何地方都另有说明:例如,CheckedListBox.DataSource 甚至不可浏览。

private BindingList<string> clbItemsList = new BindingList<string>();
public MyForm()
{
    InitializeComponent();
    clbItemsList.Add("A");
    // (...)
    checkedListBox1.DataSource = clbItemsList;
    clbItemsList.ListChanged += this.clbListChanged;
    checkedListBox1.ItemCheck += (s, e) => { BeginInvoke(new Action(()=> CheckedStateCurrent())); };
}

private void clbListChanged(object sender, ListChangedEventArgs e)
{
    foreach (var item in clbCheckedItems.ToArray()) {
        if (e.ListChangedType == ListChangedType.ItemAdded) {
            checkedListBox1.SetItemCheckState(item.Index >= e.NewIndex ? item.Index + 1 : item.Index, item.State);
        }
        if (e.ListChangedType == ListChangedType.ItemDeleted) {
            if (item.Index == e.NewIndex) { 
                clbCheckedItems.Remove(item);
                continue;
            }
            checkedListBox1.SetItemCheckState(item.Index > e.NewIndex ? item.Index - 1 : item.Index, item.State);
        }
    }
}

private List<(CheckState State, int Index)> clbCheckedItems = new List<(CheckState State, int Index)>();
private void CheckedStateCurrent()
{
    clbCheckedItems = checkedListBox1.CheckedIndices.OfType<int>()
        .Select(item => (checkedListBox1.GetItemCheckState(item), item)).ToList();
}