【问题标题】:PropertyChanged is ignored when Binding to object where reference stays the same绑定到引用保持不变的对象时忽略 PropertyChanged
【发布时间】:2019-04-11 09:32:45
【问题描述】:

我对 WPF/Binding 世界还很陌生,但现在我使用它已有一段时间并取得了一定程度的成功。

现在我遇到了一个与this question 中描述的问题非常相似的问题,但涉及的是类而不是 IEnumerable。我不确定这种行为是否也是针对某个班级的故意行为,或者是否有办法解决它。

假设我有一个简单的自定义类“Vector3”,其中包含 3 个双打 Vector3.cs

    public class Vector3
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }

        public Vector3(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public Vector3(Vector3 vec)
        {
            X = vec.X;
            Y = vec.Y;
            Z = vec.Z;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Vector3))
                return false;

            Vector3 other = obj as Vector3;

            return X == other.X && Y == other.Y && Z == other.Z;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (X.GetHashCode() * 42) ^ Y.GetHashCode() + Z.GetHashCode();
            }
        }
    }

我有一个用户控件,它公开了这种类型的 DependencyProperty

ucVector3.xaml.cs

public partial class ucVector3 : UserControl
    {
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                     typeof(Vector3), typeof(ucVector3),
                     new FrameworkPropertyMetadata(null, new PropertyChangedCallback(_OnModelChanged)));

        private static void _OnModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("Binding worked! I've received a new Vector3 " +
                              "with value X = " + (e.NewValue as Vector3).X); 
        }

        public Vector3 Value
        {
            get
            {
                return (Vector3)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }
    ...

然后我尝试使用这个用户控件绑定 Value 属性,如下例所示:

MainWindow.xaml

<Window x:Class="StackOverflow.Example.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StackOverflow.Example"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid Background="Azure">
        <Grid.RowDefinitions>
            <RowDefinition Height="5*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Content="Update" Margin="5" Grid.Row="1" Grid.ColumnSpan="2" Click="Button_Click" />
        <local:ucVector3 Value="{Binding SameReference}" Margin="5" />
        <local:ucVector3 Value="{Binding NewReference}" Margin="5" Grid.Column="1" />
    </Grid>
</Window>

MainWindow.xaml.cs

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
        SynchronizationContext uiContext = SynchronizationContext.Current;
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (uiContext != SynchronizationContext.Current)
            {
                uiContext.Send(_ =>
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }, null);
            }
            else
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        private Vector3 newValue = new Vector3(0, 0, 0);

        public Vector3 SameReference { get; set; }
        public Vector3 NewReference { get; set; }

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

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            newValue.X = newValue.X + 2;
            newValue.Y = newValue.Y + 3;
            newValue.Z = newValue.Z + 4;

            SameReference = newValue;
            NewReference = new Vector3(newValue);

            OnPropertyChanged("SameReference"); //successful notify, ignored values
            OnPropertyChanged("NewReference"); //successful notify, processed values
        }
    }

第一次按下按钮时,两个用户控件都将更新为 (3,4,5),但从第二次开始,右侧用户控件中只会更新“NewReference”属性。

我知道对于 IEnumerables,只有当引用不同时,WPF 才会传播 OnPropertyChanged 事件,并且 IEnumerable 中的更改需要调用 INotifyCollectionChanged 事件。

为什么我的“OnPropertyChanged("SameReference")”没有传播?我更改了值,我希望事件传播以更新界面,否则我不会调用事件...

这种行为是故意的吗?为什么它检查对象引用而不是它是否等于?有没有办法“强制”事件通过?或者在这种情况下我应该如何组织我的课程?

Here you can download the example solution described in this question,在 VisualStudio2015 中创建。

感谢大家的宝贵时间。

【问题讨论】:

  • OnPropertyChanged("SameReference") 被忽略,因为 SameReference 属性的值实际上并没有改变。它仍然是 newValue 对象。为了完成这项工作,Vector3 还必须实现 INotifyPropertyChanged 并为其 X、Y 和 Z 属性触发 PropertyChanged 事件。
  • 谢谢你的回答,是的,我知道这是原因,但我不明白为什么它应该这样工作,我需要......“Vector3”是一个库类不应该对“图形”一无所知。这就是模型/视图模型的原因,但是如果这是默认行为,我应该将每个对象都包装在视图模型中,如果它只检查 object.Equal 而不是只关注对象的引用,那就太糟糕了,或者至少选择强制事件,即使对象相同......
  • 调用SameReference = new Vector3(SameReference) 看起来并不太复杂...
  • 不,它并不复杂,这是我现在正在使用的解决方案,但它看起来更像是一个 hack,而不是一个解决方案。每次更改值时重新创建一个新对象并不是我所说的干净代码:) 我希望有另一种更“正确”的方式来强制 WPF 传播事件...

标签: c# wpf xaml data-binding


【解决方案1】:

从我在你的项目中看到的,你必须在你的 ucVector3.xaml.cs 中注释这些行,这些行避免更新你的用户控件:

        if (value.X == lastNudValue.X && value.Y == lastNudValue.Y && value.Z == lastNudValue.Z)
           return;

【讨论】:

  • 感谢您的回答。不,这不是问题,通过 PropertyChanged 事件到达用户控件的值是不同的(就像您在每次按下按钮时在 MainWindows.xaml.cs 中看到的那样,我将 2、3、4 添加到 x、y、 z 的 Vector3),事实上,当我调用 OnPropertyChanged("NewReference") 事件到达用户控件并且确实更新时,使用 OnPropertChanged("SameReference") 而不是 WPF 系统引发事件并且不会到达用户控件....
猜你喜欢
  • 2010-10-23
  • 2018-07-23
  • 2011-06-15
  • 1970-01-01
  • 2014-10-27
  • 2021-04-03
  • 2021-06-11
  • 2017-09-28
  • 1970-01-01
相关资源
最近更新 更多