我在WPF中绘制用户控件时出现性能问题。这就是我的工作。 基本上应用程序播放音频文件。播放音频文件的类每隔几次发送一次有关其位置的信息。
我有一个Usercontrol来呈现文件的位置。我是这样做的:
protected override void OnRender(DrawingContext drawingContext)
{
// lots of drawing happens here like drawingContext.DrawRectangle,
// drawingContext.DrawLine ....
base.OnRender(drawingContext);
}
现在我在应用程序中有另一个线程进行一些处理(基本上读取音频数据)并通过委托每秒向用户控制发送一次信息。第二个进程声明一个这样的委托:
protected virtual void OnBytesPlayed(BytesPlayedEventArgs e)
{
if (playheadCallback != null)
playheadCallback(fileStream.CurrentTime.TotalSeconds, fileStream.TotalTime.TotalSeconds);
}
用户控件注册到该事件并重绘自身:
public void PlayheadPositionUpdate(double currentFrame, double allFrames)
{
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new System.Action(() =>
{
this.InvalidateVisual();
}));
}
这一切都有效,但是一旦我启用了回调,它每秒都会抽取几次用户控件,一切都变得迟钝,性能下降。所以我猜我处理绘图或重绘的方式效率不高。有人能指出我正确的方向如何重绘用户控件,这样它不会影响播放性能或应用程序的整体性能?
如果我需要解释更多或提供更多代码,请告诉我。我希望尽可能简单地概述问题。
谢谢。编辑:
调用NAudio的代码是
var inputStream = new AudioFileReader(filename);
fileStream = inputStream;
var aggregator = new SampleAggregator(inputStream);
aggregator.NotificationCount = inputStream.WaveFormat.SampleRate;
aggregator.BytesPLayed += (s, a) => OnBytesPlayed(a);
playbackDevice.Init(aggregator);
答案 0 :(得分:2)
您应该使用数据绑定和INotifyPropertyChanged,让框架控制渲染。我有一个简单的例子来展示如何显示一个简单的图形。
<强> GraphControl.xaml 强>
<Border Height="50" Width="200" BorderBrush="Black" CornerRadius="1" BorderThickness="0.1">
<Canvas x:Name="canvas" Background="White" Margin="0" ClipToBounds="True" />
</Border>
<强> GraphControl.xaml.cs 强>
public partial class GraphControl : UserControl
{
public IEnumerable<double> Data
{
get { return (IEnumerable<double>)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(IEnumerable<double>), typeof(GraphControl), new PropertyMetadata(null, OnDataChanged));
private static void OnDataChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var control = (GraphControl)dependencyObject;
control.HandleDataChanged();
}
public Brush Color
{
get { return (Brush)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("Color", typeof(Brush), typeof(GraphControl), new PropertyMetadata(Brushes.Green));
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(GraphControl), new PropertyMetadata(100.0));
private void HandleDataChanged()
{
var points = new PointCollection();
double x = 0;
points.Add(new Point(0, canvas.ActualHeight));
var xScale = canvas.ActualWidth / (Data.Count() -1);
var yScale = canvas.ActualHeight / Maximum;
foreach (var dataPoint in Data)
{
var y = canvas.ActualHeight - dataPoint * yScale;
points.Add(new Point(x,y));
x += xScale;
}
points.Add(new Point(canvas.ActualWidth, canvas.ActualHeight));
var fill = Color.Clone();
fill.Opacity = 0.2;
polygon.Stroke = Color;
polygon.Fill = fill;
polygon.Points = points;
}
Polygon polygon = new Polygon() ;
public GraphControl()
{
InitializeComponent();
var fill = Color.Clone();
fill.Opacity = 0.2;
polygon.Stroke = Color;
polygon.Fill = fill;
polygon.StrokeThickness = 1;
canvas.Children.Add(polygon);
}
}
进行测试...... 的 MainWindows.xaml 强>
DataContext="{Binding RelativeSource={RelativeSource Self}}"
<StackPanel >
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
<simpleGraph:GraphControl Data="{Binding Data}" Margin="4" />
</StackPanel>
<强> MainWindows.xaml.cs 强>
private Random random = new Random();
public List<double> Data { get; set; }
public MainWindow()
{
InitializeComponent();
var temp = new List<double>();
for (int i = 0; i < 100; i++)
{
temp.Add(random.NextDouble() * 100);
}
Data = new List<double>(temp);
RaisePropertyChanged("Data");
Timer timer = new Timer(1);
timer.Elapsed += (sender, args) =>
{
UpdateData();
};
timer.Start();
}
private void UpdateData()
{
var data = Data.ToArray();
ShiftLeft(data, 1);
var value = random.NextDouble() * 100;
data[99] = value;
Data = new List<double>(data);
RaisePropertyChanged("Data");
}
public void ShiftLeft<T>(T[] array, int shifts)
{
Array.Copy(array, shifts, array, 0, array.Length - shifts);
Array.Clear(array, array.Length - shifts, shifts);
}
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
非常粗糙,但希望有所帮助
答案 1 :(得分:0)
您不想调用InvalidateVisual()
,因为这会导致UI的重新布局非常缓慢。但是,您也不需要使用DataBinding(虽然这是一个很好的解决方案)。
要制作您尝试快速使用的方法,请创建一个DrawingGroup&#34; backingStore&#34;保存绘图命令。在OnRender()方法中,将DrawingGroup输出到DrawingContext。然后,您可以随时更新backingStore,并更新您的视觉效果。将现有的渲染代码移动到另一个&#34; Render()&#34;刚进入backingStore的方法。
代码如下所示:
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}