WPF - 可观察排序字典
WPF - Observable Sorted Dictionary
我需要将大量数据推送到 DataGrid(我更喜欢 DataGrid,因为它适合我的要求)。为了使 UI 在加载数据时以及在初始加载完成后数据进入时响应更快,需要相当高的性能。最重要的是,数据需要排序(基于日期的降序)。因为它只是从一个线程并发更新(and/or 不可变)并不是真正需要的(据我所知,并发 and/or 不可变在任何情况下都可能会减慢负载)。因此,出于这个原因,我想实现一个 Observable 性能集合,例如 SortedDictionary
从我所看到的情况来看,上面的内容并不容易获得 - 我看到的选项是 http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So but this isn't bindable to a DataGrid (more toward a ListView). The other is 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>>
{
}
视图模型
//...
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);
}
//...
景观 (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
之间
有没有人尝试过类似上述的事情and/or可以看出通知问题可能在哪里?
更新
根据 Pieter 的建议,我将 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>
感谢您在此 Pieter 上的帮助。 refresh/sort 我从 ObservableSortedDictionary 中找到的 DataGrid 解决方案(阅读 http://programmer.wrighton.org/2009/01/wpf-datagrid-items-refresh.html 中的一条评论后)是
CollectionViewSource.GetDefaultView(TickersData).Refresh();
希望以上声明不会造成太多资源开销
我还有一个与 OnCollectionChanged 相关的问题 - NotifyCollectionChangedEventArgs 需要 SortedDictionary 的索引(而不是项目),它不是开箱即用的(使用 SortedList 更容易,但无论如何).
但现在一切正常 - 工作正常
我需要将大量数据推送到 DataGrid(我更喜欢 DataGrid,因为它适合我的要求)。为了使 UI 在加载数据时以及在初始加载完成后数据进入时响应更快,需要相当高的性能。最重要的是,数据需要排序(基于日期的降序)。因为它只是从一个线程并发更新(and/or 不可变)并不是真正需要的(据我所知,并发 and/or 不可变在任何情况下都可能会减慢负载)。因此,出于这个原因,我想实现一个 Observable 性能集合,例如 SortedDictionary
从我所看到的情况来看,上面的内容并不容易获得 - 我看到的选项是 http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So but this isn't bindable to a DataGrid (more toward a ListView). The other is 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>>
{
}
视图模型
//...
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);
}
//...
景观 (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 之间
有没有人尝试过类似上述的事情and/or可以看出通知问题可能在哪里?
更新 根据 Pieter 的建议,我将 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>
感谢您在此 Pieter 上的帮助。 refresh/sort 我从 ObservableSortedDictionary 中找到的 DataGrid 解决方案(阅读 http://programmer.wrighton.org/2009/01/wpf-datagrid-items-refresh.html 中的一条评论后)是
CollectionViewSource.GetDefaultView(TickersData).Refresh();
希望以上声明不会造成太多资源开销
我还有一个与 OnCollectionChanged 相关的问题 - NotifyCollectionChangedEventArgs 需要 SortedDictionary 的索引(而不是项目),它不是开箱即用的(使用 SortedList 更容易,但无论如何).
但现在一切正常 - 工作正常