WPF+WriteableBitmap实现高性能曲线图的绘制

 

一、前言

之前分享过一期关于DrawingVisual来绘制高性能曲线的博客,今天再分享一篇通过另一种方式来绘制高性能曲线的方法,也就是通过WriteableBitmap的方式;具体的一些细节这里就不啰嗦了,同样是局部绘制的思想,滚动条拖动到哪里,就只绘制那一部分的曲线,直接贴代码;(该程序在英特尔11代CPU的电脑可能会遇到拖动滚动条曲线图卡住不动的情况,这个是显卡驱动的问题,官方已经修复了,遇到这问题的记得更新一下驱动)

 

二、正文

1、新建一个类,继承FrameworkElement,然后在里面实现一下绘图的逻辑;

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using _Font = System.Drawing.Font;
using GDI = System.Drawing;

namespace WriteableBitmapDemo.Controls
{
  public class CruveWriteableBitmap : FrameworkElement
  {
      private static PrivateFontCollection pfc = new PrivateFontCollection();
      private WriteableBitmap bitmap;

      private int bitmap_width = 0;
      private int bitmap_height = 0;

      private static _Font font = null;
      private static _Font time_font = null;

      private PointF[][] horizontals = null;
      private PointF[][] horizontals_thin = null;
      private PointF[][] verticals = null;
      private PointF[][] verticals_thin = null;

      private List<PointF> top_points1;
      private List<PointF> top_points2;
      private List<PointF> top_points3;
      private List<PointF> bottom_points;

      private List<PointF> labelPosition_up;
      private List<string> labelText_up;
      private List<PointF> labelPosition_down;
      private List<string> labelText_down;

      private List<PointF> timePosition;
      private List<string> timeText;

      private GDI.Pen blackPen = new GDI.Pen(GDI.Color.Black, 1.5f);
      private GDI.Pen grayPen = new GDI.Pen(GDI.Color.Gray, 1f);

      private GDI.Pen top_pen1 = new GDI.Pen(GDI.Color.Black, 2);
      private GDI.Pen top_pen2 = new GDI.Pen(GDI.Color.Orange, 2);
      private GDI.Pen top_pen3 = new GDI.Pen(GDI.Color.Purple, 2);public float scaleX { get; set; } = 1f;
      private float _ScaleY { get; set; } = 1f;
      public float ScaleY
      {
          get { return _ScaleY; }
          set
          {
              _ScaleY = value;
          }
      }

      static CruveWriteableBitmap()
      {
          var appRootDataDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "msyh.ttf");
          if (!File.Exists(appRootDataDir))
          {
              var key = $"/CurveChartDemo;component/Fonts/msyh.ttf";
              StreamResourceInfo info = Application.GetResourceStream(new Uri(key, UriKind.Relative));
              using (var stream = info.Stream)
              {
                  byte[] bytes = new byte[stream.Length];
                  int len = stream.Read(bytes, 0, bytes.Length);
                  File.WriteAllBytes(appRootDataDir, bytes);
              }
          }
          pfc.AddFontFile(appRootDataDir);
      }

      public CruveWriteableBitmap()
      {
          time_font = new _Font(pfc.Families[0], 10);
          font = new _Font(pfc.Families[0], 8);
      }

      public void DrawPoints()
      {
          //InitBitmap();
          if (this.bitmap == null)
          {
              return;
          }

          this.bitmap.Lock();
          using (Bitmap backBufferBitmap = new Bitmap(this.bitmap_width, this.bitmap_height,
              this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
              this.bitmap.BackBuffer))
          {
              using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
              {
                  backBufferGraphics.SmoothingMode = SmoothingMode.AntiAlias;
                  backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed;

                  backBufferGraphics.Clear(GDI.Color.White);

                  //粗横线
                  if (this.horizontals != null)
                  {
                      foreach (var horizontal in this.horizontals)
                      {
                          backBufferGraphics.DrawLine(blackPen, horizontal[0], horizontal[1]);
                      }
                  }
                  //细横线
                  if (this.horizontals_thin != null)
                  {
                      foreach (var horizontal in this.horizontals_thin)
                      {
                          backBufferGraphics.DrawLine(grayPen, horizontal[0], horizontal[1]);
                      }
                  }
                  //粗竖线
                  if (this.verticals != null)
                  {
                      foreach (var vertical in this.verticals)
                      {
                          backBufferGraphics.DrawLine(blackPen, vertical[0], vertical[1]);
                      }
                  }
                  //细竖线
                  if (this.verticals_thin != null)
                  {
                      foreach (var vertical in this.verticals_thin)
                      {
                          backBufferGraphics.DrawLine(grayPen, vertical[0], vertical[1]);
                      }
                  }
                  //上图曲线1
                  if (this.top_points1 != null && this.top_points1.Count > 0)
                  {
                      backBufferGraphics.DrawLines(top_pen1, top_points1.ToArray());
                  }
                  //上图曲线2
                  if (this.top_points2 != null && this.top_points2.Count > 0)
                  {
                      backBufferGraphics.DrawLines(top_pen2, this.top_points2.ToArray());
                  }
                  //上图曲线3
                  if (this.top_points3 != null && this.top_points3.Count > 0)
                  {
                      backBufferGraphics.DrawLines(top_pen3, this.top_points3.ToArray());
                  }
                  //下图曲线
                  if (this.bottom_points != null && this.bottom_points.Count > 0)
                  {
                      backBufferGraphics.DrawLines(top_pen1, this.bottom_points.ToArray());
                  }

                  //文本
                  if (labelPosition_up != null && labelPosition_up.Count > 0)
                  {
                      SizeF fontSize = backBufferGraphics.MeasureString(labelText_up[0], font);
                      for (int i = 0; i < labelPosition_up.Count; ++i)
                      {
                          backBufferGraphics.DrawString(labelText_up[i], font, GDI.Brushes.Black, labelPosition_up[i].X, labelPosition_up[i].Y - fontSize.Height);
                      }
                  }
                  if (labelPosition_down != null && labelPosition_down.Count > 0)
                  {
                      for (int i = 0; i < labelPosition_down.Count; ++i)
                      {
                          backBufferGraphics.DrawString(labelText_down[i], font, GDI.Brushes.Black, labelPosition_down[i].X, labelPosition_down[i].Y);
                      }
                  }
                  if (timePosition != null && timePosition.Count > 0)
                  {
                      for (int i = 0; i < timePosition.Count; ++i)
                      {
                          if (i == 0)
                              backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X, timePosition[i].Y);
                          else
                          {
                              SizeF fontSize = backBufferGraphics.MeasureString(timeText[i], time_font);
                              backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X - fontSize.Width / 2, timePosition[i].Y);
                          }

                      }
                  }

                  backBufferGraphics.Flush();
              }
          }
          this.bitmap.AddDirtyRect(new Int32Rect(0, 0, this.bitmap_width, this.bitmap_height));
          this.bitmap.Unlock();
      }public void UpdateTimeLabel(List<PointF> timePosition, List<string> timeText)
      {
          this.timePosition = timePosition;
          this.timeText = timeText;
      }
      public void UpdatePosition(List<PointF> fhr1_points, List<PointF> fhr2_points, List<PointF> fhr3_points, List<PointF> toco_points)
      {
          this.top_points1 = fhr1_points;
          this.top_points2 = fhr2_points;
          this.top_points3 = fhr3_points;
          this.bottom_points = toco_points;
      }

      public void UpdateLabelPosition(List<PointF> labelPosition_up, List<string> labelText_up, List<PointF> labelPosition_down, List<string> labelText_down)
      {
          this.labelPosition_up = labelPosition_up;
          this.labelText_up = labelText_up;
          this.labelPosition_down = labelPosition_down;
          this.labelText_down = labelText_down;
      }

      public void UpdateHorizontalLine(PointF[][] horizontals, PointF[][] horizontals_thin)
      {
          this.horizontals = horizontals;
          this.horizontals_thin = horizontals_thin;
      }

      public void UpdateVerticalLine(PointF[][] verticals, PointF[][] verticals_thin)
      {
          this.verticals = verticals;
          this.verticals_thin = verticals_thin;
      }

      protected override void OnRender(DrawingContext dc)
      {
          InitBitmap();
          if (this.bitmap != null)
          {
              dc.DrawImage(bitmap, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
          }
          base.OnRender(dc);
      }

      private void InitBitmap()
      {
          if (bitmap == null || this.bitmap.Width != (int)this.ActualWidth || this.bitmap.Height != (int)this.ActualHeight)
          {
              if ((int)this.ActualWidth > 0 && (int)this.ActualHeight > 0)
              {
                  this.bitmap_width = (int)this.ActualWidth;
                  this.bitmap_height = (int)this.ActualHeight;
                  this.bitmap = new WriteableBitmap(bitmap_width, bitmap_height, 96, 96, PixelFormats.Bgr24, null);
                  this.bitmap.Lock();
                  using (Bitmap backBufferBitmap = new Bitmap(bitmap_width, bitmap_height,
                      this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
                      this.bitmap.BackBuffer))
                  {
                      using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
                      {
                          backBufferGraphics.SmoothingMode = SmoothingMode.HighSpeed;
                          backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed;
                          backBufferGraphics.Clear(GDI.Color.White);
                          backBufferGraphics.Flush();
                      }
                  }
                  this.bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap_width, bitmap_height));
                  this.bitmap.Unlock();
              }
          }
      }
  }
}

2、主窗口添加该控件,并添加滚动条那些

<Window
  x:Class="WriteableBitmapDemo.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:ct="clr-namespace:WriteableBitmapDemo.Controls"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:local="clr-namespace:WriteableBitmapDemo"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  Title="MainWindow"
  Width="1500"
  Height="450"
  Loaded="Window_Loaded"
  mc:Ignorable="d">
  <Grid>
      <ct:CruveWriteableBitmap x:Name="curve" Margin="0,0,0,20" />
      <ScrollViewer
          Name="scroll"
          HorizontalScrollBarVisibility="Auto"
          ScrollChanged="ScrollViewer_ScrollChanged"
          VerticalScrollBarVisibility="Disabled">
          <Canvas x:Name="canvas" Height="1" />
      </ScrollViewer>
      <Canvas
          x:Name="CanvasPanel"
          Margin="0,0,0,20"
          Background="Transparent" />
  </Grid>
</Window>

3、主窗口后台添加曲线数值生成方法和更新视图数据方法

using System.Collections.Generic;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WriteableBitmapDemo
{
  /// <summary>
  /// MainWindow.xaml 的交互逻辑
  /// </summary>
  public partial class MainWindow : Window
  {
      private bool isAdd = true;

      private Dictionary<int, int> dicTopPoints = new Dictionary<int, int>();
      private Dictionary<int, int> dicBottomPoints = new Dictionary<int, int>();

      private float y_scale;

      private static int Top_Val_Max = 240;
      private static int Top_Val_Min = 30;
      private static int Top_X_Sex = 20;
      private static int Bottom = 100;
      private static int Center = 25;
      private static int BottomOffset = 0;

      private double offset = -1;

      public MainWindow()
      {
          InitializeComponent();

          CanvasPanel.MouseMove += delegate (object sender, MouseEventArgs e)
          {
              if (e.LeftButton == MouseButtonState.Pressed)
              {
                  if (Mouse.Captured == null) Mouse.Capture(CanvasPanel);

                  if (offset >= 0 && offset <= CanvasPanel.ActualWidth)
                  {
                      scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - (e.GetPosition(this).X - offset));
                  }
                  offset = e.GetPosition(this).X;
              }
              else
              {
                  offset = -1;
                  Mouse.Capture(null); // 释放鼠标捕获
              }
          };
      }

      private void Window_Loaded(object sender, RoutedEventArgs e)
      {
          //生成曲线数据
          int temp = 50;
          for (int i = 0; i < 24 * 60 * 60 * 4; i++)
          {
              if (isAdd)
              {
                  dicTopPoints.Add(i, temp);
                  temp += 2;
              }
              else
              {
                  dicTopPoints.Add(i, temp);
                  temp -= 2;
              }

              if (temp == 210) isAdd = false;
              if (temp == 50) isAdd = true;
          }
          temp = 0;
          for (int i = 0; i < 24 * 60 * 60 * 4; i++)
          {
              if (isAdd)
              {
                  dicBottomPoints.Add(i, temp);
                  temp += 2;
              }
              else
              {
                  dicBottomPoints.Add(i, temp);
                  temp -= 2;
              }

              if (temp == 100) isAdd = false;
              if (temp == 0) isAdd = true;
          }
          //初始化滚动条和触发曲线绘制
          canvas.Width = dicTopPoints.Count;
          scroll.ScrollToLeftEnd();
      }

      private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
      {
          InitChartData((float)scroll.HorizontalOffset);
      }

      /// <summary>
      /// 根据滚动条偏移量更新需要绘制的数据
      /// </summary>
      /// <param name="offset"></param>
      private void InitChartData(float offset)
      {
          y_scale = (float)((curve.ActualHeight - Center) / (Top_Val_Max - Top_Val_Min + Bottom));

          //上图横线
          List<PointF[]> horizontalList = new List<PointF[]>();
          List<PointF[]> horizontalList_thin = new List<PointF[]>();
          for (int y = 0; y <= Top_Val_Max - Top_Val_Min; y += 10)
          {
              float currentHeight = (float)(curve.ActualHeight - (y + Bottom) * y_scale - Center);
              PointF point1 = new PointF(0, currentHeight);
              PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
              if (y % 30 == 0)
                  horizontalList.Add(new PointF[] { point1, point2 });
              else
                  horizontalList_thin.Add(new PointF[] { point1, point2 });
          }
          for (int y = 0; y <= Bottom; y += 10)
          {
              float currentHeight = (float)(curve.ActualHeight - y * y_scale - BottomOffset);
              PointF point1 = new PointF(0, currentHeight);
              PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
              if (y % 20 == 0)
                  horizontalList.Add(new PointF[] { point1, point2 });
              else
                  horizontalList_thin.Add(new PointF[] { point1, point2 });
          }

          //竖线与文字
          List<PointF[]> verticals = new List<PointF[]>();
          List<PointF[]> verticals_thin = new List<PointF[]>();

          List<PointF> timePosition = new List<PointF>();
          List<string> timeText = new List<string>();

          List<PointF> labelPosition_up = new List<PointF>();
          List<string> labelText_up = new List<string>();

          List<PointF> labelPosition_down = new List<PointF>();
          List<string> labelText_down = new List<string>();

          for (int i = 0; i < offset + curve.ActualWidth; i += Top_X_Sex * 2)
          {
              if (i < offset) continue;
              //下竖线
              PointF point1 = new PointF(i - offset, (float)(curve.ActualHeight - BottomOffset));
              PointF point2 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - BottomOffset));
              //上竖线
              PointF point3 = new PointF(i - offset, 0);
              PointF point4 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center));

              if ((i + (60 * 2)) % (60 * 2) == 0)
              {
                  verticals.Add(new PointF[] { point1, point2 });
                  verticals.Add(new PointF[] { point3, point4 });
              }
              else
              {
                  verticals_thin.Add(new PointF[] { point1, point2 });
                  verticals_thin.Add(new PointF[] { point3, point4 });
              }

              if (i % 240 == 0)
              {
                  timeText.Add(i + "");
                  timePosition.Add(new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center)));
              }

              if ((i + (60 * 2)) % (120 * 2) == 0)
              {
                  for (int y = Top_Val_Min; y <= Top_Val_Max; y += 10)
                  {
                      if (y % 30 == 0)
                      {
                          labelText_up.Add(y + "");
                          labelPosition_up.Add(new PointF(i - offset, (float)(curve.ActualHeight - (Bottom + y - Top_Val_Min) * y_scale - Center)));
                      }
                  }
                  for (int y = 20; y <= 100; y += 10)
                  {
                      if (y % 20 == 0)
                      {
                          labelText_down.Add(y + "");
                          labelPosition_down.Add(new PointF(i - offset, (float)(curve.ActualHeight - y * y_scale)));
                      }
                  }
              }
          }

          List<PointF> top_points1 = new List<PointF>();
          for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
          {
              top_points1.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 100 - Top_Val_Min) * y_scale) - Center));
          }

          List<PointF> top_points2 = new List<PointF>();
          for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
          {
              top_points2.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 20 + 100 - Top_Val_Min) * y_scale) - Center));
          }

          List<PointF> top_points3 = new List<PointF>();
          for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
          {
              top_points3.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] - 20 + 100 - Top_Val_Min) * y_scale) - Center));
          }

          List<PointF> bottom_points = new List<PointF>();
          for (int i = (int)offset, j = 0; i < dicBottomPoints.Count && j < curve.ActualWidth; i++, j++)
          {
              bottom_points.Add(new PointF(j, (float)(curve.ActualHeight - dicBottomPoints[i] * y_scale - BottomOffset)));
          }

          curve.UpdateHorizontalLine(horizontalList.ToArray(), horizontalList_thin.ToArray());
          curve.UpdateVerticalLine(verticals.ToArray(), verticals_thin.ToArray());
          curve.UpdatePosition(top_points1, top_points2, top_points3, bottom_points);
          curve.UpdateTimeLabel(timePosition, timeText);
          curve.UpdateLabelPosition(labelPosition_up, labelText_up, labelPosition_down, labelText_down);
          curve.DrawPoints();
      }
  }
}

 

三、运行效果

运行效果如下,欢迎各位大佬指点

以上就是WPF+WriteableBitmap实现高性能曲线图的绘制的详细内容,更多关于WPF WriteableBitmap曲线图的资料请关注编程宝库其它相关文章!

 前言前面我们用了,类库对象进行数据绑定,不懂的童鞋可以去找博主上一篇文章,今天给大家演示使用数据库对datadridview进行数据绑定,其实也很简单啦,让我们卷起来。1.1让我们进入正 ...