当 UserControl 有 DataContext 时,UserControl 的 DependencyProperty 为 null

Posted

技术标签:

【中文标题】当 UserControl 有 DataContext 时,UserControl 的 DependencyProperty 为 null【英文标题】:UserControl's DependencyProperty is null when UserControl has a DataContext 【发布时间】:2015-02-16 01:42:56 【问题描述】:

我已将 DependencyProperty 添加到我的视图中,绑定到 DependencyProperty 有效,但前提是我没有同时设置 DataContext。

GenericView.xaml

<UserControl x:Class="GenericProject.View.GenericView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Button Command="Binding VMFactory.CreateViewModelCommand, RelativeSource=RelativeSource AncestorType=x:Type UserControl" />
        <TextBox IsEnabled="False" Text="Binding SomeProperty, Mode=OneWay" />
    </StackPanel>
</UserControl>

GenericView.xaml.cs

public partial class GenericView : UserControl

    // The DependencyProperty for VMFactory.
    public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<GenericViewModel>), typeof(GenericView));

    public VMFactoryViewModel<GenericViewModel> VMFactory
    
        get  return (VMFactoryViewModel<GenericViewModel>)GetValue(VMFactoryProperty); 
        set  SetValue(VMFactoryProperty, value); 
    

    public GenericView()
    
        InitializeComponent();
    

在这里,我创建了两个视图来说明手头的问题。第一个视图中的 VMFactory 绑定将失败,因为我设置了 DataContext。第二个视图会成功,这个行为的原因是什么?

MainPage.xaml

<vw:GenericView DataContext="Binding Generic" VMFactory="Binding GenericFactory" />
<vw:GenericView VMFactory="Binding GenericFactory" />

【问题讨论】:

(如果它不适用,请忽略这个粗俗的评论!)您没有创建 ViewModel 来封装您的 UserControls 的逻辑,是吗?因为没有。您不会为您的 UserControls 创建 ViewModel。 UserControls 应该只包含 UI 逻辑,并且它们应该像任何其他控件一样设计。 TextBox 有 TextBoxViewModel 吗? 否。 您遇到的 PITA 是因为您正在尝试实现反模式。停下来。这是我的答案,其中包含更多详细信息***.com/a/25796096/1228 ... ... 什么? UserControl 不应该有它公开的命令,它应该有一个 ICommand 类型的公共 DP,它使用它来允许其他 VM 将 它们的 命令绑定到它。您不应该有 VMFactoryUserControl 和 VMFactoryViewModel。那就是那里的代码气味。所以我不清楚你在这里做什么,我猜。 如果您开始遵循实际的 MVVM 模式,而不是创建一个看起来相似但实际上使事情更复杂的反模式,那么绑定将开始更好地工作。 我不确定我是否同意这个@Will。复合 UI 是使用视图模型和用户控件构建的。 Microsoft 的 Patterns & Practices 团队使用用户控件和视图模型为 WPF 和 Windows Store 应用程序提供 Prism 以实现模块化。您的“文本框没有视图模型”不是一个公平的论点。文本框是控件,而不是用户控件。有区别。 @我已经做到了。使用 prism 和用户控件构建了两个企业应用程序。它们运行良好,维护它们没有任何问题。 【参考方案1】:

这是一个相当常见的绑定“陷阱”...

为了访问VMFactory,您需要使用...将您的UserControl 绑定到自身。

DataContext="Binding RelativeSource=RelativeSource Self"

然后,您不会将 GenericView 项目上的 DataContext 绑定到其他任何地方。

但是,如果您打算将其他值绑定到UserControl 外部的VMFactory(即&lt;vw:GenericView VMFactory=Binding ..."/&gt;),则应将RelativeSource 与模式FindAncestor 一起用于UserControl 类型。

<!-- Shortened to show pertinent Binding -->
<ctrl:CommandTextBox Command="Binding VMFactory.CreateViewModelCommand, RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type UserControl"/>

【讨论】:

您的第二个示例看起来像我最初的尝试,但对我不起作用,我再次尝试并了解到它确实有效,但前提是我没有同时设置 DataContext 和 VMFactory 属性用户控件。想法? DataContext 有意义,但不是 VMFactory。你试过只设置 VMFactory 吗? 这就是我试图传达的,如果我只设置 VMFactory 就可以了。如果我还设置了 DataContext 则命令被破坏。 您需要记住 UserControl 级别的 DataContext 是继承的,因此一旦绑定它,您可以将 Command 绑定视为Binding UserControl.DataContext.VMFactory.CreateViewModelCommand...。为什么要绑定 VMFactory 和 DataContext? 我以为我理解继承?我的印象是,RelativeSource、ElementName 等允许我将我的命令设置为 Binding UserControl.VMFactory.CreateViewModelCommand... 并且仍然将 Binding UserControl.DataContext.SomeProperty 用于文本框和其他项目。【参考方案2】:

我有一个可行的解决方案,似乎控件的属性是相对于控件的 DataContext 绑定的?

我当然知道控件中的项目将相对于 DataContext 绑定,但我显然以前从未以这种方式使用过控件,并且不明白控件的属性也会继承设置的 DataContext 的范围。基本上我的视图中的所有内容都是正确的,但是绑定到我的 DependencyProperty 失败了。

GenericView.xaml

<!-- Everything in here was correct. -->
<UserControl x:Class="GenericProject.View.GenericView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Button Command="Binding VMFactory.CreateViewModelCommand, RelativeSource=RelativeSource AncestorType=x:Type UserControl" />
        <TextBox IsEnabled="False" Text="Binding SomeProperty, Mode=OneWay" />
    </StackPanel>
</UserControl>

MainPage.xaml

<!-- This is where I messed up earlier, VMFactory requires a relative binding. -->
<vw:GenericView DataContext="Binding Generic"
                VMFactory="Binding DataContext.GenericFactory, RelativeSource=RelativeSource AncestorType=x:Type Page" />

【讨论】:

【参考方案3】:

正如@toadflakz 所说,这是 WPF 中一个非常常见的问题,当我学习 WPF 时,我花了一段时间才弄清楚。幸运的是,解决方案很简单。假设我们有一个 UserControl 有一个对象集作为它的 DataContext 和另一个集作为在 UserControl 中声明的 DependencyProperty 的值......你的情况。

UserControl XAML 中,您可以像往常一样将数据绑定到设置为DataContext 的对象的属性:

<TextBlock Text="Binding PropertyFromDataContextObject" />

如果你想将数据绑定到来自对象集的对象为DependencyProperty的值,你可以简单地使用RelativeSource Binding

<TextBlock Text="Binding PropertyFromDependencyPropertyObject, RelativeSource=
    RelativeSource AncestorType=x:Type YourPrefix:YourUserControl" />

请注意,只要设置了DataContextDependencyProperty 属性,这两个Bindings 就可以在同一个UserControl 中一起使用。

【讨论】:

我了解绑定,但它没有按建议工作?如果设置了 DataContext,DependencyProperty(VMFactory) 似乎为空?如果您查看我的 MainPage.xaml,您会看到我已经创建了两个 GenericView 实例以进行故障排除,如果我将 Loaded 事件处理程序添加到 UserControl,我可以看到具有 DataContext 绑定的实例将具有空 VMFactory 并且不会运行正如预期的那样。而没有 DataContext 的实例将具有 VMFactory 对象,并且命令将按预期执行。想法? 如果您只是按照我的示例进行操作,它应该可以工作......您显然已经过度复杂了您的代码。从DataContext 对象访问属性时,不应使用任何RelativeSource Binding,因为这会将数据源更改为远离DataContext。在一个新项目中进行实验。此外,您应该在 Visual Studio 的输出窗口中出现 Binding 错误,告诉您问题所在。 我没有使用RelativeSource 来访问DataContext? TextBox 绑定访问 DataContext,Command 绑定访问 VMFactory。每个的语法都与您的示例相同?

以上是关于当 UserControl 有 DataContext 时,UserControl 的 DependencyProperty 为 null的主要内容,如果未能解决你的问题,请参考以下文章

当焦点可见时,将焦点设置为usercontrol

UserControl图片显示报错问题

将UserControl显示为弹出窗口

[WIN]如何让您的UserControl显示时不闪烁

WPF 导航和销毁当前 UserControl

如何在 Blend 中动态更改 UserControl 的外观?