I'm attempting to bind the Data field on a WPF path object. I've tried two different approaches, but neither approach triggers an updated drawing.
Plan 1:
<Path Data="{Binding SegmentPoints, Converter={StaticResource PointsToBezier}}" ... />
SegmentPoints is an ObservableCollection<Point>
. The converter returns a new StreamGeometry object. I expected that if I replaced a point in the collection the thing would redraw. That doesn't work. It does redraw if I trigger a PropertyChanged event for SegmentPoints.
Plan 2:
<Path ...><Path.Data><PathGeometry Figures="{Binding Figures}" .../>...
Then, inside the view model, I update things like my BezierSegment.Point3
. Because the Point properties on the various segment types are DependencyProperties, I figured that I could update then and they would trigger and change up the tree. That's apparently not the case either.
So my questions: Is there some way to manually trigger an update on a Path object? (If so, I could potentially use NotifyOnTargetUpdated.) Is there some way to configure the system such that point changes on segments will trigger a redraw?
The binding in your first approach is only updated when the SegmentPoint
property changes, i.e. is assigned to a new collection. Therefore it does not need to be an ObservableCollection. Just create a new collection and raise the PropertyChanged event.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ICollection<Point> segmentPoints;
public ICollection<Point> SegmentPoints
{
get { return segmentPoints; }
set
{
segmentPoints = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SegmentPoints"));
}
}
}
}
However, in your second approach the trick is not to bind the PathGeometry's Figure
property, but instead directly assign a PathFigureCollection, either in XAML or in code.
<Canvas Background="Transparent" MouseMove="Canvas_MouseMove">
<Path Stroke="Blue" StrokeThickness="3">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection x:Name="figures"/>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
Add segments and modify their properties in code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var segment = new BezierSegment(new Point(100, 0), new Point(200, 300), new Point(300, 100), true);
var figure = new PathFigure();
figure.Segments.Add(segment);
figures.Add(figure);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
var firstSegment = figures[0].Segments[0] as BezierSegment;
firstSegment.Point2 = e.GetPosition(sender as IInputElement);
}
}
You may also create the Figures
property in code, as shown below. The MainWindow class defines a Figures
property which is assigned to the Figures
property of the PathGeometry.
<Canvas Background="Transparent" MouseMove="Canvas_MouseMove">
<Path Stroke="Blue" StrokeThickness="3">
<Path.Data>
<PathGeometry x:Name="geometry"/>
</Path.Data>
</Path>
</Canvas>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var segment = new BezierSegment(new Point(100, 0), new Point(200, 300), new Point(300, 100), true);
var figure = new PathFigure();
figure.Segments.Add(segment);
Figures = new PathFigureCollection();
Figures.Add(figure);
geometry.Figures = Figures;
}
public PathFigureCollection Figures { get; set; }
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
var firstSegment = Figures[0].Segments[0] as BezierSegment;
firstSegment.Point2 = e.GetPosition(sender as IInputElement);
}
}
Apparently, updates to the path figures won't work when you bind the Figures
property. The following binding assigns the figures once, but later changes aren't reflected in the Path.
// replace
// geometry.Figures = Figures;
// by
BindingOperations.SetBinding(geometry, PathGeometry.FiguresProperty,
new Binding
{
Path = new PropertyPath("Figures"),
Source = this
});