C# WPF双向绑定到属性';s属性替换父属性
我有一个ViewModel,它使用DependencyProperties(或INotifyPropertyChanged),该属性具有非常简单的复合类型,如System.Windows.Point简单复合类型不使用DependencyProperties或INotifyPropertyChanged,并且打算保持这种状态(我无法控制) 我现在要做的是创建指向点的X和Y属性的双向数据绑定,但当其中一个属性发生更改时,我希望替换整个Point类,而不是只更新成员 代码示例仅用于说明:C# WPF双向绑定到属性';s属性替换父属性,c#,wpf,data-binding,dependency-properties,inotifypropertychanged,C#,Wpf,Data Binding,Dependency Properties,Inotifypropertychanged,我有一个ViewModel,它使用DependencyProperties(或INotifyPropertyChanged),该属性具有非常简单的复合类型,如System.Windows.Point简单复合类型不使用DependencyProperties或INotifyPropertyChanged,并且打算保持这种状态(我无法控制) 我现在要做的是创建指向点的X和Y属性的双向数据绑定,但当其中一个属性发生更改时,我希望替换整个Point类,而不是只更新成员 代码示例仅用于说明: <Wi
<Window ...>
<StackPanel>
<TextBox Text="{Binding TestPoint.X, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
<TextBox Text="{Binding TestPoint.Y, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
<!-- make following label update based on textbox changes above -->
<Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"/>
</StackPanel>
</Window>
我当时的想法是将两个文本框直接绑定到TestPoint属性,并使用IValueConverter仅过滤出特定的成员,但是ConvertBack方法中存在一个问题,因为修改X值时Y值不再存在
我觉得一定有一个非常简单的解决办法,我没有得到
编辑:
上面的代码只是一个简化的示例,实际应用程序要比这个复杂得多。复合类型大约有7个成员,通常在应用程序中使用,因此将其拆分为单个成员感觉不太正确。另外,我想依靠dependency属性的OnChanged事件来调用其他更新,因此我确实需要替换整个类。我认为在这种情况下,引入两个DependencyProperties会更容易,一个用于X值,一个用于Y值,并简单地绑定到它们。 在每个DependencyProperty寄存器的PropertyMetadata中,有一个方法s.th。在每次更改值时,如果仍然需要,可以替换点对象
您还可以使用适当的单向IMultiValueConverter将标签绑定到这两个属性的多重绑定。为什么不使用访问器
public partial class MainWindow : Window
{
public Point TestPoint
{
get { return (Point)GetValue(TestPointProperty); }
set { SetValue(TestPointProperty, value); }
}
public double TestPointX
{
get { return this.TestPoint.X; }
set
{
SetValue(TestPointProperty, new Point(value, this.TestPointY);
}
}
public double TestPointY
{
get { return this.TestPoint.Y; }
set
{
SetValue(TestPointProperty, new Point(this.TestPointX, value);
}
}
public static readonly DependencyProperty TestPointProperty = DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}
在您的XAML中:
<Window ...>
<StackPanel>
<TextBox Text="{Binding TestPointX, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
<TextBox Text="{Binding TestPointY, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
<Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"/>
</StackPanel>
</Window>
正如我在评论中所说,您可以这样尝试。当我们将转换器添加到特定控件的资源时,子控件将使用相同的实例
<Grid>
<Grid.Resources />
<StackPanel>
<StackPanel>
<StackPanel.Resources>
<local:PointConverter x:Key="PointConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
<TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
<!-- make following label update based on textbox changes above -->
<Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</StackPanel>
<StackPanel>
<StackPanel.Resources>
<local:PointConverter x:Key="PointConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
<TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
<!-- make following label update based on textbox changes above -->
<Label Content="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</StackPanel>
</StackPanel>
</Grid>
注意:我没有添加任何空检查 您可以在转换器内保存另一个值,您是否尝试过这样做?您的意思是作为转换器的成员?我没有想到这一点。我想每个字段都需要单独的转换器实例,对吗?安全吗?是否保证转换期间存储的Y值在执行ConvertBack时仍然有效?当绑定到
X
和Y
属性时,请注意潜在的内存泄漏。由于点
不是INotifyPropertyChanged
,并且它们不是从属属性
绑定将使用属性描述符
,如果绑定不是一次性的,则可能导致泄漏:是的,每个字段都需要单独的转换器实例(技术上是每个x和y字段)。是的,当ConverBack执行时,存储的值将是有效的。对不起,我不能完全分离这些值。我在原始问题的底部添加了解释。对于一个简单的问题来说,这是一个有点冗长的解决方案,但我认为这将是最安全和可读的解决方案,谢谢!
<Grid>
<Grid.Resources />
<StackPanel>
<StackPanel>
<StackPanel.Resources>
<local:PointConverter x:Key="PointConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
<TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
<!-- make following label update based on textbox changes above -->
<Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</StackPanel>
<StackPanel>
<StackPanel.Resources>
<local:PointConverter x:Key="PointConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
<TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
<!-- make following label update based on textbox changes above -->
<Label Content="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</StackPanel>
</StackPanel>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public Point TestPoint
{
get
{
return (Point)GetValue(TestPointProperty);
}
set
{
SetValue(TestPointProperty, value);
}
}
// Using a DependencyProperty as the backing store for TestPoint. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestPointProperty =
DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
public Point TestPoint2
{
get
{
return (Point)GetValue(TestPoint2Property);
}
set
{
SetValue(TestPoint2Property, value);
}
}
// Using a DependencyProperty as the backing store for TestPoint. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestPoint2Property =
DependencyProperty.Register("TestPoint2", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}
public class PointConverter : IValueConverter
{
double knownX = 0.0;
double knownY = 0.0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter.ToString() == "x")
{
knownX = ((Point)value).X;
return ((Point)value).X;
}
else
{
knownY = ((Point)value).Y;
return ((Point)value).Y;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Point p = new Point();
if (parameter.ToString() == "x")
{
p.Y = knownY;
p.X = double.Parse(value.ToString());
}
else
{
p.X = knownX;
p.Y = double.Parse(value.ToString());
}
return p;
}
}