DependencyProperty 绑定问题

Posted

技术标签:

【中文标题】DependencyProperty 绑定问题【英文标题】:Issue with DependencyProperty binding 【发布时间】:2021-09-05 22:55:42 【问题描述】:

我创建了一个小的文件浏览器控件:

<UserControl x:Class="Test.UserControls.FileBrowserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="44" d:DesignWidth="461" Name="Control">
    <Grid Margin="3">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox  Margin="3" Text="Binding SelectedFile" IsReadOnly="True" TextWrapping="Wrap" />
        <Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="Binding BrowseCommand" />
    </Grid>
</UserControl>

后面有以下代码:

public partial class FileBrowserControl : UserControl

    public ICommand BrowseCommand  get; set; 
    //The dependency property
    public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
        typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
    public string SelectedFile  get return (string)GetValue(SelectedFileProperty);  set SetValue(SelectedFileProperty, value);
    //For my first test, this is a static string
    public string Filter  get; set; 

    public FileBrowserControl()
    
        InitializeComponent();
        BrowseCommand = new RelayCommand(Browse);
        Control.DataContext = this;
    
    private void Browse()
    
        SaveFileDialog dialog = new SaveFileDialog();
        if (Filter != null)
        
            dialog.Filter = Filter;
        
        if (dialog.ShowDialog() == true)
        
            SelectedFile = dialog.FileName;
        
    

我是这样使用它的:

<userControls:FileBrowserControl SelectedFile="Binding SelectedFile" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>

(SelectedFile 是使用该控件的用户控件的 ViewModel 的属性)

目前的问题是,当我点击浏览时,用户控件中的文本框正在正确更新,但未设置 viewmodel 父控件的 SelectedFile 属性(未调用 set 属性)。

如果我将绑定的模式设置为双向,我得到了这个异常:

An unhandled exception of type 'System.***Exception' occurred in Unknown Module.

那么我做错了什么?

【问题讨论】:

【参考方案1】:

主要问题是您在其构造函数中将 UserControl 的 DataContext 设置为自身:

DataContext = this;

您不应该这样做,因为它会破坏任何基于 DataContext 的绑定,即到由 DataContext 属性的属性值继承提供的视图模型实例

相反,您可以像这样更改 UserControl 的 XAML 中的绑定:

<TextBox Text="Binding SelectedFile,
                RelativeSource=RelativeSource AncestorType=UserControl" />
    

现在,当您使用 UserControl 并编写类似的绑定时

<userControls:FileBrowserControl SelectedFile="Binding SelectedFile" />

SelectedFile 属性绑定到视图模型中的 SelectedFile 属性,该属性应位于从父控件继承的 DataContext 中。

【讨论】:

好像好多了,谢谢。您似乎非常了解这些良好做法。我还有一个其他相关的小问题:目前这个 userControl 的所有逻辑都在后面的代码中。这很常见吗?因为它并没有真正尊重我试图遵循的 MVVM 架构。 用户控件通常在其代码隐藏中包含其所有逻辑,至少是与视图相关的那些逻辑部分。然而,其背后的代码不应(大量)修改其视图模型。 好的,但是假设我有一个过滤器、InitialDirectory、SelectedFile,......我应该无法将它绑定到包含这些字段的 ViewModel 吗? 如答案所示,您可以将这些属性绑定到您使用控件的视图模型属性。然而,UserControl 的 XAML 也可以直接访问视图模型属性作为绑定源,但是您可能不需要在 UserControl 中声明其他属性。如果例如您的控件的 XAML 将直接访问视图模型的 SelectedFile 属性,控件本身不应有额外的 SelectedFile 属性。【参考方案2】:

永远不要在 usercontrol 中设置 UserControl 的 DataContext:

这是错误的:

this.DataContext = someDataContext;

因为如果有人会使用你的用户控件,它的常见做法是设置它的数据上下文,它与你之前设置的有冲突

<my:SomeUserControls DataContext="Binding SomeDataContext" />

将使用哪一个?好吧,这取决于...

这同样适用于 Name 属性。您不应该像这样将名称设置为 UserControl:

<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />

因为和

冲突
<my:SomeUserControls Name="SomeOtherName" />

解决方案: 在您的控件中,只需使用RelativeSource Mode=FindAncestor:

<TextBox Text="Binding SelectedFile, RelativeSource=RelativeSource AncestorType="userControls:FileBrowserControl"" />

关于所有这些第三方控件是如何完成的问题:它们使用 TemplateBinding。但是 TemplateBinding 只能在 ControlTemplate 中使用。 http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate

在 usercontrol 中,xaml 表示 UserControl 的内容,而不是 ControlTemplate/

【讨论】:

【参考方案3】:

使用这个:

<userControls:FileBrowserControl SelectedFile="Binding SelectedFile" ...

FileBrowserControl 的 DataContext 已设置为自身,因此您实际上要求绑定到 DataContext 是 FileBrowserControl,而不是父 ViewModel 的 SelectedFile。

为您的视图命名并改用 ElementName 绑定。

SelectedFile="Binding DataContext.SelectedFile, ElementName=element"

【讨论】:

然后在我的脑海中我听到了 Unreal 的声音:God like :) 非常感谢,我没想到父级中的绑定仍然引用了子级的 DataContext。但我有点失落。所有这些第三方库如何使他们的用户控件不需要这个ElementName 这是个好问题。我认为这是因为他们使用 CustomControls,而不是 UserControls,但我并不完全确定。您应该将其作为一个新问题发布。 没问题 :-) 如果问题解决了您的问题,您还应该将问题标记为已回答;-) 谢谢。

以上是关于DependencyProperty 绑定问题的主要内容,如果未能解决你的问题,请参考以下文章

绑定是不是仅适用于 DependencyProperty?

UWP DependencyProperty 绑定和 DataTemplate 绑定

只能在 DependencyObject 的 DependencyProperty 上设置“绑定”

设置绑定到 WPF 用户控件内的自定义 DependencyProperty

尝试绑定此 ValueConverter 时出现“只能在 DependencyObject 的 DependencyProperty 上设置绑定”错误

内部字段更改时强制对 DependencyProperty 进行绑定更新