MyTetra Share
Делитесь знаниями!
Асинхронный 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&lt;T&gt; enhanced with capability of free threading.

/// </summary>

[Serializable]

public class BindableCollection<T> : ObservableCollection<T>

{

/// <summary>

/// Initializes a new instance of the<see cref="BindingCollection&lt;T&gt;">BindingCollection</see>.

/// </summary>

public BindableCollection() : base() { }


/// <summary>

/// Initializes a new instance of the<see cref="BindingCollection&lt;T&gt;">BindingCollection</see>

/// class that contains elements copied from the specified List&lt;T&gt;.

/// </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&lt;T&gt;">BindingCollection</see>

/// class that contains elements copied from the specified IEnumerable&lt;T&gt;.

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

}

}

}

}

}

}

 
MyTetra Share v.0.55
Яндекс индекс цитирования