Horrible user control work

I am doing simple graph control in wpf. And I can not explain or solve the performance problem: it is too slow compared to winforms. Maybe I'm doing something wrong.

I am preparing a demo to demonstrate the problem.

Here is the control control:

public class Graph : FrameworkElement
{
    private Point _mouse;
    private Point _offset = new Point(500, 500);

    public Graph()
    {
        Loaded += Graph_Loaded;
    }

    private void Graph_Loaded(object sender, RoutedEventArgs e)
    {
        // use parent container with background to receive mouse events too
        var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
        if (parent != null)
            parent.MouseMove += (s, a) => OnMouseMove(a);
    }

    protected override void OnRender(DrawingContext context)
    {
        // designer bugfix
        if (DesignerProperties.GetIsInDesignMode(this))
            return;

        Stopwatch watch = new Stopwatch();
        watch.Start();

        // generate some big figure (try to vary that 2000!)
        var radius = 1.0;
        var figures = new List<LineSegment>();
        for (int i = 0; i < 2000; i++, radius += 0.1)
        {
            var segment = new LineSegment(new Point(radius * Math.Sin(i) + _offset.X, radius * Math.Cos(i) + _offset.Y), true);
            segment.Freeze();
            figures.Add(segment);
        }
        var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
        geometry.Freeze();
        var pen = new Pen(Brushes.Black, 5);
        pen.Freeze();
        context.DrawGeometry(null, pen, geometry);

        // measure time
        var time = watch.ElapsedMilliseconds;
        Dispatcher.InvokeAsync(() =>
        {
            Window.GetWindow(this).Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
        }, DispatcherPriority.Loaded);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        var mouse = e.GetPosition(this);
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            // change graph location
            _offset.X += mouse.X - _mouse.X;
            _offset.Y += mouse.Y - _mouse.Y;
            InvalidateVisual();
        }
        // remember last mouse position
        _mouse = mouse;
    }
}

Here's how to use it in xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525" WindowState="Maximized">
    <Grid Background="White">
        <local:Graph/>
    </Grid>
</Window>

Some notes: the control will draw a shape that can be moved with the mouse:

enter image description here

The title will display 2 dimensions: first you need how long it took to complete OnRender(), and secondly, how long the actual rendering took (first call after rendering).

2000: 1000 , 3000 , ( ).

:

  • InvalidateVisual() MouseMove? , ?
  • , , - . ?
  • , 5ms, (200 +). ?

, , , ? winform , , wpf 1000... = (


. . , 300ms ( 2000). , ( ), .

+4
3

, , gdi draw wpf ( -).

, ( InvalidateVisuals() ).

protected override void OnRender(DrawingContext context)
{
    using (var bitmap = new GDI.Bitmap((int)RenderSize.Width, (int)RenderSize.Height))
    {
        using (var graphics = GDI.Graphics.FromImage(bitmap))
        {
            // use gdi functions here, to ex.: graphics.DrawLine(...)
        }
        var hbitmap = bitmap.GetHbitmap();
        var size = bitmap.Width * bitmap.Height * 4;
        GC.AddMemoryPressure(size);
        var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        image.Freeze();
        context.DrawImage(image, new Rect(RenderSize));
        DeleteObject(hbitmap);
        GC.RemoveMemoryPressure(size);
    }
}

. .

:

  • , gdi , DrawImage , .
  • wpf gdi ( ): , , , ..
  • , (, ), .
+1

WPF . . . , , . , GPS WPF.

direct2d WPF. - : http://www.codeproject.com/Articles/113991/Using-Direct-D-with-WPF

.

PS . WPF . . . . , Microsoft , , . . IPad , . WPF , .

+3

StreamGeometry, 5% -10% boost

    protected override void OnRender(DrawingContext context)
    {
        // designer bugfix
        if (DesignerProperties.GetIsInDesignMode(this))
            return;

        Stopwatch watch = new Stopwatch();
        watch.Start();

        // generate some big figure (try to vary that 2000!)
        var radius = 1.0;
        StreamGeometry geometry = new StreamGeometry();

        using (StreamGeometryContext ctx = geometry.Open())
        {
            Point start = new Point(radius * Math.Sin(0) + _offset.X, radius * Math.Cos(0) + _offset.Y);
            ctx.BeginFigure(start, false, false); 
            for (int i = 1; i < 2000; i++, radius += 0.1)
            {
                Point current = new Point(radius * Math.Sin(i) + _offset.X, radius * Math.Cos(i) + _offset.Y);
                ctx.LineTo(current, true, false);
            }
        }
        //var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
        geometry.Freeze();
        Pen pen = new Pen(Brushes.Black, 5);
        pen.Freeze();
        context.DrawGeometry(null, pen, geometry);

        // measure time
        var time = watch.ElapsedMilliseconds;
        Dispatcher.InvokeAsync(() =>
        {
            Window.GetWindow(this).Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
        }, DispatcherPriority.Loaded);
    }

2

, , . UIElement , , FrameworkElement

public class Graph : UIElement
{
    TranslateTransform _transform = new TranslateTransform() { X = 500, Y = 500 };
    public Graph()
    {
        CacheMode = new BitmapCache(1.4); //decrease this number to improve performance on the cost of quality, increasing improves quality 
        this.RenderTransform = _transform;
        IsHitTestVisible = false;
    }

    protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        base.OnVisualParentChanged(oldParent);

        if (VisualParent != null)
            (VisualParent as FrameworkElement).MouseMove += (s, a) => OnMouseMoveHandler(a);
    }

    protected override void OnRender(DrawingContext context)
    {
        // designer bugfix
        if (DesignerProperties.GetIsInDesignMode(this))
            return;

        Stopwatch watch = new Stopwatch();
        watch.Start();

        // generate some big figure (try to vary that 2000!)
        var radius = 1.0;
        StreamGeometry geometry = new StreamGeometry();

        using (StreamGeometryContext ctx = geometry.Open())
        {
            Point start = new Point(radius * Math.Sin(0), radius * Math.Cos(0));
            ctx.BeginFigure(start, false, false);
            for (int i = 1; i < 5000; i++, radius += 0.1)
            {
                Point current = new Point(radius * Math.Sin(i), radius * Math.Cos(i));
                ctx.LineTo(current, true, false);
            }
        }
        //var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
        geometry.Freeze();
        Pen pen = new Pen(Brushes.Black, 5);
        pen.Freeze();
        context.DrawGeometry(null, pen, geometry);

        // measure time
        var time = watch.ElapsedMilliseconds;
        Dispatcher.InvokeAsync(() =>
        {
            Application.Current.MainWindow.Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
        }, DispatcherPriority.Loaded);
    }

    protected void OnMouseMoveHandler(MouseEventArgs e)
    {
        var mouse = e.GetPosition(VisualParent as FrameworkElement);
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            _transform.X = mouse.X;
            _transform.Y = mouse.Y;
        }
    }
}

5000 , , .

, ( ). , 1000- , . , .


3

here is an example using DrawingVisualthe lightest approach available in WPF

public class Graph : UIElement
{
    DrawingVisual drawing;
    VisualCollection _visuals;
    TranslateTransform _transform = new TranslateTransform() { X = 200, Y = 200 };
    public Graph()
    {
        _visuals = new VisualCollection(this);

        drawing = new DrawingVisual();
        drawing.Transform = _transform;
        drawing.CacheMode = new BitmapCache(1);
        _visuals.Add(drawing);
        Render();
    }

    protected void Render()
    {

        // designer bugfix
        if (DesignerProperties.GetIsInDesignMode(this))
            return;
        Stopwatch watch = new Stopwatch();
        watch.Start();

        using (DrawingContext context = drawing.RenderOpen())
        {

            // generate some big figure (try to vary that 2000!)
            var radius = 1.0;
            StreamGeometry geometry = new StreamGeometry();

            using (StreamGeometryContext ctx = geometry.Open())
            {
                Point start = new Point(radius * Math.Sin(0), radius * Math.Cos(0));
                ctx.BeginFigure(start, false, false);
                for (int i = 1; i < 2000; i++, radius += 0.1)
                {
                    Point current = new Point(radius * Math.Sin(i), radius * Math.Cos(i));
                    ctx.LineTo(current, true, false);
                }
            }
            geometry.Freeze();
            Pen pen = new Pen(Brushes.Black, 1);
            pen.Freeze();
            // measure time
            var time = watch.ElapsedMilliseconds;
            context.DrawGeometry(null, pen, geometry);

            Dispatcher.InvokeAsync(() =>
            {
                Application.Current.MainWindow.Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
            }, DispatcherPriority.Normal);
        }

    }
    protected override Visual GetVisualChild(int index)
    {
        return drawing;
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            var mouse = e.GetPosition(VisualParent as FrameworkElement);

            _transform.X = mouse.X;
            _transform.Y = mouse.Y;
        }
        base.OnMouseMove(e);
    }
}
+2
source

All Articles