(混合智能客户端)RSS 媒体播放器
一个使用 MVVM 模式构建的 WPF 应用程序,用于查看 RSS 视频源。
引言
我长期以来一直在 CodeProject 上阅读文章,我想是时候尝试自己写一篇了。我写文章的能力几乎为零,所以希望第一篇至少还能勉强读得下去。
我想要一个替代 Internet Explorer 的工具来阅读/播放 RSS 视频源,因此我决定编写一个应用程序,让我可以轻松地浏览和观看我最喜欢的源中的视频。在编写完 RSS 部分后,我决定也添加支持从文件系统打开媒体。
Using the Code
要使用此应用程序,您需要安装 .NET 3.5。该应用程序使用 WPF 的 MediaElement
进行视频和音频播放。MediaElement
依赖于 Windows Media Player 10+ 才能运行。
当您启动应用程序时,媒体浏览器将展开,其中包含库视图和 RSS 视图。您可以关闭任一视图,并从菜单栏的“窗口”下重新打开它们。
要隐藏媒体浏览器,请点击屏幕最底部写着“Media”的地方。应用程序的其余部分相当直观;只需选择您想查看的媒体,然后您就可以像大多数其他媒体应用程序一样控制播放。
关注点
媒体元素
应用程序的整洁性
MediaElement
与 MVVM 模式并非完全兼容,老实说,这给编写此类应用程序带来了复杂性。为什么?因为它缺少依赖属性。我不知道原因,我只相信 WPF 神明自有安排。
不管原因是什么,我必须找到一种方法来解决这个问题。由于我无法在 XAML 中绑定它的属性,例如 Position
、Source
和 Volume
,所以我需要一个代码隐藏文件,但同时,我不想弄乱 UI 和主窗口的代码隐藏文件。对我来说,解决方案是使用一个包含媒体元素和所有操作它的控件的用户控件。
依赖的强大功能
这解决了保持整洁的问题。现在,我该如何更新要播放的媒体?
在 MVVM 应用程序中,ViewModel 根本不知道其关联视图中有哪些控件。我决定在用户控件上创建一个依赖属性,这样在主窗口中,我就可以在 XAML 中将控件绑定到 ViewModel 的一个属性。
这有点令人困惑,也许用代码解释会更好。
在 MyMediaControl
的代码隐藏中,我这样声明一个依赖属性。重要的是要注意,您应该**仅**在包装属性中包含 get
和 set
。像这里所示。这样做会导致属性无法正常工作。
public static readonly DependencyProperty MediaSourceProperty =
DependencyProperty.Register("MyMediaSource", typeof(string),
typeof(MyMediaControl), new PropertyMetadata(LastNameChangedCallback));
public string MyMediaSource
{
get
{
return (string)GetValue(MediaSourceProperty);
}
set
{
SetValue(MediaSourceProperty, value);
}
}
剩下要做的就是捕获属性更改事件。像这样
private static void MediaSourceChangedCallBack(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
if (e.NewValue.ToString().Length > 0)
_MediaURI = new Uri((string)e.NewValue);
}
现在有了功能齐全的依赖属性,剩下要做的就是在 XAML 中绑定该属性。
在 MainWindow.xaml 中
<UC:MyMediaControl MyMediaSource="{Binding ElementName=mainwnd,Path=DataContext.Source}"/>
该属性需要显式绑定到主窗口的数据上下文。出于某种原因,它默认情况下不会查看主窗口的数据上下文,而是会查看用户控件中的属性。
通过这种设置,我能够更改 MainWindowViewModel
上的 Source 属性,并同时更新用户控件,同时保持所有元素之间的松散耦合。
无外观控件
WPF 控件被设计成无外观的。这意味着您可以在不影响业务逻辑的情况下更改任何视觉效果。要使用现有控件实现此目的,您可以使用控件模板。
这是此应用程序中使用的滑块控件的模板
<ControlTemplate x:Key="HorizontalSlider" TargetType="{x:Type Slider}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"
MinHeight="{TemplateBinding Slider.MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TickBar
Name="TopTick"
SnapsToDevicePixels="True"
Placement="Top"
Fill="{StaticResource GlyphBrush}"
Height="15"
Visibility="Collapsed" />
<Border
Name="TrackBackground"
Margin="0"
CornerRadius="4"
Height="15"
Grid.Row="1"
Background="{StaticResource LightBrush}"
BorderBrush="{StaticResource NormalBorderBrush}"
BorderThickness="1" />
<Track Grid.Row="1" Name="PART_Track">
<Track.DecreaseRepeatButton>
<RepeatButton
Style="{StaticResource SliderButtonStyle}"
Command="Slider.DecreaseLarge" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource SliderThumbStyle}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton
Style="{StaticResource SliderButtonStyle}"
Command="Slider.IncreaseLarge" />
</Track.IncreaseRepeatButton>
</Track>
<TickBar
Name="BottomTick"
SnapsToDevicePixels="True"
Grid.Row="2"
Fill="{TemplateBinding Foreground}"
Placement="Bottom"
Height="4"
Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TickPlacement" Value="TopLeft">
<Setter TargetName="TopTick"
Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="TickPlacement" Value="BottomRight">
<Setter TargetName="BottomTick"
Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="TickPlacement" Value="Both">
<Setter TargetName="TopTick"
Property="Visibility" Value="Visible"/>
<Setter TargetName="BottomTick"
Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
无外观控件可以让您的应用程序拥有非常独特的外观,而无需从头开始创建自定义控件。
您会发现,从控件的默认模板开始是最容易的。有很多方法可以在代码中获取它,但如果您使用的是标准的 WPF 控件,MSDN 提供了所有 WPF 控件的默认模板:http://msdn.microsoft.com/en-us/library/aa970773.aspx。
数据转换器
我最后想介绍的一个快速主题是数据转换器。在使用 MVVM 时,您应该很少(如果需要的话)使用它们,但我认为它们是非常强大的工具,值得简要介绍。
数据转换器允许您在 XAML 中绑定各种数据,并使用一个可以操作代码中数据的自定义类来转换它。一个简单的 Converter 类看起来像这样
public class DoubleToPercent : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
// value is the data from the source object.
double thisdbl = (double)value;
int percent = (int)(thisdbl * 100);
// Return the value to pass to the target.
return percent;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
您需要实现 IvalueConverter
,它为您的类提供了两个转换数据的功能。
在 XAML 中,您的绑定将如下所示
<TextBlock Text="{Binding ElementName=mMediaElement, Path=Volume,
Converter={StaticResource DoubleToPercent}}"/>