简体   繁体   中英

WPF Two-way binding to property's property to replace the parent property

I have a ViewModel that uses DependencyProperties (or INotifyPropertyChanged) that has a property of a very simple composite type like System.Windows.Point. The simple composite type doesn't use DependencyProperties or INotifyPropertyChanged, and it's intended to stay that way (it's out of my control).

What I want to do now is to create two-way data binding to the X and Y properties of the Point, but when one of these are changed, I want the entire Point class to be replaced, rather than updating just the member.

Code sample just for illustration:

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

Code-behind:

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

What I was thinking was to bind both TextBoxes directly to TestPoint property and use IValueConverter to filter out only the specific member, but then there's a problem in ConvertBack method, because the Y value is not there anymore when modifying X value.

I feel there must be a really simple solution to this that I'm not getting.

Edit:

The code above is just a simplified example, the actual application is more complex than that. The composite type has about 7 members and is commonly used throught the application, so splitting it to individual members doesn't feel right. Also I want to rely on OnChanged event of the dependency property to invoke other updates, so I really need to replace the entire class.

Why don't you use accessors ?

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

And in your 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>

I think in this case, it would be easier to introduce two DependencyProperties, one for the X-Value and one for the Y-Value and simply bind to them. In the PropertyMetadata of each DependencyProperty register a method, s.th. on each value change you can replace your Point-object, if you still need that.

You could also bind your label to a MultiBinding of both properties using an appropriate one-way-IMultiValueConverter.

As i have told in comments, you can try like this. When we add the converter to particular control's resource same instance will be used for the child controls.

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

and code behind,

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

Note: I havent added any null checks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM