У меня есть требование толкнуть большие объемы данных в DataGrid (я бы предпочел DataGrid, поскольку он подходит для цели для моего требования). Чтобы сделать пользовательский интерфейс более отзывчивым во время загрузки данных и по мере того, как данные поступают после завершения начальной загрузки, требуется довольно высокая производительность. Кроме того, данные необходимо сортировать (по убыванию в зависимости от даты). Поскольку он обновляется только из одного потока, являющегося параллельным (и/или неизменным), на самом деле не требуется (из того, что я понимаю, является одновременным и/или неизменным, может в любом случае замедлить загрузку). Поэтому по этой причине я хотел бы реализовать коллекцию Observable, такую как SortedDictionaryWPF - Наблюдаемый Сортированный словарь
Из того, что я видел выше, не всегда доступно - параметры, которые я видел, это http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So, но это не привязывается к DataGrid (больше к ListView). Другой - http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/, который основан на словарях и ручных сортировках (что кажется противоречащим интуиции, поскольку уже есть SortedDictionary - и снова, похоже, не привязывается к DataGrid легко).
Ниже то, что я
public class ObservableSortedDictionary<TKey, TValue> : IObservableSortedDictionary<TKey, TValue>
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private int _capacity = 0;
private SortedDictionary<TKey, TValue> _dictionary;
protected SortedDictionary<TKey, TValue> Dictionary
{
get { return _dictionary; }
private set { _dictionary = value; }
}
#region Fields
private readonly SimpleMonitor _monitor;
#endregion
#region Constructors
public ObservableSortedDictionary(IComparer<TKey> comparer)
{
this._monitor = new SimpleMonitor();
CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
_dictionary = new SortedDictionary<TKey, TValue>(comparer);
}
public ObservableSortedDictionary(int capacity, IComparer<TKey> comparer)
{
this._monitor = new SimpleMonitor();
_capacity = capacity;
CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
_dictionary = (new SortedDictionary<TKey, TValue>(comparer)); }
public ObservableSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer)
{
if (dictionary == null)
{
throw new ArgumentNullException("dictionary");
}
CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
this._monitor = new SimpleMonitor();
_dictionary = new SortedDictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer, int capacity)
{
if (dictionary == null)
{
throw new ArgumentNullException("dictionary");
}
CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
this._monitor = new SimpleMonitor();
_capacity = capacity;
try
{
_dictionary = new SortedDictionary<TKey, TValue>(dictionary, comparer);
}
catch (Exception ex)
{
throw;
}
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
Insert(key, value, true);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return Dictionary.Keys; }
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException("key");
CheckReentrancy();
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if (removed)
OnCollectionChanged();
return removed;
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return Dictionary.Values; }
}
public TValue this[TKey key]
{
get
{
return Dictionary[key];
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Insert(item.Key, item.Value, true);
}
public void Clear()
{
if (Dictionary.Count > 0)
{
CheckReentrancy();
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Dictionary.Count; }
}
public bool IsReadOnly
{
get { return ((IDictionary<TKey, TValue>)Dictionary).IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddRange(IDictionary<TKey, TValue> items)
{
if (items == null) throw new ArgumentNullException("items");
if (items.Count > 0)
{
if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
throw new ArgumentException("An item with the same key has already been added.");
else
{
foreach (var item in items)
{
Dictionary.Add(item.Key, item.Value);
OnPropertyChanged();
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(item.Key, item.Value));
}
}
}
}
private void Insert(TKey key, TValue value, bool add)
{
if (key == null) throw new ArgumentNullException("key");
CheckReentrancy();
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
}
else
{
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
if (_capacity > 0 && Dictionary.Count > _capacity)
{
Dictionary.Remove(Dictionary.Keys.Last());
OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
}
}
}
#region SimpleMonitor
protected IDisposable BlockReentrancy()
{
this._monitor.Enter();
return this._monitor;
}
protected void CheckReentrancy()
{
if ((this._monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1))
{
throw new InvalidOperationException("Collection Reentrancy Not Allowed");
}
}
[Serializable]
private class SimpleMonitor : IDisposable
{
private int _busyCount;
public bool Busy
{
get { return this._busyCount > 0; }
}
public void Enter()
{
this._busyCount++;
}
#region Implementation of IDisposable
public void Dispose()
{
this._busyCount--;
}
#endregion
}
#endregion
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
if (CollectionChanged != null) using (BlockReentrancy()) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
if (CollectionChanged != null) using (BlockReentrancy()) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) using (BlockReentrancy()) {CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
if (CollectionChanged != null) using (BlockReentrancy()) {CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));}
}
void ObservableSortedDictionary_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((KeyValuePair<TickerKey, TickerViewModel>)(item)).Value.PropertyChanged += ObservableSortedDictionary_PropertyChanged; }
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((KeyValuePair<TickerKey, TickerViewModel>)(item)).Value.PropertyChanged -= ObservableSortedDictionary_PropertyChanged; }
}
}
void ObservableSortedDictionary_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//if (e.PropertyName == "Dictionary")
OnPropertyChanged("Dictionary");
}
}
public interface IObservableSortedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<KeyValuePair<TKey, TValue>>
{
}
ViewModel
//...
private ObservableSortedDictionary<TickerKey, TickerViewModel> _tickersData;
public ObservableSortedDictionary<TickerKey, TickerViewModel> TickersData
{
get
{
return _tickersData;
}
set
{
if (value != _tickersData)
{
_tickersData = value;
OnPropertyChanged("TickersData");
}
}
}
//...
if (TickersData == null)
{
TickerComparer comparer = new TickerComparer();
TickersData = new ObservableSortedDictionary<TickerKey, TickerViewModel>(_tickersInsert, comparer, 50);
}
else
{
TickersData.AddRange(_tickersInsert);
foreach (var item in _tickersInsert) TickersData.Add(item.Key, item.Value);
}
//...
The View (XAML)
//...
<DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData.Values}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding TickerPrice}" Header="Price" Width="50"/>
<DataGridTextColumn Binding="{Binding TickerVolume}" Header="Volume" Width="50" />
<DataGridTextColumn Binding="{Binding TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" />
</DataGrid.Columns>
</DataGrid>
//...
Несколько вещей примечания
- Вышеуказанное загружает DataGrid изначально из строк из базы данных, но по мере поступления новых данных это не отражается в DataGrid (даже если новые данные добавляются в ObservableSortedDictionary.
- Связывание ObservableSortedDictionary к XAML осуществляется с помощью значений части словаря (т.е. TickersData.Values, а не просто TickersData)
- слабость, как представляется, в уведомлении CollectionChanged/PropertyChanged. В частности, между отправкой события из изменения в словарь. Сбор значений до DataGrid. . .
. Кто-нибудь пытался что-либо сделать, как указано выше, и/или может видеть, где может возникнуть проблема с уведомлением?
UPDATE После внушения Питера, я изменил определение XAML DataGrid к
<DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value.TickerPrice}" Header="Price" Width="50"/>
<DataGridTextColumn Binding="{Binding Value.TickerVolume}" Header="Volume" Width="50" />
<DataGridTextColumn Binding="{Binding Value.TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" />
</DataGrid.Columns>
</DataGrid>
Почему бы просто не использовать ObservableCollection, а затем вставить в соответствующее положение, чтобы поддержать своего рода? И если вам нужна производительность, то почему DataGrid? – Paparazzi
Вы привязываетесь к 'TickersData.Values' - свойству' Values' вашего отсортированного словаря, а не самому сортированному словарю. Попытайтесь напрямую привязать к TickersData. –
Спасибо за комментарий Frisbee - я понимаю, что вы говорите вокруг ObservableCollection, но я не хочу создавать собственный собственный алгоритм сортировки (который не будет таким же результативным, как сказать из сортированной коллекции). И причина для DataGrid (а не ListView) заключается в том, что из-за этого у него есть необходимая функциональность (сортировка столбцов и т. Д.) – TerrorBight