作为我尝试修复my other problem regarding odd disabling的一部分,我尝试更新绑定的ObservableCollection,而不是每隔几秒更换一次。显然它会出错,因为事件无法修改它。所以,我基于this link创建了一个多线程的。
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var eh = CollectionChanged;
if (eh != null)
{
Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
let dpo = nh.Target as DispatcherObject
where dpo != null
select dpo.Dispatcher).FirstOrDefault();
if (dispatcher != null && dispatcher.CheckAccess() == false)
{
dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
}
else
{
foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
nh.Invoke(this, e);
}
}
}
}
我更改了对原始集合的所有引用以使用新集合。我还更改了事件处理程序代码以添加或更新集合中的现有项。这很好用。视图显示正确更新。由于我现在正在更新而不是替换,因此我必须使用CollectionViewSource
为列表应用2种不同的排序属性。
<CollectionViewSource x:Key="AskDepthCollection" Source="{Binding Path=AskDepthAsync}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="SortOrderKey" Direction="Ascending" />
<scm:SortDescription PropertyName="QuotePriceDouble" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="BidDepthCollection" Source="{Binding Path=BidDepthAsync}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="SortOrderKey" Direction="Ascending" />
<scm:SortDescription PropertyName="QuotePriceDouble" Direction="Descending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
当然这意味着只对视图进行排序而不是集合本身。我需要最终位于第一个位置的ViewModel数据。大按钮(在另一个问题上显示)必须反映第一个位置的数据。具有所有业务代码的选项卡管理器只能访问主机表单和主ViewModel。它不能直接与托管列表的选项卡控件一起使用。所以,这些都是我尝试过的。
FirstInView
扩展程序。我尝试通过调用MyCollectionProperty.FirstInView()在父ViewModel中以及在选项卡管理器中使用它。所有在CollectionViewSource中检索第一个ViewModel的尝试都以一件事结束:应用程序关闭而没有错误。我不知道这是因为我试图在多线程集合上执行这些任务,或者它是否是其他内容。
无论哪种方式,我都无法弄清楚如何从排序视图中获取顶部项目。在我使用CollectionViewSource之前,我以编程方式对其进行排序并构建新列表,因此我可以抓住第一个。现在这是不可能的,因为我更新而不是重建。
除了我复制集合,排序,存储第一个ViewModel,然后抛出已排序的副本之外,您有什么想法吗?
我又尝试了#1和2。对于两者,在将第二个项添加到集合后获取视图时出现错误,但在添加第一个项后它似乎有效。它与我尝试更新常规ObservableCollection项时出现的错误非常类似(并引导我使用多线程版本)。
CollectionViewSource.GetDefaultView(AskDepthAsync)
Error: [error.initialization.failed] NotSupportedException, This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread., at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at (mynamespace).MTObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) in C:\path\to\class\MTObservableCollection.cs:line 34 at (mynamespace).MTObservableCollection`1.c__DisplayClass9.b__4() in C:\path\to\class\MTObservableCollection.cs:line 29 TargetInvocationException, Exception has been thrown by the target of an invocation. [...]
所以,现在我回到多线程错误。调用是在改变集合的同一事件线程中进行的:事件&gt;&gt;改变收集&gt;&gt; 获取第一个已排序的项目&gt;&gt;更新大按钮属性。
this.Dispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate
{
//ICollectionView view = CollectionViewSource.GetDefaultView(((MyBaseViewModel)this.DataContext).AskDepthAsync);
ICollectionView view = CollectionViewSource.GetDefaultView(this.askDepthList.ItemsSource);
IEnumerable<DepthLevelViewModel> col = view.Cast<DepthLevelViewModel>();
List<DepthLevelViewModel> list = col.ToList();
((MyBaseViewModel)this.DataContext).AskDepthCurrent = list[0];
return null;
}), null);
注释掉的行将检索集合并完成整个调用而不会出现问题。但是,它缺少排序,因此0索引仍然只是原始集合中的第一个项目。我可以断点,看到它没有排序描述。使用ItemsSource
的未注释行确实会提取正确的行。
view
包含正确的排序说明和源集合。col
仍然有SortDescriptions
和SourceList
。该列表显示了我投射到的DepthLevelViewModel
类型的1个条目,但枚举为 为空 。list
枚举空,col
的项目为零。因此,list[0]
失败。如果我尝试从资源中检索CollectionViewSource,也会发生这种情况。对此有何反馈?
编辑2.1 :如果摆脱上面的col
和list
,请改用:
ICollectionView view = CollectionViewSource.GetDefaultView(this.askDepthList.ItemsSource);
var col = view.GetEnumerator();
bool moved = col.MoveNext();
DepthLevelViewModel item = (DepthLevelViewModel)col.Current;
((MyBaseViewModel)this.DataContext).AskDepthCurrent = item;
col
仍为为空。它显示零项,因此moved
为false,col.Current
失败。
看起来我不得不依靠手动排序集合并单独留下CollectionViewSource
。
IEnumerable<DepthLevelViewModel> depthQuery = ((MyBaseViewModel)this.DataContext).AskDepthAsync.OrderBy(d => d.QuotePriceDouble);
List<DepthLevelViewModel> depthList = depthQuery.ToList<DepthLevelViewModel>();
DepthLevelViewModel item = depthList[0];
((MyBaseViewModel)this.DataContext).AskDepthCurrent = item;
由于这不是理想的方法,因此我将这篇文章作为未答复的。但是,只要找到永久解决方案就足够了。
我已将解决方案迁移到VS2015,并将每个项目的框架升级到4.6。所以,如果你知道一些更新的东西,我会喜欢听到它。
答案 0 :(得分:0)
这可能不是最佳选择,但它是我设法做到的唯一比重新排序更好的方法。
在定义了CollectionViewSource
的标签内容控件(视图)和跨功能使用的父DataContext
(各种视图,标签管理器等)中,我添加了这个:
void TabItemControl_DataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
((TabViewModel)DataContext).AskCollection = (CollectionViewSource)Resources["AskDepthCollection"];
((TabViewModel)DataContext).BidCollection = (CollectionViewSource)Resources["BidDepthCollection"];
}
它存储对选项卡上使用的已排序集合的引用。在标签管理器中,我把:
Private Function GetFirstQuote(ByVal eType As BridgeTraderBuyIndicator) As DepthLevelViewModel
Dim cView As ICollectionView
Dim cSource As CollectionViewSource
Dim retView As DepthLevelViewModel = Nothing
If eType = BridgeTraderBuyIndicator.Buy Then
cSource = multilegViewModel.AskCollection
Else
cSource = multilegViewModel.BidCollection
End If
Try
cView = cSource.View()
Dim enumerator As IEnumerator = cView.Cast(Of DepthLevelViewModel).GetEnumerator()
Dim moved As Boolean = enumerator.MoveNext()
If moved Then
retView = DirectCast(enumerator.Current(), DepthLevelViewModel)
Else
ApplicationModel.Logger.WriteToLog((New StackFrame(0).GetMethod().Name) & ": ERROR - Unable to retrieve the first quote.", LoggerConstants.LogLevel.Heavy)
End If
Catch ex As Exception
ApplicationModel.AppMsgBox.DebugMsg(ex, (New StackFrame(0).GetMethod().Name))
End Try
Return retView
End Function
当我需要获得第一项时,我就这样称呼它:
Dim ask As DepthLevelViewModel
ask = GetFirstQuote(MyTypeEnum.Buy)
如果没有提供更好的解决方案,我会将此标记作为已接受的答案。