后台线程上的 WPF 绘图视觉对象


当我

使用 WPF 绘制许多简单形状时,我不需要阻止 UI。

在 WinForms 中,我将设置后台缓冲区并绘制到后台线程上的缓冲区,然后将生成的缓冲区绘制到控件。效果很好。

在 WPF 中,我尝试过使用 DrawingVisual,但它似乎在编写绘图时阻止了 UI 线程。

如何将 DrawingVisual.RenderOpen() 下的所有内容移动到后台线程上,以便在它工作时 UI 线程不会被阻止?

我想添加一种在其他线程中将VisualBrush绘制到DrawingBrush的方法。

众所周知,VisualBrush应该在UI线程中使用,而VisualBrush不能冻结不能在其他线程中使用的。

如果要在其他线程中使用VisualBrush并将其绘制到DrawingVisual,则应将VisualBrush更改为Image。

要将 VisualBrush 更改为 Image 作为代码:

  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;
    }

你可以在其他线程中绘制它。

     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;
        }

但是,如果您应该将一些VisualBrush绘制到DrawingVisual并将DrawingVisual更改为bitmapImage并显示图像。

你应该在UIThread中将VsisualBrush旋转到图像

        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);
        }

对于VisualTaget应该使用视觉对象,如何将DrawingVisual更改为BitmapImage并显示它?

       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;

元素是我们的自定义控件,具有获取其位置的属性。

但对于 Dispatcher.InvokeAsync 来说,这不是一个好方法,需要太长时间。

最新更新