目录
文件分析
上篇构建好了项目,紧接着就要把视频列表展示出来,这不是什么难事,不过我们先对宝妈视频文件夹的进行分析,了解宝妈的存放方式。
从图中可以看出宝妈的视频文件夹存放还是比较复杂,有单层文件夹存放也有多层文件夹,而且会有空的文件夹,另外视频文件还不一样有 mp4 、mkv、avi ,唯独没有图片文件,毕竟宝妈说过视频列表最好有图片,......(项目协商).....最终和宝妈商量让她辛苦一点,自己截一张图放到和视频文件一起就可以了 格式 jpg、png 都行,接下来要看怎么读取了。
数据实体
创建数据实体 CardModel.cs
namespace YiZiPlayer.Model
{
/// <summary>
/// 数据模型
/// </summary>
public class CardModel
{
// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 文件夹路径
/// </summary>
public string FolderPath { get; set; }
/// <summary>
/// 图片路径
/// </summary>
public string PicturePath { get; set; }
}
}
遍历加载
添加数据组对象
加载到的数据需要展示在界面上的,需要到 MainViewModel.cs 加入数组属性 ObservableCollection<CardModel> VideoList ,存放数据,你会问为什么不是 List<CardModel> VideoList ,理由就是 数组 List 没办法动态响应到界面
ObservableCollection<CardModel> videoList;
/// <summary>
/// 文件列表
/// </summary>
public ObservableCollection<CardModel> VideoList
{
get { return videoList; }
set { videoList = value; RaisePropertyChanged(); }
}
遍历文件夹存入对象
通过对文件夹的分析,我打算使用文件夹的名称做为标题,而没有视频文件的文件夹跳过,如果有图片文件取第一张作为封面,没有则显示默认图片,如果有子文件夹继续递归
/// <summary>
/// 遍历文件夹,装载数据
/// </summary>
void GetVideoList(string path)
{
if (Directory.Exists(path))
{
string[] _folders = Directory.GetDirectories(path); //查询子目录
string[] _fileVideos = Utils.GitFileVideos(path); //查询视频文件
string[] _filePictures = Utils.GitFilePictures(path); //查询图片文件
//是否存在视频文件
if (_fileVideos.Length != 0)
{
CardModel cardModel = new CardModel();
//存在图片文件
if (_filePictures.Length != 0)
{
cardModel.PicturePath = _filePictures[0]; //第一张图片为封面
}
else
{
cardModel.PicturePath = @"pack://application:,,,/YiZiPlayer;component/Resource/null.png"; //无图片时应用项目里图片素材
}
cardModel.FolderPath = path; //保存路径
cardModel.Title = System.IO.Path.GetFileName(path); //文件夹名称作为标题
VideoList.Add(cardModel);
}
else if (_folders.Length != 0)
{
//子文件夹递归
foreach (string str in _folders)
{
GetVideoList(str);
}
}
}
}
引用资源的方式
这里边有个知识点,如何引用资源文件 pack://application:,,,/YiZiPlayer;........ 上面的代码段中有写到,没有图片就显示 “默认图片”, pack://application:,,,/(项目名);component/(路径)
设计视图
既然使用了 HandyControl 控件库,那就优先在里边找找,这能省不少事,经过和宝妈的框图比对卡片控件比较符合如图,不过还需要改进一下,下边的小字 改成我们的两个按钮 播放、打开目录
界面UI数据绑定
MainWindow.xaml 加入代码
ListBox 中指定数据源 ItemsSource="{Binding VideoList}
每一项是绑定 都使用 Binding 进行,具体看代码
<Grid >
<ListBox Margin="32" Padding="0,15" hc:ScrollViewer.IsInertiaEnabled="True" BorderThickness="0" Style="{StaticResource WrapPanelHorizontalListBox}" ItemsSource="{Binding VideoList}">
<ListBox.ItemTemplate>
<DataTemplate DataType="data:CardModel">
<hc:Card MaxWidth="240" BorderThickness="0" Effect="{StaticResource EffectShadow2}" Margin="8" Footer="{Binding Title}">
<Border CornerRadius="4,4,0,0" Style="{StaticResource BorderClip}">
<Image Width="240" Height="240" Source="{Binding PicturePath}" Stretch="Uniform" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown" >
<i:InvokeCommandAction Command="{Binding Source={StaticResource Locator},Path=Main.PlayFileCommand}" CommandParameter="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
</Border>
<hc:Card.FooterTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis" Height="60" Style="{StaticResource TextBlockLarge}" FontSize="20" Text="{Binding DataContext.Title,RelativeSource={RelativeSource AncestorType=hc:Card}}" HorizontalAlignment="Left"/>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="播放" Margin="5,5,60,5" Style="{StaticResource ButtonSuccess}" Command="{Binding Source={StaticResource Locator},Path=Main.PlayFileCommand}" CommandParameter="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}"/>
<Button Content="打开文件夹" Margin="5" Style="{StaticResource ButtonDashed}" Command="{Binding Source={StaticResource Locator},Path=Main.OpenFolderCommand}" CommandParameter="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</hc:Card.FooterTemplate>
</hc:Card>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
控件事件Command
单独整一小段来说明什么是控件事件(毕竟要照顾一下初学者),简单的说就是控件的加载、点击、鼠标滑过等等都是事件,事件通常结合命令使用,比如 按钮点击后 要执行什么命令,但不是所有的控件都提供事件,所以WPF提供了 System.Windows.Interactivity.dll 组件,让我们可以给控件添加事件,但前提是先要引用
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
/*例如图片的点击事件用到组件*/
<Image Width="240" Height="240" Source="{Binding PicturePath}" Stretch="Uniform" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown" >
<i:InvokeCommandAction Command="{Binding Source={StaticResource Locator},Path=Main.PlayFileCommand}" CommandParameter="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
/*按钮的自带点击事件*/
<Button Content="打开文件夹" Margin="5" Style="{StaticResource ButtonDashed}" Command="{Binding Source={StaticResource Locator},Path=Main.OpenFolderCommand}" CommandParameter="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}" />
RelayCommand命令
前端控件事件所触发的后台命令必须由 RelayCommand 去定义,事件可以传参数也可以不传
例如在 MainViewModel.cs 定义的命令
/// <summary>
/// 加载数据 无参数
/// </summary>
public RelayCommand LoadDataCommand
{
get
{
var command = new RelayCommand(() =>
{
//代码段......
});
return command;
}
}
/// <summary>
/// 点击打开文件夹,路径参数
/// </summary>
public RelayCommand<string> OpenFolderCommand
{
get
{
var command = new RelayCommand<string>((string path) =>
{
System.Diagnostics.Process.Start(path);
});
return command;
}
}
/// <summary>
/// 点击播放按钮,路径参数
/// </summary>
public RelayCommand<string> PlayFileCommand
{
get
{
var command = new RelayCommand<string>((string path) =>
{
//代码段......
});
return command;
}
}
异步Task
用到异步因为宝妈的视频文件夹数据太多了,一开始计划是遍历文件夹,同时显示界面,不成想数据量大,界面直接卡住了,故而用到 Task 异步线程
委托 Dispatcher.Invoke
异步Task 是解决了读数据界面卡顿的问题,但异步线程中的数据变更是没办法响应到界面,会报异常错误,所以需要用到了 委托
具体代码
/// <summary>
/// 加载数据
/// </summary>
public RelayCommand LoadDataCommand
{
get
{
var command = new RelayCommand(() =>
{
//异步线程,加载数据防止卡顿
Task task = new Task(() =>
{
try
{
Thread.Sleep(1500);
Application.Current.Dispatcher.Invoke(new Action(() => {
LoadData();
}));
}
catch (Exception ex)
{
Growl.Error(ex.Message);
}
});
task.Start();
});
return command;
}
}
界面加载事件绑定命令
视频列表相关代码写到这里也快收尾了,这一步是指界面打开事件时去执行后台加载数据的命令,Command="{Binding LoadDataCommand}"
<hc:Window x:Class="YiZiPlayer.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
DataContext="{Binding Source={StaticResource Locator},Path=Main}"
WindowState="Maximized"
WindowStartupLocation="CenterScreen"
Icon="/Resource/favicon32.ico"
mc:Ignorable="d"
Title="一仔播放器" Height="768" Width="1366">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadDataCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
运行效果
效果不错吧!在看看框图基本一致