DependencyProperty 未在 PropertyChanged 上更新

Posted

技术标签:

【中文标题】DependencyProperty 未在 PropertyChanged 上更新【英文标题】:DependencyProperty not updating on PropertyChanged 【发布时间】:2021-06-23 09:43:55 【问题描述】:

在 ViewModel 中,我有一个本身实现 INotifyPropertyChanged 的​​属性。绑定到 DependencyProperty 时,即使我提高了属性更改,当我仅在绑定到 DependencyProperty 的当前实例上设置属性时,我也不会进入 DependencyProperty 更改的回调。 (详情见下方代码)

为什么这个构造会失败?

实现我想要的一种方法是在每次更改其属性时将 CurrentFoobar 设置为新实例。

是否存在更漂亮的方法来实现想要的行为?

编辑: 我知道这个特定的示例可能看起来无关紧要,因为可以简单地绑定到 TextBlocks Text 属性。但是,我有兴趣为不公开与 TextBlock 不同的正确依赖属性的控件做 类似的操作


代码

我有一个 ViewModel,其属性为 Foobar 类型的“CurrentFoobar”。

public class MainViewModel : INotifyPropertyChanged

    public  MainViewModel()
    
        var foobar = new Foobar() Foo = "Hello", Bar = 42;
        CurrentFoobar = foobar;
        updateCommand = new UpdateCommand(this);
    

    private UpdateCommand updateCommand;

    public ICommand UpdateCommand
    
        get => updateCommand;
    

    private Foobar _curfoobar;
    public Foobar CurrentFoobar
    
        get => _curfoobar;
        set
        
            _curfoobar = value;
            _curfoobar.PropertyChanged += (s, e) => OnPropertyChanged();
            OnPropertyChanged();
        
    

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    

Foobar 是一个具有两个属性 string Foo 和 int Bar 的类。 Foobar 实现 INotifyPropertyChanged。

public class Foobar : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    

    private string _foo;
    public string Foo 
    
        get => _foo;
        set
        
            _foo = value;
            OnPropertyChanged();
        
    

    private int _bar;
    public int Bar
    
        get => _bar;
        set
        
            _bar = value;
            OnPropertyChanged();
        
    

在 CurrentFoobar 设置器中,我订阅了当前 Foobar 实例上的 PropertyChanged,并引发 OnPropertyChanged。

    private Foobar _curfoobar;
    public Foobar CurrentFoobar
    
        get => _curfoobar;
        set
        
            _curfoobar = value;
            _curfoobar.PropertyChanged += (s, e) => OnPropertyChanged();
            OnPropertyChanged();
        
    

此外,我有一个带有 CustomControl 的视图,它公开了一个 DependencyProperty“FoobarProperty”。 我将此 DependencyProperty 绑定到我的 ViewModels CurrentFoobar。

public partial class UserControl1 : UserControl

    public UserControl1()
    
        InitializeComponent();
    

    
    public static readonly DependencyProperty FoobarProperty = DependencyProperty.Register(
        "Foobar", typeof(Foobar), typeof(UserControl1), new PropertyMetadata(default(Foobar), FoobarPropertyChangedCallback));

    private static void FoobarPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        var uc1 = d as UserControl1;
        var newfb = e.NewValue as Foobar;
        uc1.TextBlock1.Text = newfb.Foo;
        uc1.TextBlock2.Text = newfb.Bar.ToString();
    

    public Foobar Foobar
    
        get  return (Foobar) GetValue(FoobarProperty); 
        set  SetValue(FoobarProperty, value); 
    

如果我将 CurrentFoobar 设置为 Foobar 的新实例,则 DependencyProperty 会按预期更新。但是,如果我只是更改 CurrentFoobar 的当前实例的属性,它不会。自从我提出 CurrentFoobar 的属性更改后,我本来希望它会更新。

为了更好的衡量,这里是更新命令:

public class UpdateCommand : ICommand


    private MainViewModel _vm;
    public UpdateCommand(MainViewModel vm)
    
        _vm = vm;
    

    public bool CanExecute(object parameter)
    
        return true;
    

    public void Execute(object parameter)
    
        _vm.CurrentFoobar.Foo = "World";
        _vm.CurrentFoobar.Bar = 84;
    

    public event EventHandler CanExecuteChanged;

【问题讨论】:

"我希望它会更新,因为我提出了 CurrentFoobar 的属性更改。" - 不是那样工作的 在 xaml 中使用绑定而不是 uc1.TextBlock1.Text = newfb.Foo; uc1.TextBlock2.Text = newfb.Bar.ToString(); @ASh 不,显然不。你知道原因吗?我本来希望 DependencyProperty 侦听 PropertyChanged 事件。抱歉没有发布 xaml - CurrentFoobar 绑定到 xaml 中的 FoobarProperty 为: 【参考方案1】:

为什么它不起作用

您的代码未按预期运行的原因是DependencyPropertys 仅在 值实际更改时调用其属性更改回调,而不是每次设置属性时。这与INotifyPropertyChanged 不同,INotifyPropertyChanged 可以编程为在实施者愿意时发送更改通知。

在您的代码中,当MainViewModel.CurrentFoobar 的属性发生更改时,您会在CurrentFoobar 本身上引发PropertyChanged。 (顺便说一句,这通常不是你应该如何处理的,但我们现在将忽略它。)这告诉绑定更新UserControl1.Foobar。问题是,由于CurrentFoobar 的值实际上并没有改变,UserControl1.FoobarProperty 看着新值并说“哦,这与我们已有的相同”并忽略它。它从不调用属性更改回调,因为属性实际上从未更改。

应该怎么做

首先,摆脱_curfoobar.PropertyChanged += (s, e) => OnPropertyChanged();。绑定系统已经知道,如果它绑定到A.B.C,它需要监视ABC 的变化。如果B 发生变化,它知道需要更新B 的值,然后还要重新评估C

问题是您没有一直使用绑定系统。在FoobarPropertyChangedCallback 中,您手动为 UI 元素分配值。相反,您应该在具有更多绑定的 XAML 中执行此操作。像这样的东西(在你的UserControl1.xaml):

<TextBlock Name="TextBlock1" Text="Binding Foobar.Foo"/>
...
<TextBlock Name="TextBlock2" Text="Binding Foobar.Bar"/>

附带说明:要使绑定在UserControl 中工作,您需要正确设置DataContext。它的设置需要与Window 稍有不同。我不会在这里讨论,但this answer 完美地解释了它。 (确保您查看我链接到的具体答案,在撰写本文时接受的答案是错误的)。

UserControl中没有绑定

阅读您的评论后,您可以通过以下方法使这项工作在 UserControl 中不进行数据绑定(因为没有可用作目标的依赖项属性)。

private static void FoobarPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)

    var self = d as UserControl1;
    if (e.OldValue != null)  (e.OldValue as Foobar).PropertyChanged -= self.FoobarPropertyChanged; 
    if (e.NewValue != null)  (e.NewValue as Foobar).PropertyChanged += self.FoobarPropertyChanged; 
    self.UpdateFromFoobar();


private void FoobarPropertyChanged(object sender, PropertyChangedEventArgs e)

    UpdateFromFoobar();


private void UpdateFromFoobar()

    //Update all targets with values from current Foobar
    if (Foobar != null)
    
        TextBlock1.Text = Foobar.Foo;
        TextBlock2.Text = Foobar.Bar.ToString();
    

您仍会从 MainViewModel 中删除 _curfoobar.PropertyChanged += (s, e) =&gt; OnPropertyChanged();

上面的代码做了以下事情:

    确保FoobarPropertyChanged 始终与Foobar 的当前值相关联。 (而不是任何旧值。)

    每当Foobar 的值发生变化时调用UpdateFromFoobar

    只要当前Foobar 的任何属性发生变化,就会调用UpdateFromFoobar

您可能希望将else 添加到if (Foobar != null) 并清空目标属性。如果有很多属性,这可以通过仅更新受影响的属性来提高效率,基于PropertyChangedEventArgs.PropertyName;每当Foobarany 属性发生变化时,当前代码都会更新所有目标属性。

【讨论】:

感谢您提供格式正确的答案!我很高兴知道它以这种方式工作。你能给我一些关于这种行为的文档的指示吗?关于“应该如何做”。我知道提供的示例看起来很愚蠢。然而想象它不是一个 TextBlock 并且它没有暴露任何 DependencyProperties。我在这里的尝试是使用 DependencyProperty 包装这样的控件。 @kev 实际上,我不确定我是否知道有关该特定点的任何文档。我只是从经验中学到的。至于其余的,我已经相应地更新了答案。

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

了解 XAML 中是不是未设置 DependencyProperty

注册期间未在 Macbook Pro 2020 M1 上检测到适用于 iOS 的 Apple Configurator

DependencyProperty.Register() 用法?

自定义 DependencyProperty 与 RoutedEvent

NDK 未在 android studio 中配置问题

INotifyPropertyChanged 和 DependencyProperty 有啥关系?