|
|||||||
Асинхронный ObservableCollection
Время создания: 26.12.2019 00:42
Текстовые метки: ObservableCollection
Раздел: Компьютер - C# - WPF
Запись: Kozlov-AE/Tetra/master/base/1577310126fud80tmbt8/text.html на raw.githubusercontent.com
|
|||||||
|
|||||||
Cоздавайте объект типа ListCollectionView прямо во ViewModel и используйте его в биндингах. Соответственно, всю сортировку (лучше использовать свойство ListCollectionView.CustomSort, что увеличит скорость сортировки) и фильтрацию можно будет сделать во ViewModel. Вроде подводных камней в таком решении нет. В качестве коллекции-источника для ListCollectionView можно взять обычную ObservableCollection. Данные для коллекции-источника по возможности формируйте в отдельном потоке (если не хочется получить чёрные окна вместо интерфейса), но учтите, что заполнять ObservableCollection придётся в UI-потоке, потому что CollectionView, которая будет дёргаться при изменениях в коллекции-источнике, может работать только в UI-потоке (так уж спроектированы все наследники ICollectionView, наследуются от DispatcherObject). Поэтому, лучше всего, коллекцию-источник заполнять через Dispatcher.BeginInvoke (ViewModel рекомендую наследовать от DispatcherObject) по одному элементу за раз, желательно с приоритетом DispatcherPriority.Background. То же самое, в коде: public class MyViewModel : DispatcherObject, INotifyPropertyChanged { private readonly IEnumerable<Data> myData; private readonly ObservableCollection<DataViewModel> myCollection; private ListCollectionView myCollectionView; public MyViewModel(IEnumerable<Data> data) { myData = data; myCollection = new ObservableCollection<DataViewModel>(); } public ListCollectionView MyCollection { get { return myCollectionView ?? CreateMyCollectionView(); } } private ListCollectionView CreateMyCollectionView() { myCollectionView = new ListCollectionView(myCollection) { CustomSort = new MyComparer(), Filter = MyFilter }; FetchData(myData, dataObject => myCollection.Add(new DataViewModel(dataObject)));
return myCollectionView; } protected void FetchData<T>(IEnumerable<T> data, Action<T> fetch) { State = ViewModelState.Fetching; var dataEnumerator = data.GetEnumerator();
Dispatcher.BeginInvoke( new Action<IEnumerator<T>, Action<T>> ( FetchDataImpl ), DispatcherPriority.Background, dataEnumerator, fetch ); } private void FetchDataImpl<T>(IEnumerator<T> sourceEnumerator, Action<T> fetch) { if (!sourceEnumerator.MoveNext()) { State = ViewModelState.Active; return; } fetch(sourceEnumerator.Current); Dispatcher.BeginInvoke( new Action<IEnumerator<T>, Action<T>> ( FetchDataImpl ), DispatcherPriority.Background, sourceEnumerator, fetch ); } } Сегодня наткнулся на интересный вариант решения проблемы с обновлением CollectionView/ObservableCollection из другого потока, здесь коллекция-источник посылает уведомления о своём изменении в подходящем контексте синхронизации, что позволяет заполнять/изменять её в отдельном потоке. Этот класс надо иcпользовать вместо ObservableCollection/FetchData и можно забивать его данными в отдельном потоке: /// <summary> /// An ObservableCollection<T> enhanced with capability of free threading. /// </summary> [Serializable] public class BindableCollection<T> : ObservableCollection<T> { /// <summary> /// Initializes a new instance of the<see cref="BindingCollection<T>">BindingCollection</see>. /// </summary> public BindableCollection() : base() { } /// <summary> /// Initializes a new instance of the<see cref="BindingCollection<T>">BindingCollection</see> /// class that contains elements copied from the specified List<T>. /// </summary> /// <param name="list">The list from which the elements are copied.</param> /// <exception cref="System.ArgumentNullException">The list parameter cannot be null.</exception> public BindableCollection(List<T> list) : base(list) { } /// <summary> /// Initializes a new instance of the<see cref="BindingCollection<T>">BindingCollection</see> /// class that contains elements copied from the specified IEnumerable<T>. /// </summary> /// <param name="list">The list from which the elements are copied.</param> /// <exception cref="System.ArgumentNullException">The list parameter cannot be null.</exception> public BindableCollection(IEnumerable<T> list) { if (list == null) throw new ArgumentNullException("list"); foreach (var item in list) { Items.Add(item); } } /// <summary> /// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed. /// </summary> public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var collectionChanged = CollectionChanged; if (collectionChanged != null) { using (var blockReentrancy = BlockReentrancy()) { foreach (var @delegate in collectionChanged.GetInvocationList()) { var dispatcherInvoker = @delegate.Target as DispatcherObject; var syncInvoker = @delegate.Target as ISynchronizeInvoke; if (dispatcherInvoker != null) { // We are running inside DispatcherSynchronizationContext, // so we should invoke the event handler in the correct dispatcher. dispatcherInvoker.Dispatcher.Invoke(@delegate, DispatcherPriority.Background, this, e); } else if (syncInvoker != null) { // We are running inside WindowsFormsSynchronizationContext, // so we should invoke the event handler in the correct context. syncInvoker.Invoke(@delegate, new object[] { this, e }); } else { // We are running in free threaded context, so just directly invoke the event handler. var handler = (NotifyCollectionChangedEventHandler)@delegate; handler(this, e); } } } } } } |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|