wpfcurvedrawingvisual

Custom DrawingVisual making application sluggish


I'm wanting to render a bezier curve that will contain many hundreds of points. This curve doesn't need to be hit testable or interactable in any way, so I thought I'd try a Visual as that seems to be the most light weight.

Using the code below though, why is it causing the rest of the application to run slowly? for example, window resizing is very slow.

I'm just looking for the most efficient way to render curves without any of the input handling functionality (even with this example, you can hook up to the MouseOver event and it will only fire when your cursor is actually over the lines, so it looks like I'm still paying for that (setting IsHitTestVisiable doesn't seem to help with the performance))

public class VisualHost : FrameworkElement
{
    VisualCollection _children;

    public VisualHost()
    {
        _children = new VisualCollection(this);
        _children.Add(CreateDrawingVisualRectangle());

    }

    DrawingVisual CreateDrawingVisualRectangle()
    {
        var drawingVisual = new DrawingVisual();
        var drawingContext = drawingVisual.RenderOpen();

        var geometry = new StreamGeometry();
        using (var ctx = geometry.Open())
        {
            ctx.BeginFigure(new Point(0, 0), false, false);
            var r = new Random();

            for (int i = 0; i < 500; ++i)
            {
                var p1 = new Point(r.Next(0, 1000), r.Next(0, 1000));
                var p2 = new Point(r.Next(0, 1000), r.Next(0, 1000));
                ctx.QuadraticBezierTo(p1, p2, true, false);
            }
        }

        geometry.Freeze();

        drawingContext.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry);
        drawingContext.Close();

        return drawingVisual;
    }

    protected override int VisualChildrenCount
    {
        get { return _children.Count; }
    }

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index >= _children.Count)
        {
            throw new ArgumentOutOfRangeException();
        }

        return _children[index];
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        Content = new VisualHost();
    }
}

Solution

  • You could use a BitmapCache to create a bitmap that caches the rendering of the DrawingVisual...so that when your FrameworkElement is invalided (due to the sizing) the cached bitmap is used to provide the "visual bits", instead of the slower route of having to render the drawing instructions inside of the "DrawingVisual" again (i.e. what was described by StreamGeometry in the drawingcontext).

       DrawingVisual CreateDrawingVisualRectangle()
        {
            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();
    
            var geometry = new StreamGeometry();
            using (var ctx = geometry.Open())
            {
                ctx.BeginFigure(new Point(0, 0), false, false);
                var r = new Random();
    
                for (int i = 0; i < 500; ++i)
                {
                    var p1 = new Point(r.Next(0, 1000), r.Next(0, 1000));
                    var p2 = new Point(r.Next(0, 1000), r.Next(0, 1000));
                    ctx.QuadraticBezierTo(p1, p2, true, false);
                }
            }
    
            geometry.Freeze();
    
            drawingContext.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry);
            drawingContext.Close();
    
            drawingVisual.CacheMode = new BitmapCache();
    
            return drawingVisual;
        }