Примерно на таком коде проявилась проблема. Есть некий объект со свойством в виде коллекции ObservableCollection
public class CollectionHolder
{
public CollectionHolder()
{
Collection = new ObservableCollection<string> { "a", "b" };
}
public IList<string> Collection { get; set; }
}
Для просмотра свойств объекта и коллекции используется окно
public class Window2 : Window
{
public Window2()
{
lb = new ListBox();
lb.SetBinding(ListBox.ItemsSourceProperty, "Collection");
Content = lb;
}
private ListBox lb;
}
Это окно вызывается из главного окна приложения как модальное, а потом закрывается и ссылки на него не сохраняются. DataContext обнуляется. Потом объект CollectionHolder может быть модифицирован каким либо другим потоком. Тут и возникает исключение от Dispatcher.
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
holder = new CollectionHolder();
var btn = new Button();
Content = btn;
btn.Click += Click;
}
private CollectionHolder holder;
private int i;
private void Click(object Sender, RoutedEventArgs E)
{
if (i == 0)
{
var win2 = new Window2 { DataContext = holder };
win2.ShowDialog();
win2.DataContext = null;
GC.Collect();
i = 1;
}
else
{
ThreadPool.QueueUserWorkItem(_ =>
{
holder.Collection.Add("c"); // Exception !
});
i = 0;
}
}
Если вместо ObservableCollection применить свой класс
public class MyList<T> : List<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { collectionChanged += value; }
remove { collectionChanged -= value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private NotifyCollectionChangedEventHandler collectionChanged;
}
то видно, что при байндинге в event NotifyCollectionChangedEventHandler вызывается add к методу из ListCollectionView. При установке DataContext ListBox или окна в null или другой объект или очистке байндинга у ListBox.ItemsSource remove никогда не вызывается. Как же тогда отцепить коллекцию, реализующую INotifyCollectionChanged от ListBox ?
Здравствуйте, Spirit_1, Вы писали:
S_>то видно, что при байндинге в event NotifyCollectionChangedEventHandler вызывается add к методу из ListCollectionView. При установке DataContext ListBox или окна в null или другой объект или очистке байндинга у ListBox.ItemsSource remove никогда не вызывается. Как же тогда отцепить коллекцию, реализующую INotifyCollectionChanged от ListBox ?
Контролы WPF взаимодействуют с коллекцией не напрямую, а используя внутренний механизм CollectionView, которым манипулируют достаточно хитрожопо (пардон). Скорее всего именно из-за этого связь с коллекцией все еще остается, в то время, как в контроле ссылки уже нет.
Варианты решения:
Использовать ObservableCollection только для UI, а в потоке манипулировать обычным списком. Перед показом окна, перекладываем список в коллекцию. После показа окна, делаем наоборот.
Модифицировать коллекцию через Dispatcher.
Перейти на .Net Framework 4.5, в которой обещали разрешить модификацию коллекций в не UI-потоках.
MM>Контролы WPF взаимодействуют с коллекцией не напрямую, а используя внутренний механизм CollectionView, которым манипулируют достаточно хитрожопо (пардон). Скорее всего именно из-за этого связь с коллекцией все еще остается, в то время, как в контроле ссылки уже нет.
Замечательно, значит после каждого соединения с ItemControl в памяти навсегда зависает CollectionView. Это ж глюк!
MM>Варианты решения:
MM>
MM>Использовать ObservableCollection только для UI, а в потоке манипулировать обычным списком. Перед показом окна, перекладываем список в коллекцию. После показа окна, делаем наоборот.
MM>Модифицировать коллекцию через Dispatcher.
MM>Перейти на .Net Framework 4.5, в которой обещали разрешить модификацию коллекций в не UI-потоках.
MM>
Примерно по первому варианту решил, только с точностью до наоборот. Написал потомка ListBox, который правильно подписывается и отписывается от коллекции и следит за её обновлением, а базовому ListBox отдает обычную коллекцию IEnumerable.
Здравствуйте, Spirit_1, Вы писали:
S_>Замечательно, значит после каждого соединения с ItemControl в памяти навсегда зависает CollectionView. Это ж глюк! 
Нет, не навсегда.
S_>Примерно по первому варианту решил, только с точностью до наоборот. Написал потомка ListBox, который правильно подписывается и отписывается от коллекции и следит за её обновлением, а базовому ListBox отдает обычную коллекцию IEnumerable.
Мне кажется, наследника контрола нужно лепить только в самом крайнем случае, когда больше ничего не помогает. Завтра понадобится заменить ListBox на TreeView — мало не покажется.