我们开发了一个简单的 WPF UserControl,它是一个 ChartLine,通常应该显示 -100 到 100 范围内的 512 个值。该图表有效,但是,该图表需要每 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 的高级 PolyLine,但性能没有改变!
注意 2:我正在使用 C#/WPF 和 .NET 4.0 (VS 2010)。
提前感谢,路易斯。