【问题标题】:WPF Thread binding access error on binding in MVVMMVVM中绑定时出现WPF线程绑定访问错误
【发布时间】:2017-03-28 05:07:46
【问题描述】:

所以我正在尝试将 WPF 的动态数据显示合并到我的 MVVM caliburn 项目中(如果有人希望测试此错误,我正在使用来自动态数据显示的未来的 LineChart 控件)。有一个 LineChart 绑定到 ObservableCollection。它仅在集合是在具有 LineChart 的控件的代码中进行时才有效。如果您尝试将 ViewModel 绑定到集合,则依赖属性会引发 InvalidOperationException。这个问题怎么解决?

我已经看到,当您更改属性绑定到的集合并知道修复该问题的方法时会发生这种情况,但在绑定的实际过程中从来没有。我已经尝试将集合的创建放在调度程序调用中(就像您对添加或删除所做的那样),但它没有帮助。

编辑:正如我在第二段中所说,例外不是在更改集合的时候。它在绑定点被提升。更重要的是,我尝试使用other question 中的解决方案,但它们没有帮助。

编辑 #2:异常消息是“调用线程无法访问此对象,因为不同的线程拥有它”。 人们不断告诉我收集更改的解决方案,但它甚至没有涉及到更改。它在绑定阶段失败(xaml 中的 ItemsSource="{binding collection}")。

编辑 #3:我仔细检查并注意到 ViewModel 是在 UI 线程中创建的,这只会提出更多问题。

【问题讨论】:

  • 如果您可以发布 StackTrace 或至少是 MessageInvalidOperationException,这将非常有帮助。
  • 好多了。似乎您有一些代码在调度程序线程之外分配了某些 UI 元素的 ItemsSource 属性。更重要的是,这绝对是在 C# 中而不是在 XAML 代码中完成的任务。所以它不是数据绑定,而是手动属性分配。您可以发布MainWindow.xaml.cs 的第 75 行吗?或者甚至是整个方法gogogo() 和线程的开始?找到异常的原因应该不难。
  • 我检查了您的代码,但很抱歉,它对我来说完全无法使用。首先,它不会编译。 (这很糟糕。)其次,我不清楚异常到底发生在哪里。你有很多代码和注释代码。我不能只是猜测你的意思以及它发生在哪里。所以请清理您的代码,删除注释代码,准确显示异常发生的行,使其可编译,然后重新上传。 (这叫做Minimal, Complete, and Verifiable example。)等你重新上传,我再看一遍。
  • 而“最小”也意味着你删除所有与你的异常无关的代码。例如,我不知道SynchronizedObservableCollection 与它有什么关系,以及为什么TouchView 甚至存在。

标签: c# wpf multithreading xaml mvvm


【解决方案1】:

好的,我花了很长时间才找到问题的根本原因。

与其他人所怀疑的不同,这根本不是多线程问题。
相反,这是您正在使用的 DynamicDataDisplay 库的问题。

ItemsSource 绑定在 ListBox 对象上有效,而在 ChartMicrosoft.Research.DynamicDataDisplay.Markers2.LineChart 类型)上无效:
Chart 既没有视觉也没有逻辑父母。

如果您将以下代码插入Button_Click 并在其后设置断点,则可以检查这一点:

var visualParent = VisualTreeHelper.GetParent(Chart);
var logicalParent1 = Chart.Parent;
var logicalParent2 = LogicalTreeHelper.GetParent(Chart);

可以看到都是null
所以你用Path=ExampleCollectionLineChart.ItemsSourceProperty上设置的Binding找不到任何源值,而只是将null分配给ItemsSource。那是因为 DataContext 是从父级继承的 - 但是当没有任何父级时,也没有任何 DataContext 可以继承。
而且因为Chart 不是可视化树或逻辑树的一部分,所以与外部DataContext 的任何绑定都没有(简单的)方法可以工作。

要验证DataContext 是否为null,只需将此行添加到前面的代码中:

var dataContext = Chart.DataContext;

现在有三种可能的解决方案。
首先,您可以使用以下代码从Window 手动继承DataContext

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // Just add the following line.
    Chart.DataContext = DataContext;

    Chart.StrokeThickness = 3;
    Chart.SetBinding(LineChart.ItemsSourceProperty, new Binding("ExampleCollection"));
    // ...
}

如果您只添加这一行,您会发现您的其他多线程代码运行良好,并且图表已更新为某种正弦波模式。

第二,作为另一种可能的解决方案,您可以查阅DynamicDataDisplay 库的文档并检查使用数据绑定将ItemsSource 分配给LineChart 的正确和预期方式。 我尝试自己搜索文档,甚至从该库中调试了两个小时左右的大量代码,但文档几乎不存在,而且代码太复杂,无法在几个小时内完全理解。我尝试使用几种工具(Visual Studio Live Visual Tree,Snoop,...)来显示 ChartPlotter 的可视化树,但我每次都得到一个 StackOverflowException,所以基本上这个库中的某些东西有点缺陷和错误.

第三,您可以使用Resource 作为一种代理对象来创建到MainWindowViewModel 的同一实例的“绑定桥”。
为此,您必须执行此处建议的操作:Data binding outside the visual tree. Data Context bridging

底线:所以,如果您只是想完成工作,我会在代码中设置DataContext,如上所示。 (特别是如果DataContext 中的ViewModel 的实例永远不会改变。)
如果您想使用纯数据绑定,那么我可能会使用“绑定桥”或搜索另一个支持此方案的图表库。

【讨论】:

  • 非常感谢您提供的所有帮助和信息丰富的答案/cmets!
  • @PaulVinogradov 非常欢迎您!很高兴我能帮助你! :-)
【解决方案2】:

正如THIS 线程所说,您可以使用 UI 调度程序并调用更改 UI 线程上的 ObservableCollection 的函数。 Application.Current.Dispatcher 应该为您提供 UI 调度程序。正如解决方案所暗示的那样,它可以在 ViewModel 中。然而,一个更简洁、更通用的解决方案是 IMPLEMENT 一个并发且仍可观察的集合,它仍将存储调度程序并在 UI(或指定)线程上运行更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 2021-03-01
    相关资源
    最近更新 更多