Reputation: 81
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.
Upvotes: 3
Views: 849
Reputation: 698
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>
Upvotes: 7
Reputation: 1185
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.
Upvotes: 0
Reputation: 4481
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.
Upvotes: 0