Question

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?

Was it helpful?

Solution

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
    });
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top