简体   繁体   中英

WPF DrawingVisual on a background thread?

I need to not block the UI while I draw lots of simple shapes using WPF.

In WinForms, I would set up background buffer and draw to the buffer on a background thread, then draw the resulting buffer to the control. It works very well.

In WPF, I've experimented with using a DrawingVisual, but it seems to block the UI thread while it composes the drawing.

How would I move everything under DrawingVisual.RenderOpen() onto a background thread so that while it's working the UI thread is not blocked?

Take a look at library created by Dwayne Need. It will allow you render UI on multiple threads. Also see this blog post.

I want add a way to draw a VisualBrush to DrawingBrush in other thread.

As all we know ,the VisualBrush should use in UI thread and the VisualBrush cant Freeze that cant use in other thread.

If you want to use the VisualBrush in other thread and draw it to DrawingVisual ,you should change VisualBrush to Image.

To change VisualBrush to Image as the code :

  public static BitmapSource ToImageSource(this Brush brush, Size size, double dpiX, double dpiY)
    {
      DrawingVisual drawingVisual = new DrawingVisual();
      using (DrawingContext drawingContext = drawingVisual.RenderOpen())
        drawingContext.DrawRectangle(brush, (Pen) null, new Rect(size));
      BitmapImage bitmapImage = new BitmapImage();
      if (Math.Abs(size.Width) > 0.001 && Math.Abs(size.Height) > 0.001)
      {
        RenderTargetBitmap bitmap = new RenderTargetBitmap((int) (size.Width * dpiX / 96.0), (int) (size.Height * dpiY / 96.0), dpiX, dpiY, PixelFormats.Pbgra32);
        bitmap.Render((Visual) drawingVisual);
        bitmapImage.BeginInit();
        bitmapImage.DecodePixelWidth = (int) (size.Width * dpiX / 96.0);
        bitmapImage.DecodePixelHeight = (int) (size.Height * dpiY / 96.0);
        bitmapImage.StreamSource = (Stream) bitmap.ToMemoryStream(ImageFormat.Png);
        bitmapImage.EndInit();
        bitmapImage.Freeze();
      }
      return (BitmapSource) bitmapImage;
    }

And you can draw it in other thread.

     var drawVisual=VisualBrush.ToImageSource(drawBounds.Size the drawBounds is we give, Dpix you can write 96, Dpiy);
     Thread thread = new Thread(() =>
        {
            var target = new VisualTarget(hostVisual);
            s_event.Set();

            var dv = new DrawingVisual();

            using (var dc = dv.RenderOpen())
            {     
                 dc.DrawRectangle(new ImageBrush(drawVisual), new Pen(Brushes.Black, 0.0), drawBounds);

            }
            target.RootVisual = dv;
        }

But if you should draw some VisualBrush to DrawingVisual and change the DrawingVisual to bitmapImage and show the image.

You should wirte the VsisualBrush to Image in UIThread

        List<(ImageSource brush, Rect drawBounds)> drawVisual = new List<(ImageSource, Rect)>();
        foreach (var temp in Elements)
        {
            UIElement element = temp;
            Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(element);

            var drawBounds = descendantBounds;
            drawBounds.Offset(temp location - new Point());

            await Dispatcher.InvokeAsync(() =>
             {
                 var brush = new VisualBrush(element);

                 drawVisual.Add((brush.ToImageSource(drawBounds.Size, Dpix, Dpiy), drawBounds));
             }, DispatcherPriority.Input);
        }

For VisualTaget should use the Visual ,how to change the DrawingVisual to BitmapImage and show it?

       HostVisual hostVisual = new HostVisual();
        List<(ImageSource brush, Rect drawBounds)> drawVisual = new List<(ImageSource, Rect)>();
        foreach (var temp in Elements)
        {
            Element element = temp;
            Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(element);

            var drawBounds = descendantBounds;
            drawBounds.Offset(temp.Bounds.Location - new Point());

            await Dispatcher.InvokeAsync(() =>
             {
                 var brush = new VisualBrush(element);

                 drawVisual.Add((brush.ToImageSource(drawBounds.Size, Dpi.System.X, Dpi.System.Y), drawBounds));
             }, DispatcherPriority.Input);
        }

        Thread thread = new Thread(() =>
        {
            var target = new VisualTarget(hostVisual);
            s_event.Set();

            var dv = new DrawingVisual();

            using (var dc = dv.RenderOpen())
            {
                foreach (var temp in drawVisual)
                {
                    dc.DrawRectangle(new ImageBrush(temp.brush), new Pen(Brushes.Black, 0.0), temp.drawBounds);
                }
            }

            var bounds = VisualTreeHelper.GetDescendantBounds(dv);
            var width = (int) Math.Round(bounds.Width);
            var height = (int) Math.Round(bounds.Height);               

            var bitmap = new RenderTargetBitmap((int) Math.Round(width * Dpi.System.FactorX),
                (int) Math.Round(height * Dpi.System.FactorY), Dpi.System.X, Dpi.System.Y,
                PixelFormats.Pbgra32);

            bitmap.Render(dv);                

            dv = new DrawingVisual();
            using (var dc = dv.RenderOpen())
            {
                dc.DrawImage(bitmap, new Rect(size));
            }
            target.RootVisual = dv;

            System.Windows.Threading.Dispatcher.Run();
        });

        thread.TrySetApartmentState(ApartmentState.STA);

        thread.IsBackground = true;
        thread.Start();

        s_event.WaitOne();
        VisualHost.Child = hostVisual;

The Element is our CustomControl that have a property to get it's location.

But its not a good way for Dispatcher.InvokeAsync need too long.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM