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 为:为什么它不起作用
您的代码未按预期运行的原因是DependencyProperty
s 仅在 值实际更改时调用其属性更改回调,而不是每次设置属性时。这与INotifyPropertyChanged
不同,INotifyPropertyChanged
可以编程为在实施者愿意时发送更改通知。
在您的代码中,当MainViewModel.CurrentFoobar
的属性发生更改时,您会在CurrentFoobar
本身上引发PropertyChanged
。 (顺便说一句,这通常不是你应该如何处理的,但我们现在将忽略它。)这告诉绑定更新UserControl1.Foobar
。问题是,由于CurrentFoobar
的值实际上并没有改变,UserControl1.FoobarProperty
看着新值并说“哦,这与我们已有的相同”并忽略它。它从不调用属性更改回调,因为属性实际上从未更改。
这应该怎么做
首先,摆脱_curfoobar.PropertyChanged += (s, e) => OnPropertyChanged();
。绑定系统已经知道,如果它绑定到A.B.C
,它需要监视A
、B
或C
的变化。如果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) => OnPropertyChanged();
。
上面的代码做了以下事情:
确保FoobarPropertyChanged
始终与Foobar
的当前值相关联。 (而不是任何旧值。)
每当Foobar
的值发生变化时调用UpdateFromFoobar
。
只要当前Foobar
的任何属性发生变化,就会调用UpdateFromFoobar
。
您可能希望将else
添加到if (Foobar != null)
并清空目标属性。如果有很多属性,这可以通过仅更新受影响的属性来提高效率,基于PropertyChangedEventArgs.PropertyName
;每当Foobar
的any 属性发生变化时,当前代码都会更新所有目标属性。
【讨论】:
感谢您提供格式正确的答案!我很高兴知道它以这种方式工作。你能给我一些关于这种行为的文档的指示吗?关于“应该如何做”。我知道提供的示例看起来很愚蠢。然而想象它不是一个 TextBlock 并且它没有暴露任何 DependencyProperties。我在这里的尝试是使用 DependencyProperty 包装这样的控件。 @kev 实际上,我不确定我是否知道有关该特定点的任何文档。我只是从经验中学到的。至于其余的,我已经相应地更新了答案。以上是关于DependencyProperty 未在 PropertyChanged 上更新的主要内容,如果未能解决你的问题,请参考以下文章
了解 XAML 中是不是未设置 DependencyProperty
注册期间未在 Macbook Pro 2020 M1 上检测到适用于 iOS 的 Apple Configurator
DependencyProperty.Register() 用法?