WPF音频播放与波形显示(一)

实现目标:

  1. 自定义控件,可显示实时波形,响应鼠标事件;
  2. 用listBox的方式同时显示多个控件,分别显示不同的波形,有区别的响应鼠标事件。

关键词: Button; Label; Canvas; 绘图; 自定义控件

一. 控件外观设计

新建一个WPF User Control Library,构建如下外观:

从左至右依次为WPF自带的label、button、canvas控件,xaml代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<UserControl x:Class="libUserC.User1"
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"
mc:Ignorable="d" Height="80" d:Width="485">
<Grid Background="#FFECF3F5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="48"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btn" Margin="10,10,4,10" Grid.Column="1" Click="btn_Click"/>
<Label x:Name="label" Background="#FFEAC0C0" Margin="10" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="18"/>
<Canvas x:Name="canvas" Grid.Column="2" HorizontalAlignment="Stretch" Height="60" Margin="10" VerticalAlignment="Top" Background="#FFEAE4E4" MinWidth="300"/>
</Grid>
</UserControl>

二. 控件的后台代码

功能简介: 点击按钮能够在label上、button上显示内容(显示一个int数据,可通过控件的Num属性修改);控件能够定时刷新canvas,显示波形,控件初始创建时,波形数据data为零。调用控件的程序可以通过直接修改data,将想要显示的波形传递给该控件。

C#代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace libUserC
{
public partial class User1 : UserControl
{
public short[] data = new short[1000];//波形数据
private readonly DispatcherTimer PlotTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle);
public System.Windows.Shapes.Path WavePath = new System.Windows.Shapes.Path();
public User1()
{

InitializeComponent();
PlotTimer.IsEnabled = true;
PlotTimer.Interval = TimeSpan.FromMilliseconds(500);
PlotTimer.Tick += PlotTimer_Tick;
}
private void PlotTimer_Tick(object sender, EventArgs e)
{

double cvsHeightHalf=canvas.RenderSize.Height / 2;
int cvsWidth=(int)canvas.RenderSize.Width;
PathGeometry Geometry = new PathGeometry();
PathFigure PathFigure = new PathFigure();
PolyLineSegment PolyLine = new PolyLineSegment();
PathFigure.StartPoint = new Point(0, cvsHeightHalf);
int[] maxIndexPlot = new int[cvsWidth + 1];
int SampleCount = data.Length;
for (int i = 0; i <= cvsWidth; i++)
{//将像素点的位置与数据在数组中的索引位置相对应
maxIndexPlot[i] = (int)((double)i / cvsWidth * SampleCount);
}
float maxVaulePlot = float.MinValue;
float minVaulePlot = float.MaxValue;
for (int i = 0; i < cvsWidth; i++)
{
maxVaulePlot = float.MinValue;
minVaulePlot = float.MaxValue;
for (int j = maxIndexPlot[i]; j < maxIndexPlot[i + 1]; j++)
{
if (maxVaulePlot < data[j])
maxVaulePlot = (float)data[j];
if (minVaulePlot > data[j])
minVaulePlot = (float)data[j];
}
maxVaulePlot /= 32767f;
if (maxVaulePlot > 1) maxVaulePlot = 1;
minVaulePlot /= 32767f;
if (minVaulePlot < -1) minVaulePlot = -1;
PolyLine.Points.Add(new Point(i, -maxVaulePlot * cvsHeightHalf * 1 + cvsHeightHalf));
PolyLine.Points.Add(new Point(i, -minVaulePlot * cvsHeightHalf * 1 + cvsHeightHalf));
}
PathFigure.Segments.Add(PolyLine);
Geometry.Figures.Add(PathFigure);
WavePath.Data = Geometry;
WavePath.Stroke = new SolidColorBrush(Colors.OrangeRed);
WavePath.StrokeThickness = 1;
if (!canvas.Children.Contains(WavePath))
canvas.Children.Add(WavePath);
}

int _Num = 0;//显示在label上的数值
public int Num
{
set { if (_Num != value) _Num = value; }
get { return _Num; }
}
private void btn_Click(object sender, RoutedEventArgs e)
{

btn.Content = _Num;
label.Content = _Num;
}
}
}

三. 调用控件

功能描述: 在一个ListBox中放置多个上面设计的控件,通过修改控件的public属性,实现不同的波形显示(data),不同设备编号显示(Num)。

3.1 Xaml代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:libUserC="clr-namespace:libUserC;assembly=libUserC" x:Class="wpfTest.MainWindow"
Title="MainWindow" Height="546.522" Width="841.418" SizeChanged="Window_SizeChanged">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="227*"/>
</Grid.RowDefinitions>
<ListBox Name="listShower" Grid.Row="1">
<libUserC:User1 Name="U0" Num="0"/>
<libUserC:User1 Name="U1" Num="1"/>
<libUserC:User1 Name="U2" Num="2"/>
<libUserC:User1 Name="U3" Num="3"/>
<libUserC:User1 Name="U4" Num="4"/>
<libUserC:User1 Name="U5" Num="5" BorderBrush="#FF5F5A5A" BorderThickness="1"/>
</ListBox>
</Grid>
</Window>

3.2 后台代码

为简单起见,波形是采用Random随机数的方式生成的。
这里也使用了一个定时器,定时地产生波形数据,更新控件的data属性。控件会自动更新波形显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Threading;
namespace wpfTest
{
public partial class MainWindow : Window
{
public MainWindow()
{

InitializeComponent();
PlotTimer.IsEnabled = true;
PlotTimer.Interval = TimeSpan.FromMilliseconds(500);
PlotTimer.Tick += PlotTimer_Tick;
}
Random ra = new Random();
List<short[]> listData = new List<short[]>();
private void PlotTimer_Tick(object sender, EventArgs e)
{

for (int dataGroupIndex = 0; dataGroupIndex < 5; dataGroupIndex++)
{
short[] data = new short[2000];
for (int i = 0; i < 2000; i++)
{
data[i] = (short)ra.Next(short.MinValue, short.MaxValue);
}
listData.Add(data);
libUserC.User1 temp = listShower.Items[dataGroupIndex] as libUserC.User1;
temp.data = data;
}
}
private readonly DispatcherTimer PlotTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle);
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{

listShower.Width = this.RenderSize.Width;
foreach(libUserC.User1 item in listShower.Items)
{
item.Width = this.RenderSize.Width;
}
}
}
}

3.3 运行效果


点击按钮,会分别显示0-5六个不同的编号,波形也在不停变化。

四. 总结

基本的目标已实现,可以看到自定义控件极大地简化了最终的实现。
每个控件都使用了定时器,这样做占用了大量资源,而且即使是没有数据变化的时候,控件也在定时的刷新显示,例如上面的第六个控件。下一篇将进行优化,并加入声音播放功能。

热评文章