WPF 双向绑定到 属性 的 属性 以替换父 属性
WPF Two-way binding to property's property to replace the parent property
我有一个使用 DependencyProperties(或 INotifyPropertyChanged)的 ViewModel,它有一个非常简单的复合类型 属性,例如 System.Windows.Point。 简单复合类型不使用 DependencyProperties 或 INotifyPropertyChanged,它打算保持这种状态(这是我无法控制的)。
我现在想做的是创建双向数据绑定到 Point 的 X 和 Y 属性,但是当其中之一发生更改时,我想要替换整个 Point class ,而不是只更新成员。
代码示例仅供说明:
<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>
代码隐藏:
public partial class MainWindow : Window
{
public Point TestPoint
{
get { return (Point)GetValue(TestPointProperty); }
set { SetValue(TestPointProperty, value); }
}
public static readonly DependencyProperty TestPointProperty = DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}
我的想法是将两个TextBox直接绑定到TestPoint 属性并使用IValueConverter仅过滤掉特定成员,但是ConvertBack方法出现问题,因为Y值不再存在修改X值时。
我觉得一定有一个我没有得到的非常简单的解决方案。
编辑:
上面的代码只是一个简化的例子,实际的应用要复杂得多。复合类型有大约 7 个成员,并且在整个应用程序中普遍使用,因此将它拆分为单个成员感觉不对。另外我想依靠依赖项 属性 的 OnChanged 事件来调用其他更新,所以我真的需要替换整个 class.
我认为在这种情况下,引入两个 DependencyProperties,一个用于 X 值,一个用于 Y 值并简单地绑定到它们会更容易。
在每个 DependencyProperty 的 PropertyMetadata 中注册一个方法,s.th。在每次值更改时,如果您仍然需要,您可以替换您的 Point 对象。
您还可以使用适当的单向 IMultiValueConverter 将您的标签绑定到两个属性的 MultiBinding。
你为什么不使用访问器?
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>
和后面的代码,
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;
}
}
注意:我没有添加任何空检查。
我有一个使用 DependencyProperties(或 INotifyPropertyChanged)的 ViewModel,它有一个非常简单的复合类型 属性,例如 System.Windows.Point。 简单复合类型不使用 DependencyProperties 或 INotifyPropertyChanged,它打算保持这种状态(这是我无法控制的)。
我现在想做的是创建双向数据绑定到 Point 的 X 和 Y 属性,但是当其中之一发生更改时,我想要替换整个 Point class ,而不是只更新成员。
代码示例仅供说明:
<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>
代码隐藏:
public partial class MainWindow : Window
{
public Point TestPoint
{
get { return (Point)GetValue(TestPointProperty); }
set { SetValue(TestPointProperty, value); }
}
public static readonly DependencyProperty TestPointProperty = DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}
我的想法是将两个TextBox直接绑定到TestPoint 属性并使用IValueConverter仅过滤掉特定成员,但是ConvertBack方法出现问题,因为Y值不再存在修改X值时。
我觉得一定有一个我没有得到的非常简单的解决方案。
编辑:
上面的代码只是一个简化的例子,实际的应用要复杂得多。复合类型有大约 7 个成员,并且在整个应用程序中普遍使用,因此将它拆分为单个成员感觉不对。另外我想依靠依赖项 属性 的 OnChanged 事件来调用其他更新,所以我真的需要替换整个 class.
我认为在这种情况下,引入两个 DependencyProperties,一个用于 X 值,一个用于 Y 值并简单地绑定到它们会更容易。 在每个 DependencyProperty 的 PropertyMetadata 中注册一个方法,s.th。在每次值更改时,如果您仍然需要,您可以替换您的 Point 对象。
您还可以使用适当的单向 IMultiValueConverter 将您的标签绑定到两个属性的 MultiBinding。
你为什么不使用访问器?
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>
和后面的代码,
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;
}
}
注意:我没有添加任何空检查。