【发布时间】: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