C# WPF双向绑定到属性';s属性替换父属性

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

我有一个ViewModel,它使用DependencyProperties(或INotifyPropertyChanged),该属性具有非常简单的复合类型,如System.Windows.Point简单复合类型不使用DependencyProperties或INotifyPropertyChanged,并且打算保持这种状态(我无法控制)

我现在要做的是创建指向点的X和Y属性的双向数据绑定,但当其中一个属性发生更改时,我希望替换整个Point类,而不是只更新成员

代码示例仅用于说明:

<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;
    }
}