gpt4 book ai didi

c# - WPF 自定义 ChartLine 性能问题

转载 作者:太空宇宙 更新时间:2023-11-03 11:00:12 25 4
gpt4 key购买 nike

我们开发了一个简单的 WPF UserControl,它是一个 ChartLine,通常应该显示 512 个值,范围在 -100 到 100 之间。该图表有效,但是,图表需要每 1 秒清除和更新一次其值,并且它需要一秒钟(1.4~~秒)来简单地呈现其所有值。在这次失败的尝试之后,我尝试使用 Microsoft 的旧 DynamicDataDisplay (D3),它应该更快,但对性能的影响是完全相同的,更新屏幕上的 512 值也需要一秒多的时间。

下面是我的代码,我相信可能有一些缓存技术、较低的位图分辨率或其他有助于实现我的目标的东西。

XAML:

<UserControl x:Class="IHM.OsciloscopeGraphic"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:IHM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="740" Loaded="UserControl_Loaded">
<Grid x:Name="gdMain">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OsciloscopeGraphic}}, Path=TitleGreen}" HorizontalAlignment="Right" HorizontalContentAlignment="Center" Margin="10, 0" FontSize="18" Width="115" Background="Green"/>
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OsciloscopeGraphic}}, Path=TitleLightBlue}" FontSize="18" Margin="10, 0" HorizontalAlignment="Left" HorizontalContentAlignment="Center" Background="LightBlue" Grid.Column="1" Width="115"/>

<Grid Name="gdChartArea" Grid.Row="1" Grid.ColumnSpan="2" >
<Border BorderBrush="Black" BorderThickness="1" Margin="30, 10, 10, 30"/>
<Canvas x:Name="cnvChart" Margin="30, 10, 10, 30">
<Canvas.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#4C000080" Offset="1"/>
<GradientStop Color="#4C7F7FFF"/>
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</Grid>
</Grid>

C#代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;

namespace IHM
{
public partial class OsciloscopeGraphic : UserControl
{
#region Properties
/// <summary>
/// If steps Lines are 0, will divide the grid equally by the number in lines grid
/// </summary>
public int LinesGrid { get; set; }
/// <summary>
/// If steps Columns are 0, will divide the grid equally by the number in lines grid
/// </summary>
public int ColumnsGrid { get; set; }

public int StepsLines { get; set; }
public int StepsColumns { get; set; }

public int MaxHorizontal { get; set; }

public int MaxVertical { get; set; }

public int MinHorizontal { get; set; }
public int MinVertical { get; set; }

public static readonly DependencyProperty TitleGreenProperty =
DependencyProperty.Register("TitleGreen", typeof(string), typeof(BarGraphicSplitted), new UIPropertyMetadata("TRS"));

[Bindable(true)]
public string TitleGreen
{
get { return (string)GetValue(TitleGreenProperty); }
set { SetValue(TitleGreenProperty, value); }
}
public static readonly DependencyProperty TitleLightBlueProperty =
DependencyProperty.Register("TitleLightBlue", typeof(string), typeof(BarGraphicSplitted), new UIPropertyMetadata("FRT"));

[Bindable(true)]
public string TitleLightBlue
{
get { return (string)GetValue(TitleLightBlueProperty); }
set { SetValue(TitleLightBlueProperty, value); }
}
#endregion Properties

#region Local Fields/Variables

private bool initialized = false;

private int Quantidade
{
get { return (Math.Abs(this.MaxHorizontal - this.MinHorizontal) + 1); }
}
#endregion Local Fields/Variables

public OsciloscopeGraphic()
{
InitializeComponent();

this.MaxHorizontal = 255;
this.MinHorizontal = 0;
this.MaxVertical = 100;
this.MinVertical = -100;
this.LinesGrid = 0;
this.ColumnsGrid = 0;

this.StepsColumns = 10;
this.StepsLines = 10;
}

#region Private Local/Methods

private Line CreateGridLine()
{
Line lm = new Line();
lm.Stroke = Brushes.Black;
lm.StrokeThickness = 1;
lm.StrokeDashArray = new DoubleCollection() { 1, 4 };
lm.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);

return lm;
}

private Line CreateHorizontalGridLine(Point start, double length)
{
Line ln = CreateGridLine();
//It has the same value because the line will be a vertical line
ln.X1 = start.X;
ln.X2 = start.X + length;
ln.Y1 = start.Y;
ln.Y2 = start.Y;

return ln;
}

private Line CreateHorizontalScaleLine(Point start)
{
Line l = CreateScaleLine();
l.X1 = start.X;
l.X2 = start.X - 5;
l.Y1 = start.Y;
l.Y2 = start.Y;

return l;
}

private Line CreateScaleLine()
{
Line l = new Line();
l.Stroke = Brushes.Black;
l.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);

return l;
}

private Line CreateVerticalGridLine(Point start, double length)
{
Line ln = CreateGridLine();
//It has the same value because the line will be a vertical line
ln.X1 = start.X;
ln.X2 = start.X;
ln.Y1 = start.Y;
ln.Y2 = start.Y + length;
return ln;
}
private Line CreateVerticalScaleLine(Point start)
{
Line l = CreateScaleLine();
l.X1 = start.X;
l.X2 = start.X;
l.Y1 = start.Y;
l.Y2 = start.Y + 5;
return l;
}

private void DrawGrid(Grid grid, Canvas chart)
{
bool makeBySteps = true;
if ((this.StepsColumns == 0) || (this.StepsLines == 0))
{
makeBySteps = false;
if ((this.LinesGrid == 0) || (this.ColumnsGrid == 0))
throw new DivideByZeroException();
}

//get canvas absolute position
var getPos = chart.TransformToVisual(grid);
Point XYpos = getPos.Transform(new Point(0, 0));

//draw the lines
double actualWidth = (chart.ActualWidth);
double initialPosition = (XYpos.X + 1);
double length = this.MaxHorizontal - this.MinHorizontal + 1;
double stepLegend = (makeBySteps) ? this.StepsColumns : length / Convert.ToDouble(this.ColumnsGrid);
int counter = (makeBySteps) ? ((int)length) / this.StepsColumns : this.ColumnsGrid;
double step = (makeBySteps) ? (actualWidth / length) * this.StepsColumns : (actualWidth / this.ColumnsGrid);
length = Math.Abs(length);
double remainder = 0d;


for (int i = 0; i <= counter; i++)
{
//vertical gridlines
double steps = i * step;
Point start = new Point(initialPosition + steps, XYpos.Y);
Line Lm = CreateVerticalGridLine(start, chart.ActualHeight);
grid.Children.Add(Lm);

//vertical scale lines
Point startScale = new Point(initialPosition + steps, XYpos.Y + chart.ActualHeight);
Line LineScale = CreateVerticalScaleLine(startScale);
grid.Children.Add(LineScale);


//bottom labels
Label lb = new Label();
lb.Width = 20;
lb.Height = 20;
lb.Padding = new Thickness(0);
lb.HorizontalContentAlignment = HorizontalAlignment.Center;
lb.ClipToBounds = false;
//this garantes that it will consider the reminder of divisions
double numero = this.MinHorizontal + (i * stepLegend);
remainder += numero - Math.Round(numero);
numero = Math.Round(numero);
if (remainder > 1)
{
remainder -= 1;
numero += 1;
}
else if (remainder < -1)
{
remainder += 1;
numero -= 1;
}
lb.Content = numero;
grid.Children.Add(lb);
lb.HorizontalAlignment = HorizontalAlignment.Left;
lb.VerticalAlignment = VerticalAlignment.Top;
//TODO: big coment explaining in details the line bellow
lb.Margin = new Thickness((XYpos.X - 10) + steps, XYpos.Y + chart.ActualHeight + 5, 0, 0);
}

initialPosition = XYpos.Y;
double actualHeight = (chart.ActualHeight);
length = this.MaxVertical - this.MinVertical + 1;
stepLegend = (makeBySteps) ? this.StepsLines : length / Convert.ToDouble(this.LinesGrid);
counter = (makeBySteps) ? ((int)length) / this.StepsLines : this.LinesGrid;
step = (makeBySteps) ? (actualHeight / length) * this.StepsLines : (actualHeight / this.LinesGrid);
//initialPosition = (makeBySteps) ? initialPosition + ((actualHeight / length) * (length % this.StepsLines)) : initialPosition;
length = Math.Abs(length);
remainder = 0d;

for (int i = 0; i <= counter; i++)
{
double steps = i * step;
Point start = new Point(XYpos.X, actualHeight + initialPosition - steps);
//horizontal gridlines
Line lm = CreateHorizontalGridLine(start, actualWidth);
grid.Children.Add(lm);
//horizontal scale lines
Line l = CreateHorizontalScaleLine(start);
grid.Children.Add(l);
//side labels
Label lb = new Label();
lb.Width = 30;
lb.Height = 20;
lb.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Right;
lb.Padding = new Thickness(0);
lb.VerticalContentAlignment = VerticalAlignment.Center;
lb.ClipToBounds = false;
//this garantes that it will consider the reminder of divisions
double numero = this.MinVertical + (i * stepLegend);
remainder += numero - Math.Round(numero);
numero = Math.Round(numero);
if (remainder > 1)
{
remainder -= 1;
numero += 1;
}
else if (remainder < -1)
{
remainder += 1;
numero -= 1;
}
lb.Content = numero;

grid.Children.Add(lb);
lb.HorizontalAlignment = HorizontalAlignment.Left;
lb.VerticalAlignment = VerticalAlignment.Top;
//TODO: big coment explaining in details the line bellow
lb.Margin = new Thickness(XYpos.X - 37, start.Y - 10, 0, 0);
}
}


private void DrawGrid()
{
this.DrawGrid(gdChartArea, cnvChart);
}

private void DrawLine(List<int> p_values, SolidColorBrush cor)
{
Polyline cl = new Polyline();
cl.Stroke = cor;
cl.StrokeThickness = 2;
cl.StrokeLineJoin = PenLineJoin.Round;
//cl.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
double stepHorizontal = cnvChart.ActualWidth / ((this.MaxHorizontal - this.MinHorizontal) + 1);
double stepVertical = cnvChart.ActualHeight / ((this.MaxVertical - this.MinVertical) + 1);

for (int i = 0; i < p_values.Count; i++)
{
int val = p_values[i];
double x = (stepHorizontal * i);
double y = cnvChart.ActualHeight - ((val - this.MinVertical) * stepVertical);

cl.Points.Add(new Point(x, y));
}

cnvChart.Children.Add(cl);
}

private void DrawLineGreen(List<int> p_values)
{
DrawLine(p_values, Brushes.Green);
}

private void DrawLineLightBlue(List<int> p_values)
{
DrawLine(p_values, Brushes.LightBlue);
}

private List<int> GetRandomValues()
{
int quantidade = this.Quantidade;
List<int> lsValues = new List<int>(quantidade);
int seed = 0;
long ticks = DateTime.Now.Ticks;
while (ticks > int.MaxValue)
{
ticks -= int.MaxValue;
}
seed = Convert.ToInt32(ticks);
Random ran = new Random(seed);

for (int i = 0; i < quantidade; i++)
{
int randomValue = ran.Next(this.MinVertical, this.MaxVertical);
lsValues.Add(randomValue);
}

return lsValues;
}

#endregion Private Local/Methods

#region Public Methods

public void Clear()
{
this.cnvChart.Children.Clear();
}

public void UpdateGraphValues()
{
UpdateGraphValues(GetRandomValues(), GetRandomValues());
}

public void UpdateGraphValues(List<int> p_frontValues, List<int> p_backValues)
{
//Clear current graphic values.
Clear();

DrawLineGreen(p_frontValues);
DrawLineLightBlue(p_backValues);
}

#endregion Public Methods

#region Window Events

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (!initialized)
{
DrawGrid();
UpdateGraphValues();
initialized = true;
}
}

#endregion Window Events
}
}

要在我希望的条件下测试图形,您可以简单地实例化`

    private OsciloscopeGraphic graphicOscNormal = new OsciloscopeGraphic() 
{
MinHorizontal = 0,
MaxHorizontal = 255,
MinVertical = -100,
MaxVertical = 100
};

在计时器内,您可以调用 `graphicOscNormal.UpdateGraphValues() ` 这将使用随机值填充图形以用于测试目的。稍后这些值将来自已经实现的串口。

注意:我也尝试用 DrawingVisual 和 DrawingContext.DrawLine 替换高级折线,但性能没有改变!

注意 2:我使用的是 C#/WPF 和 .NET 4.0 (VS 2010)。

提前致谢,Luís。

最佳答案

(最大的)问题是您的随机数生成器 - 它效率极低。尝试:

    private Random ran = new Random(0);
private List<int> GetRandomValues()
{
int quantidade = this.Quantidade;
List<int> lsValues = new List<int>(quantidade);

for (int i = 0; i < quantidade; i++)
{
int randomValue = ran.Next(this.MinVertical, this.MaxVertical);
lsValues.Add(randomValue);
}

return lsValues;
}

在优化时,对配置文件有好处。

如果您真的想要真的快速渲染,那么您几乎必须回到 GDI。例如 - 更新您的 Canvas (cnvChart) 以使用此 FastCanvas :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;

namespace WpfApplication1
{
class FastCanvas : Canvas
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
uint dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UnmapViewOfFile(IntPtr lbBaseAddress);


protected System.Drawing.Graphics GDIGraphics;
protected InteropBitmap interopBitmap = null;
protected InteropBitmap buffBitmap = null;

private const uint FILE_MAP_ALL_ACCESS = 0xF001F;
private const uint PAGE_READWRITE = 0x04;

private int bpp = PixelFormats.Bgra32.BitsPerPixel / 8;
protected IntPtr MapViewPointer;

public struct ScopeLine
{
public SolidColorBrush lineBrush;
public List<Point> linePoints;
}

public List<ScopeLine> Lines = new List<ScopeLine>();

protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
if (Lines.Count() > 0)
{
ImageSource drIs = null;

if (interopBitmap == null)
{
uint byteCount = (uint)((int)this.ActualWidth * (int)this.ActualHeight * bpp);

var fileMappingPointer = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, PAGE_READWRITE, 0, byteCount, null);
this.MapViewPointer = MapViewOfFile(fileMappingPointer, FILE_MAP_ALL_ACCESS, 0, 0, byteCount);
PixelFormat format = PixelFormats.Bgra32;
var stride = (int)((int)this.ActualWidth * format.BitsPerPixel / 8);
this.interopBitmap = Imaging.CreateBitmapSourceFromMemorySection(fileMappingPointer,
(int)this.ActualWidth,
(int)this.ActualHeight,
format,
stride,
0) as InteropBitmap;

this.GDIGraphics = GetGdiGraphics(MapViewPointer);
}

GDIGraphics.FillRectangle(System.Drawing.Brushes.Transparent,
new System.Drawing.Rectangle(0, 0,
(int)this.ActualWidth,
(int)this.ActualHeight));

foreach (ScopeLine dLine in Lines)
{
var pointCount = dLine.linePoints.Count();
Color lpColour;
lpColour = dLine.lineBrush.Color;
System.Drawing.Color lp2Colour;
lp2Colour = System.Drawing.Color.FromArgb(lpColour.A,
lpColour.R,
lpColour.G,
lpColour.B);


System.Drawing.Pen lpPen = new System.Drawing.Pen(lp2Colour, 1.5f);
System.Drawing.PointF newPoint = new System.Drawing.PointF((float)dLine.linePoints[0].X,
(float)dLine.linePoints[0].Y);


for (int i = 0; i < pointCount - 1; i++)
{
System.Drawing.PointF newPoint1 = new System.Drawing.PointF((float)dLine.linePoints[i + 1].X,
(float)dLine.linePoints[i + 1].Y);
GDIGraphics.DrawLine(lpPen, newPoint, newPoint1);
newPoint = newPoint1;
}

}

var bmpsrc = interopBitmap.GetAsFrozen();
if (bmpsrc == null || bmpsrc.CheckAccess())
{
drIs = (System.Windows.Media.Imaging.BitmapSource)bmpsrc;
}
else
{
//Debug.WriteLine("No access to TheImage");
}

dc.DrawImage(drIs, new Rect(this.RenderSize));
}
}

private System.Drawing.Graphics GetGdiGraphics(IntPtr mapViewPointer)
{
System.Drawing.Graphics gdiGraphics;
System.Drawing.Bitmap gdiBitmap;
gdiBitmap = new System.Drawing.Bitmap((int)this.ActualWidth,
(int)this.ActualHeight,
(int)this.ActualWidth * bpp,
System.Drawing.Imaging.PixelFormat.Format32bppArgb,
mapViewPointer);

gdiGraphics = System.Drawing.Graphics.FromImage(gdiBitmap);
gdiGraphics.CompositingMode = CompositingMode.SourceCopy;
gdiGraphics.CompositingQuality = CompositingQuality.HighSpeed;
gdiGraphics.SmoothingMode = SmoothingMode.HighSpeed;

return gdiGraphics;
}
}
}

并将您的 DrawLine 更改为:

    private void DrawLine(List<int> p_values, SolidColorBrush cor)
{
double stepHorizontal = cnvChart.ActualWidth / ((this.MaxHorizontal - this.MinHorizontal) + 1);
double stepVertical = cnvChart.ActualHeight / ((this.MaxVertical - this.MinVertical) + 1);

List<Point> pts = new List<Point>();

for (int i = 0; i < p_values.Count; i++)
{
int val = p_values[i];
double x = (stepHorizontal * i);
double y = cnvChart.ActualHeight - ((val - this.MinVertical) * stepVertical);
pts.Add(new Point(x,y));
}

FastCanvas.ScopeLine newLine;
newLine.lineBrush = cor;
newLine.linePoints = pts;

cnvChart.Lines.Add(newLine);
}

UpdateValues 到:

    public void UpdateGraphValues(List<int> p_frontValues, List<int> p_backValues)
{
cnvChart.Lines.Clear();
DrawLineGreen(p_frontValues);
DrawLineLightBlue(p_backValues);
cnvChart.InvalidateVisual();
}

与使用 WPF 渲染时大约 5-7fps 相比,像这样使用 GDI 相同的图形可以实时更新(对于 512 个点轻松 > 30fps)。

关于c# - WPF 自定义 ChartLine 性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17969146/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com