Question

In a WPF UI I have nodes connected by bezier paths, like so:

It might be... atomic http://nv3wrg.blu.livefilestore.com/y1pIGBd33lCC6lF-9H0MqgnL40BdNEoEemZDENzgpEI1IL2j4B-qb3qS3WlxMSys28IjqNngR7mdfvQBnPzerf4cFJQj9VqHBh4/acurve.png?psid=1

When the user drags a node around, the connecting paths need to be updated in real-time. However, I've noticed some slowdown (especially if one node is connected to many others, or multiple nodes are being dragged at once). I profiled it, and the main problem appears to be here:

Proof I actually used a profiler, so please don't be all like "OMG, premature opiumzation; you are DEMON!!" http://nv3wrg.blu.livefilestore.com/y1pjRfQYuN57yei5qdUxW4Dlh4vVCzPy8TcfEzlw_8cUicfOR6BwHCTntcQbQUspRAgBdKcItC0ZcEJbIWMKaYrCtDMOtCBKB4g/profile.png?psid=1

This is the function that is called each time either the source or destination property is changed. The geometry that makes up the path seems to be being regenerated internally each time any of the control points change. Perhaps if there were a way to prevent the geometry from being regenerated until after all the relevant dependency properties have been set?

EDIT: Mart's solution to use StreamGeometry sped it up exponentially; the function is nowhere close to a bottleneck. A little Reflecting suggests that PathGeometry uses StreamGeometry internally, and every time any of the dependency properties are changed, the StreamGeometry is recalculated. So this way just cuts out the middleman. The final result is:

private void onRouteChanged()
{
    Point src = Source;
    Point dst = Destination;
    if (!src.X.isValid() || !src.Y.isValid() || !dst.X.isValid() || !dst.Y.isValid())
    {
        _shouldDraw = false;
        return;
    }

    /*
        * The control points are all laid out along midpoint lines, something like this:
        * 
        *   -------------------------------- 
        *  |          |          |          |
        *  |   SRC    |    CP1   |          |
        *  |          |          |          |
        *   -------------------------------- 
        *  |          |          |          |
        *  |          |    MID   |          |
        *  |          |          |          |
        *   ------------------------------- 
        *  |          |          |          |
        *  |          |    CP2   |    DST   |
        *  |          |          |          |
        *   -------------------------------- 
        *   
        * This causes it to be horizontal at the endpoints and vertical
        * at the midpoint.
        */

    double mx = (src.X + dst.X) / 2;
    double my = (src.Y + dst.Y) / 2;
    Point mid = new Point(mx, my);
    Point cp1 = new Point(mx, src.Y);
    Point cp2 = new Point(mx, dst.Y);

    _geometry.Clear();
    _shouldDraw = true;
    using(StreamGeometryContext ctx = _geometry.Open())
    {
        ctx.BeginFigure(src, false, false);
        ctx.QuadraticBezierTo(cp1, mid, true, false);
        ctx.QuadraticBezierTo(cp2, dst, true, false);
    }
}

The full source code of the project is available at http://zeal.codeplex.com for the curious.

Was it helpful?

Solution

1- I would try to use StreamGeometry:

        StreamGeometry streamGeo = new StreamGeometry();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            streamGeo.Clear();
            var ctx = streamGeo.Open();
            ctx.BeginFigure(new Point(0, 0), false, false);
            ctx.QuadraticBezierTo(new Point(10, 10), new Point(10, i), true, true);
            ctx.Close();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // For 10k it took 30 ms

It looks much faster than PathGeometry+PathFigure.

When you set the Point for the QuadraticBezierSegment it recalculates everything. That's why it is slow. And more slow when it is already added to a geometry.

2- Try to use only 1 frameworkelement for all of your curves. Check this: Writing More Efficient ItemsControls

OTHER TIPS

If you don't need hit testing, context menus, tooltips for your curves, you can use simple visuals instead of framework elements.

I would imagine that your performance issues come from descending from FrameworkElement and having the WPF layout engine recompute the layout as the curve is computed.

What you can consider is to model the curve by descending from Freezable and then use a FrameworkElement (like PathGeometry) to display the actual geometry.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top