使用 DataTemplate 时更改内容会更改 DataContext

Changing content changes DataContext when using DataTemplate

X 问题。

我想暂时放大(让它占据全部可用space)一部分window内容。

Window 布局相当复杂:许多嵌套面板、拆分器、要放大的内容有 10 层深。将 Visibility 更改为拉伸内容是不够的(感谢拆分器)并且看起来非常复杂。

Y问题

我决定将该内容移动到用户控件中并执行类似(伪代码)

if(IsEnlarged)
{
    oldContent = window.Content; // store
    window.Content = newContent;
}
else
    window.Content = oldContent; // restore

没问题。它在测试项目中运行完美......直到我开始使用数据模板。

问题:如果使用数据模板,那么一旦 window.Content = newContent 出现,newContent.DataContext 就会丢失并与 window.DataContext 相同。这将触发各种绑定错误,附加行为突然更改为默认值等。各种不好的东西。

问题:为什么 DataContext 会发生变化?如何prevent/fix这个问题?


这是一个重现(抱歉,不能再短了):

MainWindow.xaml 包含

<Window.Resources>
    <DataTemplate DataType="{x:Type local:ViewModel}">
        <local:UserControl1 />
    </DataTemplate>
</Window.Resources>
<Grid Background="Gray">
    <ContentControl Content="{Binding ViewModel}" />
</Grid>

MainWindow.cs 包含

public ViewModel ViewModel { get; } = new ViewModel();

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
}

UserControl1.xaml 包含

<Button Width="100"
        Height="100"
        CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
        Command="{Binding Command}" />

ViewModel(使用 DelegateCommand

public class ViewModel
{
    public DelegateCommand Command { get; set; }

    bool _set;
    object _content;

    public ViewModel()
    {
        Command = new DelegateCommand(o =>
        {
            var button = (Button)o;
            var window = Window.GetWindow(button);
            _set = !_set;
            if (_set)
            {
                _content = window.Content;
                var a = button.DataContext; // a == ViewModel
                window.Content = button;
                var b = button.DataContext; // b == MainWindow ??? why???
            }
            else
                window.Content = _content;
        });
    }
}

var a = ...上设置断点,启动程序,点击按钮,执行步骤并观察button.DataContext以及Output中的绑定错误window.

您一定是在尝试将 DataTemplate 用作 ContentTemplate 用于 ContentControl。由于 ContentTemplateContent 进行操作,因此它将使用 Content 作为其 DataContext。您的 Content 包含 ViewModel.

一旦您的 Button 不再是您的 DataTemplate 的一部分,那么它将使用 MainWindow's DataContext.

没有看到您的评论,我假设您希望 UserControlDataContext 保持完整,即使您的 UserControl 不属于 DataTemplate

因此,Button 的简单集 DataContext 显式使用 XAML 使用 RelativeSource

例如,

<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" /> 

在代码中使用 DataContext 不是个好主意。

好的,这里有一些一般的想法。

如果您将对象 (viewmodel) 绑定到内容控件的内容 属性,则 wpf 使用 DataTemplate 来可视化该对象。如果您没有 DataTemplate,您只会看到 object.ToString()。 DataContext继承是指DataContext被继承给子元素。所以 real 用户控件将从父控件继承 DataContext。这些是您在创建 UserControl 时在 Whosebug 上发现的常见错误 - 它们通常会破坏 DataContext 继承并将 DataContext 设置为自身或新的 DataContext。