WPF开发随笔收录-WriteableBitmap绘制高性能曲线图

  • WPF开发随笔收录-WriteableBitmap绘制高性能曲线图已关闭评论
  • 221 次浏览
  • A+
所属分类:.NET技术
摘要

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


一、前言

之前分享过一期关于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();         }     } }

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

WPF开发随笔收录-WriteableBitmap绘制高性能曲线图