WPF异步高效绘制过万级别的矩形图形矢量图,无限放大缩小,显示效果极佳

WPF异步高效绘制过万级别的矩形图形矢量图,无限放大缩小,显示效果极佳,先看效果如下:
在这里插入图片描述
在这里插入图片描述
想在WPF上绘制图形,有哪些方式?

1.通过Canvas静态绘图方法,例如:

<Canvas>
    <Ellipse Width="50" Height="50" Fill="Blue" Canvas.Left="10" Canvas.Top="10"/>
    <Rectangle Width="80" Height="40" Fill="Red" Canvas.Left="50" Canvas.Top="50"/>
</Canvas>

2.DrawingContext进行绘图,例如:

protected override void OnRender(DrawingContext drawingContext)
{
    base.OnRender(drawingContext);

    // 创建一个画刷和画笔
    SolidColorBrush brush = new SolidColorBrush(Colors.Green);
    Pen pen = new Pen(Brushes.Black, 2);

    // 绘制一个矩形
    drawingContext.DrawRectangle(brush, pen, new Rect(10, 10, 100, 50));
}

3.使用DrawingVisual进行低级别的绘图

DrawingVisual是一个轻量级的可视化对象,允许你在低级别上执行绘图。通过重写OnRender方法,你可以使用DrawingContext在DrawingVisual上进行绘图。

例如:

protected override void OnRender(DrawingContext drawingContext)
{
    base.OnRender(drawingContext);

    using (DrawingContext dc = drawingContext.RenderOpen())
    {
        // 在DrawingVisual上进行绘图
        dc.DrawEllipse(Brushes.Blue, new Pen(Brushes.Black, 2), new Point(50, 50), 30, 30);
    }
}

4.使用graphics绘制Image流可以直接绑定到界面

但是如果要考率绘制大批量图形,我们就要性能和内存等多个方面考虑了,否则绘制1w的矩形需要1分钟,哪个客户也接受不了啊。

我们考虑这些方面:

  • 1.使用UI 虚拟化控件: 如果你有过万级别的矩形,不要一次性全部绘制。考虑采用虚拟化的方式,只在当前视图区域内绘制可见的矩形。这可以通过一些控件如VirtualizingStackPanel或者自定义的虚拟化机制来实现。
  • 2.使用独立的绘制线程: 考虑在一个独立的绘制线程中进行绘制操作,以避免对UI主线程的影响。这样可以更好地控制绘制操作的调度和频率。
  • 3.多线程异步绘制 将绘制操作异步化,可以避免UI主线程阻塞。使用Task或async/await模式来在后台线程进行绘制操作,然后在UI线程更新视图。
  • 4.自定义UIElement: 如果使用WPF,可以考虑创建自定义的UIElement,通过重写OnRender方法实现高效的绘制。确保只在需要更新时进行绘制。
  • 5.可以使用Freezable类来冻结Pen对象,这个对提高性能有非常明显的对比,最后我在这里做个测评

实现过程:

XAML代码

<Window x:Class="WpfAppTest.shiliangTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTest" PreviewMouseWheel="Window_PreviewMouseWheel"
        mc:Ignorable="d"
        Title="shiliangTest" Height="800" Width="1000">
    <Grid>
        <Canvas x:Name="DrawingContainer" Width="800" Height="800" Panel.ZIndex="1" />

    </Grid>
</Window>

后台代码:

   public class VisualHost1 : FrameworkElement
   {
       private readonly DrawingVisual _drawingVisual;

       public VisualHost1()
       {
           _drawingVisual = new DrawingVisual();
       }

       public DrawingContext RenderOpen()
       {
           return _drawingVisual.RenderOpen();
       }

       protected override void OnRender(DrawingContext drawingContext)
       {
           base.OnRender(drawingContext);
           drawingContext.DrawDrawing(_drawingVisual.Drawing);
       }
   }
  public VisualHost1 _visualHost;
  double scale;
  public shiliangTest()
  {
      InitializeComponent();

      _visualHost = new VisualHost1();
      DrawingContainer.Children.Add(_visualHost);

      double canvasWidth = DrawingContainer.Width;
      double canvasHeight = DrawingContainer.Height;
      //double canvasWidth = 3000;
      //double canvasHeight = 3000;
      new SelectBoxRect(DrawingContainer);

      using (new PerformanceCounter("ghfgh"))
      {
          DrawRectangles1(canvasWidth, canvasHeight);
      }
      scale = 1;
  }

private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {

            if (Keyboard.IsKeyDown(Key.LeftCtrl))
            {
                if (e.Delta < 0)
                {
                    scale -= 0.01;
                }
                else
                {
                    scale += 0.01;
                }
                // scale += (double)e.Delta / 35000;
                ScaleTransform transfrom = new ScaleTransform();
                transfrom.ScaleX = transfrom.ScaleY = scale;
                this.DrawingContainer.RenderTransform = transfrom;
            }
}  

  private Pen CreateAndFreezePen()
  {
      // 创建Pen
      Pen pen = new Pen(Brushes.Black, 1);

      // 冻结Pen
      if (pen.CanFreeze)
      {
          pen.Freeze();
      }

      return pen;
  }

  private void DrawRectangles1(double canvasWidth, double canvasHeight)
  {
      int rows = 100; // 纵向矩形数量
      int columns = 100; // 横向矩形数量


      using (DrawingContext drawingContext = _visualHost.RenderOpen())
      {

          double rectangleWidth = canvasWidth / columns;
          double rectangleHeight = canvasHeight / rows;

          var pen = CreateAndFreezePen();
          for (int i = 0; i < columns; i++)
          {
              for (int j = 0; j < rows; j++)
              {
                  double x = i * rectangleWidth;
                  double y = j * rectangleHeight;

                  // 创建矩形几何图形
                  Rect rectangleRect = new Rect(new Point(x, y), new Size(rectangleWidth, rectangleHeight));
                  Geometry rectangleGeometry = new RectangleGeometry(rectangleRect);

                  // 绘制矩形
                  //drawingContext.DrawGeometry(Brushes.Blue, new Pen(Brushes.Black, 1), rectangleGeometry);
                  drawingContext.DrawGeometry(Brushes.Blue, pen, rectangleGeometry);

              }
          }
      }
  }
        

对比是否冻结Pen的结果

这是未冻结的耗时
在这里插入图片描述

这是冻结后的耗时
在这里插入图片描述
SelectBoxRect类

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfAppTest
{
    public class SelectBoxRect
    {
        private Canvas MyCanvas { get; set; }


        private Point _startPoint;
        private Rectangle _drawingRectangle;

        public SelectBoxRect(Canvas canvas)
        {
            MyCanvas = canvas;

            _drawingRectangle = new Rectangle
            {
                Stroke = Brushes.Red,
                StrokeThickness = 2,
                StrokeDashArray = new DoubleCollection(new List<double>() { 4, 2 }),
                Width = 0,
                Height = 0
            };
            MyCanvas.Children.Add(_drawingRectangle);

            canvas.MouseLeftButtonDown += MyCanvas_MouseLeftButtonDown;
            canvas.MouseMove += MyCanvas_MouseMove;
            canvas.MouseLeftButtonUp += MyCanvas_MouseLeftButtonUp;
        }

        private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _drawingRectangle.Visibility = Visibility.Visible;

            _startPoint = e.GetPosition(MyCanvas);

            Canvas.SetLeft(_drawingRectangle, _startPoint.X);
            Canvas.SetTop(_drawingRectangle, _startPoint.Y);
        }

        private async void MyCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                await UpdateRectangleAsync(e.GetPosition(MyCanvas));
            }
        }

        private void MyCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _drawingRectangle.Visibility = Visibility.Collapsed;
        }

        private async Task UpdateRectangleAsync(Point currentPoint)
        {
            try
            {
                await Task.Run(() =>
                {
                    Application.Current.Dispatcher.Invoke(() =>
                     {
                         double width = currentPoint.X - _startPoint.X;
                         double height = currentPoint.Y - _startPoint.Y;

                         _drawingRectangle.Width = Math.Abs(width);
                         _drawingRectangle.Height = Math.Abs(height);

                         if (width < 0)
                         {
                             Canvas.SetLeft(_drawingRectangle, currentPoint.X);
                         }

                         if (height < 0)
                         {
                             Canvas.SetTop(_drawingRectangle, currentPoint.Y);
                         }
                     });
                });
            }
            catch
            {
            }
        }

    }


}

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 在WPF中,可以通过使用鼠标滚轮事件来实现图片的放大缩小效果。具体步骤如下: 1. 首先,在XAML文件中添加一个Image控件,用于显示图片: ```xaml <Image x:Name="myImage" Source="path_to_image.jpg" Stretch="Uniform"/> ``` 2. 接下来,为Image控件添加鼠标滚轮事件处理程序。可以在Window的Loaded事件中进行绑定: ```csharp private void Window_Loaded(object sender, RoutedEventArgs e) { myImage.MouseWheel += MyImage_MouseWheel; } ``` 3. 在鼠标滚轮事件处理程序中,获取鼠标滚动的delta值,根据delta值来实现图片的放大缩小效果。可以使用ScaleTransform对Image进行缩放: ```csharp private void MyImage_MouseWheel(object sender, MouseWheelEventArgs e) { // 获取鼠标滚动的delta值 int delta = e.Delta; // 计算缩放比例变化值 double scaleChange = delta > 0 ? 0.2 : -0.2; // 创建缩放变换对象 ScaleTransform scaleTransform = new ScaleTransform(); // 获取当前缩放比例 double currentScale = myImage.LayoutTransform == null ? 1 : ((ScaleTransform)myImage.LayoutTransform).ScaleX; // 计算新的缩放比例 double newScale = currentScale + scaleChange; // 设置缩放变换的缩放比例 scaleTransform.ScaleX = newScale; scaleTransform.ScaleY = newScale; // 应用缩放变换到Image控件 myImage.LayoutTransform = scaleTransform; } ``` 通过以上步骤,当鼠标滚动时,图片将按照滚动方向进行放大缩小。可以通过调整scaleChange的值来控制缩放的速度和幅度。 ### 回答2: 在WPF中,可以通过鼠标滚轮事件来实现图片的放大缩小效果。具体步骤如下: 1. 首先,在XAML文件中添加一个Image控件,用于显示图片。 ```xaml <Image Name="myImage" Source="your_image_source" /> ``` 2. 在代码-behind文件中,对Image控件注册鼠标滚轮事件。 ```csharp myImage.MouseWheel += MyImage_MouseWheel; ``` 3. 在鼠标滚轮事件的处理函数中,根据滚轮的滚动方向,调整Image的放大缩小倍数。 ```csharp private void MyImage_MouseWheel(object sender, MouseWheelEventArgs e) { if (e.Delta > 0) { myImage.Width *= 1.1; // 放大图片 myImage.Height *= 1.1; } else if (e.Delta < 0) { myImage.Width *= 0.9; // 缩小图片 myImage.Height *= 0.9; } } ``` 4. 最后,为了保持图片的居中显示效果,可以将Image控件放置在一个ScrollView控件中。 ```xaml <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <Image Name="myImage" Source="your_image_source" /> </ScrollViewer> ``` 通过以上步骤,当鼠标滚动时,图片将实现放大缩小效果,并通过ScrollView来提供滚动条,以便查看放大后的图片内容。 ### 回答3: 在WPF中,可以通过一些简单的步骤实现图片通过鼠标滚动实现放大缩小效果。 首先,在XAML中创建一个Image控件,并设置其Stretch属性为Uniform,该属性可以确保图片在控件中等比例显示,不会发生变形。 接下来,通过绑定Image控件的Width和Height属性来实现缩放效果。可以在Image的父容器(比如Grid或者Canvas)中添加一个LayoutTransform属性,并绑定一个ScaleTransform转换器。 在Code-behind中,创建一个ScaleTransform对象,并将其绑定到Image控件的LayoutTransform属性。然后,通过监测鼠标滚动事件来改变ScaleTransform的ScaleX和ScaleY属性。 具体的代码如下所示: 在XAML中: ```xaml <Grid> <Image x:Name="MyImage" Source="path/to/your/image" Stretch="Uniform" /> </Grid> ``` 在Code-behind中: ```csharp public partial class MainWindow : Window { private ScaleTransform _scaleTransform; private const double ZoomFactor = 0.2; // 缩放因子,可根据需要调整 public MainWindow() { InitializeComponent(); _scaleTransform = new ScaleTransform(); MyImage.LayoutTransform = _scaleTransform; MouseWheel += MainWindow_MouseWheel; } private void MainWindow_MouseWheel(object sender, MouseWheelEventArgs e) { if (e.Delta > 0) { _scaleTransform.ScaleX += ZoomFactor; _scaleTransform.ScaleY += ZoomFactor; } else { if (_scaleTransform.ScaleX - ZoomFactor > 0 && _scaleTransform.ScaleY - ZoomFactor > 0) { _scaleTransform.ScaleX -= ZoomFactor; _scaleTransform.ScaleY -= ZoomFactor; } } } } ``` 以上代码通过监测鼠标滚轮事件,根据滚轮滚动的方向改变ScaleTransform的ScaleX和ScaleY属性,从而实现图片的放大缩小效果。缩放因子ZoomFactor可以根据需求进行调整。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的诗人Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值