Обновления в DataSource сбрасывают флажки CheckedListBox

У меня есть 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 обновляется. Однако, когда некоторые элементы отмечены флажком, нажатие кнопки не только обновляет список, но и сбрасывает все элементы, чтобы они не были отмечены флажками. Как я могу это исправить?

Спасибо!


person Sipo    schedule 22.08.2019    source источник


Ответы (2)


Вам нужно отслеживать состояние проверки самостоятельно.

Как вариант, вы можете создать класс модели для элементов, содержащих текст и состояние проверки. Затем в ItemCheck событие элемента управления, установите значение состояния проверки модели элемента. Также в ListChenged события вашего BindingList<T> обновить контрольное состояние элементов.

Пример

Создайте класс CheckedListBoxItem:

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);
    }
}
person Reza Aghaei    schedule 22.08.2019
comment
Спасибо! Есть одна проблема: при загрузке формы все галочки сняты. Только когда я меняю список, проверяются правильные. - person Sipo; 22.08.2019
comment
Без проблем. В настоящее время мы просто синхронизируем состояние проверки элемента, когда элемент добавляется/удаляется/редактируется. Вы можете инкапсулировать логику, которая у вас есть в ListChange, и поместить ее в такой метод, как RefreshCheckState, а затем вызвать его после установки DataSource. - person Reza Aghaei; 22.08.2019

Другой метод. Он не требует поддержки пользовательского класса (пытается этого не делать).

Поскольку базовый список элементов в данном случае неуправляемый (управляется где-то еще), состояние проверенных элементов должно обрабатываться вручную.
Это может быть интересно. чтобы увидеть, какова последовательность событий, возникающих при добавлении или удалении элемента из BindingList (например, нет событий, уведомляющих об изменении списка до его обновления, ListChangedEventArgs.OldIndex никогда не устанавливается, поэтому всегда -1 и т. д.) .

Поскольку источником CheckedListBox является простой List<string>, при обновлении списка CheckState элемента теряется. Следовательно, эти состояния необходимо сохранять и повторно применять при необходимости, вызывая SetItemCheckedState.

Поскольку состояние элементов должно быть скорректировано в соответствии с новой композицией списка (после того, как элемент был удален или вставлен), и эта процедура является синхронной, событие ItemCheck (используемое для обновления всех элементов CheckState) вызывается нестандартно и требует отложенного выполнения. . Вот почему здесь используется BeginInvoke().

В общем, специализированный объект класса, который хранит эти состояния внутри, как показано в Reza Aghaei answer, — это путь. . Здесь связывание страдает от отсутствия поддержки в базовых классах. Не то, чтобы где-то было указано иное: например, 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();
}
person Jimi    schedule 22.08.2019
comment
Спасибо! Когда я буду более уверен в своем понимании параллелизма, я попробую это решение. :) - person Sipo; 22.08.2019