【问题标题】:Debugging: Why a data-bound section breaks off when DataContext is reapplied?调试:为什么重新应用 DataContext 时数据绑定部分会中断?
【发布时间】:2010-12-02 03:37:46
【问题描述】:

更新 2010 06 12
将精华提炼成更小的样品 - 可在此处获取解决方案 http://db.tt/v6r45a4

现在似乎是与选项卡控件有关的问题: 重新应用数据上下文时(单击按钮),似乎一切都按预期工作。绑定刷新,但活动选项卡上的所有绑定都被破坏。

您可以通过选择任何选项卡并单击按钮来验证这一点,您会看到该选项卡上的控件运行正常。添加了一堆日志语句,这就是我的朋友如何得出这个概要和他的修复[在选项卡控件上设置 DataContext="{Binding}" ]

但我们仍然不确定为什么会这样......

TabControl Data Context now set to ReproTabItemBug.MainViewModel
TabPage [LeftTabPage] Data Context now set to ReproTabItemBug.LeftViewModel
System.Windows.Data Error: 40 : BindingExpression path error: 'MiddleProp' property not found on 'object' ''MainViewModel' (HashCode=50608417)'. BindingExpression:Path=MiddleProp; DataItem='MainViewModel' (HashCode=50608417); target element is 'TextBox' (Name='MiddleTabTextbox'); target property is 'Text' (type 'String')
TabPage [MiddleTabPage] Data Context now set to ReproTabItemBug.MiddleViewModel
Middle tab textbox text changed to 634272638824920423
TabPage [RightTabPage] Data Context now set to ReproTabItemBug.RightViewModel
Middle tab textbox text changed to 

上一篇文章 (免责声明:前面的帖子很长......拿你的爆米花。我花了一天的大部分时间在这上面......)

我的 ViewModel 由三个 POCO subViewModel 组成。每个 subVieModel 都具有绑定在上述 3 个部分中的属性。每个 subViewModel 都作为标准的 .net get-only 属性公开(无 INotifyPropertyChanged)

我的视图有 3 个部分。每个部分都有一个类似这样的 DataContext 设置器..

...
<TabItem  x:Name="_tabPageForVM2"  DataContext="{Binding PropReturningSubVM2}">
  <!-- followed by UI Items that are data-bound to props inside this sub-viewmodel -->
</TabItem>
...

总结一下

<MainView> <!--DataContext set programmmatically to an Instance Of MainViewModel) -->
   <Control DataContext="{Binding PropReturningSubVM1}" >.. Section1 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM2}" >.. Section2 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM3}" >.. Section3 .. </Control>
</MainView>

现在是令人费解的部分。 在正常启动时,我创建了 MainViewModel 的一个实例(将子视图模型传递到它的 ctor 中)。监视窗口中的属性证实了这一点。

    Trace.WriteLine("Before setting datacontext");
    mainView.DataContext = null;
    mainView.DataContext = mainViewModel;
    //mainView.Refresh();
    Trace.WriteLine("After setting datacontext");

一切都很完美。现在由于我无法控制的原因,存在 UI 被关闭但视图仍驻留在内存中的情况。因此,为了在下次显示时将其清除,我创建了我的视图模型的新实例并重新应用数据上下文(通过调用与以前相同的初始化例程) 但是,当执行DataContext=mainViewModel 集时,我在输出窗口中看到一堆绑定错误。有趣的是,只有一个标签页(子视图模型)内的绑定被破坏了。其他 2 个子视图模型正常运行 - 没有绑定错误。

System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MaxValue' (type 'Double')
System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MajorTickCount' (type 'Int32')
...

属性存在于 SubViewModel2 上,但查找使用的是 MainViewModel。

接下来我在 MainView 上添加了一个 Refresh 方法(在重新应用 DataContext 后调用)来执行此操作

    _tabPageForVM2.DataContext = null;
    _tabPageForVM2.DataContext = mainVM.PropReturningSubVM2;

这解决了问题。

让我困惑的是

  • subVM2 有什么特别之处?如果它在第一次分配 DataContext 时有效,为什么第二次会中断?
  • 我将 tabPage 的 header 设置为 {Binding} 以检查它绑定的内容,它显示“FullTypeNameOfSubVM2”,这表明正在设置标签页的 DataContext 属性。那么为什么绑定被破坏了呢?

【问题讨论】:

    标签: wpf data-binding


    【解决方案1】:

    您可以下载Snoop 或其他显示可视化树的工具,并在那里看到 TabItem 的内容实际上不是此 TabItem 的可视子项,而是 TabControl 的可视子项。

    所以 TabItem 是 TabItem 内容的逻辑子级,而 TabControl 是 TabItem 内容的可视子级。 DataContext 应该从逻辑父级继承,但在我看来,它是从 TabItem 或 TabControl 随机继承的。

    我可以建议的最佳解决方案是将绑定移动到如下内容:

    <TabItem x:Name="LeftTabPage" Header="LeftModel">
        <StackPanel Orientation="Horizontal" DataContext="{Binding Left}">
            <my:Gauge x:Name="gauge" Height="200" Width="200" Value="{Binding LeftProp}"/>
            <Viewbox Height="200" Width="200" >
                <my:Gauge x:Name="scaledGauge" Value="{Binding LeftProp}"/>
            </Viewbox>
        </StackPanel>
    </TabItem>
    
    <TabItem x:Name="MiddleTabPage" Header="{Binding}">
        <TextBox x:Name="MiddleTabTextbox" DataContext="{Binding Middle}" Text="{Binding MiddleProp}" />
    </TabItem>
    
    <TabItem x:Name="RightTabPage" Header="RightModel">
        <TextBox DataContext="{Binding Right}" Text="{Binding RightProp}"/>
    </TabItem>
    

    我想我明白了。我会尽力解释。

    如果第二个选项卡处于活动状态(例如),MiddleTabTextbox 是 MiddleTabPage 的逻辑子级,它是 MyTabControl 的可视子级(它有 2 个不同的父级和 2 个不同的 DataContexts)。第一个选项卡不活动,它只有逻辑子 - StackPanel。当您单击按钮时,DataContext 会发生变化。 StackPanel 没问题 - 它有一个父级。但是 MiddleTabTextbox 应该怎么做呢?应该从视觉或逻辑父级获取 DataContext 吗?从逻辑父级获取上下文似乎是合乎逻辑的 :) 我加速了这种行为,但 MiddleTabTextbox 从可视子级即 MyTabControl 获取它。因此,获得 MiddleViewModel 后,您会获得带有一堆绑定错误的 MainViewModel。我不知道为什么 WPF 从可视父级继承 DataContext 而不是逻辑父级。

    【讨论】:

    • 我对此进行了一些修复 - 但我有兴趣知道我做错了什么。这样我就不会再这样做了:)
    • 我已经添加了一些想法来发布。我认为你没有做错,但是 WPF 做错了。
    • 我怀疑有类似的事情......但被其他事情束缚住了。感谢您为我调查...很棒的窥探:)(仅供参考meta.stackexchange.com/questions/43019
    【解决方案2】:

    这个:

    DataContext="{Binding PropReturningSubVM2}"
    

    看起来很可疑。您正在使用 Binding 表达式,而不是指定 Source 或 RelativeSource,这意味着您希望它在您的 DataContext 引用的任何对象上查找名为 PropReturningSubVM2 的属性,并绑定该结果表达式返回你的DataContext。

    所以你这里有一种循环引用的事情;您是在声明中说您的 DataContext 是您的 DataContext 上的另一件事。在过程代码中,这很好,因为您可以完全控制它何时发生,您执行它,然后继续前进。但是 XAML 不是程序性的,它是声明性的;所以你声明,“这是我想要建立和执行的关系”,即使你声明的关系是自相矛盾的。如果它完全起作用,那只是因为框架以特定的顺序做事才使它起作用(首先属性是从父控件继承的,然后应用绑定,并且由绑定引起的属性更改不会' t 触发绑定再次运行)。显然,有时事情不会按特定顺序发生,这就是你的问题。

    我建议删除循环引用。将您的绑定更改为以下内容:

    DataContext="{Binding PropReturningSubVM2,
     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"
    

    这消除了奇怪的循环引用的东西,因为您不再根据您的自己的 DataContext 绑定您的 DataContext,您现在根据您的父 TabControl 的 DataContext 显式绑定它 - 所以没有更多的循环,没有更多的排序依赖,希望没有更多的错误。

    (如果合适,用不同的类型代替 TabControl ——理想情况下,您可能希望使用以编程方式设置 DataContext 的父控件的类型。)

    【讨论】:

    • 没有。这似乎不是问题(它第一次有效+为什么只有第二次失败?)。反正我试过了,效果完全一样。选项卡标题显示 SubVM 类型名称,指示 DataContext 已设置,绑定错误仍然存​​在,并且该部分中的任何控件均未绑定。 - &lt;TabItem Header= "{Binding}" DataContext="{Binding DataContext.PropReturningSubVM2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"&gt;
    【解决方案3】:

    据我所知,所有项目控件都会自动为其项目设置 DataContext。 本地数据上下文上的绑定应该处理该行为。 如我所见,当绑定重新评估本地 DataContext 为空并且绑定解析为更高级别的 DataContext 时会发生什么。
    我会尝试通过设置 BindingMode 来设置绑定的更新行为。如果这不起作用,我会尝试在正确的时间强制更新。 一种方法是使用多重绑定: 1. 多重绑定中的绑定是您现在的绑定 2. binding是对父datacontext的绑定

    multivalueconverter 只是在双向传递第一个值 - 这样你就可以根据你想要的任何更改来更新绑定 - 2. 绑定仅用于在父数据上下文(或另一个值更改)时进行额外更新。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 2011-11-28
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2013-05-23
      • 1970-01-01
      相关资源
      最近更新 更多