WPF在usercontrol中的高效绘图

时间:2015-04-12 16:17:35

标签: c# wpf user-controls naudio

我在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);

2 个答案:

答案 0 :(得分:2)

您应该使用数据绑定和INotifyPropertyChanged,让框架控制渲染。我有一个简单的例子来展示如何显示一个简单的图形。 enter image description here

<强> 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();            
}