Table of Contents
File analysis
data entity
Traversal loading
Add data group object
Traverse folders to store objects
How to reference resources
design view
Interface UI data binding
Control event Command
RelayCommandCommand
Asynchronous Task
Delegate Dispatcher.Invoke
Interface loading event binding command
running result
File analysis
After building the project in the previous article, we need to display the video list. This is not difficult, but we first analyze Bao Ma’s video folder to understand how Bao Ma is stored.
It can be seen from the picture that Bao Ma’s video folder storage is still relatively complicated. There are single-layer folders and multi-layer folders, and there will be empty folders. In addition, the video files include mp4, mkv, and avi. Only there is no picture file. After all, Bao’s mother said that it is best to have pictures in the video list,…(project negotiation)…..Finally, I discussed with Bao’s mother to let her work harder, and take a picture and put it on it. It can be used together with the video file. The formats are jpg and png. The next step is to see how to read it.
data entity
Create data entity CardModel.cs
namespace YiZiPlayer.Model { /// <summary> /// Data model /// </summary> public class CardModel { // <summary> /// Title /// </summary> public string Title { get; set; } /// <summary> /// Folder path /// </summary> public string FolderPath { get; set; } /// <summary> /// Image path /// </summary> public string PicturePath { get; set; } } }
Traversal loading
Add data group object
The loaded data needs to be displayed on the interface. You need to add the array attribute ObservableCollection
ObservableCollection<CardModel> videoList; /// <summary> /// File List /// </summary> public ObservableCollection<CardModel> VideoList { get { return videoList; } set { videoList = value; RaisePropertyChanged(); } }
Traverse the folder and save the object
Through the analysis of the folder, I plan to use the name of the folder as the title, and skip the folder without video files. If there is a picture file, use the first one as the cover. If there is no picture file, the default picture will be displayed. If there are subfolders, continue recursion
/// <summary> /// Traverse the folder and load data /// </summary> void GetVideoList(string path) { if (Directory.Exists(path)) { string[] _folders = Directory.GetDirectories(path); //Query subdirectories string[] _fileVideos = Utils.GitFileVideos(path); //Query video files string[] _filePictures = Utils.GitFilePictures(path); //Query picture files //Whether the video file exists if (_fileVideos.Length != 0) { CardModel cardModel = new CardModel(); //Image file exists if (_filePictures.Length != 0) { cardModel.PicturePath = _filePictures[0]; //The first picture is the cover } else { cardModel.PicturePath = @"pack://application:,,,/YiZiPlayer;component/Resource/null.png"; //Apply the picture material in the project when there is no picture } cardModel.FolderPath = path; //Save path cardModel.Title = System.IO.Path.GetFileName(path); //Folder name as title VideoList.Add(cardModel); } else if (_folders.Length != 0) { //Subfolder recursion foreach (string str in _folders) { GetVideoList(str); } } } }
How to quote resources
There is a knowledge point here, how to reference the resource file pack://application:,,,/YiZiPlayer;… The above code snippet says that if there is no picture, the “default picture” will be displayed, < strong>pack://application:,,,/(project name);component/(path)
Design View
Since you use the HandyControl control library, you should first search in it. This can save a lot of trouble. After comparing it with Baoma’s block diagram, the card control is more consistent with the picture, but it needs to be improved. The small words below are changed to ours. Two buttons to play and open directories
Interface UI data binding
Add code to MainWindow.xaml
Specify the data source in the ListBox ItemsSource=”{Binding VideoList}
Each item is bound using Binding. See the code for details.
<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="Play" 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="Open Folder" 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>
Control event Command
A separate paragraph will explain what a control event is (after all, we have to take care of beginners). Simply put, the loading, clicking, mouse sliding, etc. of a control are all events. Events are usually used in conjunction with commands, such as executing a button after clicking it. What command, but not all controls provide events, so WPF provides the System.Windows.Interactivity.dll component, which allows us to add events to the control, but only if it is referenced first
xmlns:i=”http://schemas.microsoft.com/expression/2010/interactivity”
/*For example, the click event of an image uses components*/ <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> /*The button’s own click event*/ <Button Content="Open Folder" Margin="5" Style="{StaticResource ButtonDashed}" Command="{Binding Source={StaticResource Locator},Path=Main.OpenFolderCommand}" CommandParameter ="{Binding DataContext.FolderPath,RelativeSource={RelativeSource AncestorType=hc:Card}}" />
RelayCommand command
The background command triggered by the front-end control event must be defined by RelayCommand. The event can pass parameters or not.
For example, the command defined in MainViewModel.cs
/// <summary> /// Load data no parameters /// </summary> public RelayCommand LoadDataCommand { get { var command = new RelayCommand(() => { //Code snippet... }); return command; } } /// <summary> /// Click to open the folder, path parameters /// </summary> public RelayCommand<string> OpenFolderCommand { get { var command = new RelayCommand<string>((string path) => { System.Diagnostics.Process.Start(path); }); return command; } } /// <summary> /// Click the play button, path parameters /// </summary> public RelayCommand<string> PlayFileCommand { get { var command = new RelayCommand<string>((string path) => { //Code snippet... }); return command; } }
Asynchronous Task
Asynchronous is used because Bao Ma’s video folder has too much data. The initial plan was to traverse the folder and display the interface at the same time. Unexpectedly, the amount of data was so large that the interface got stuck directly, so the Task asynchronous thread was used.
Delegation Dispatcher.Invoke
Asynchronous Task solves the problem of stuck in the data reading interface, but the data changes in the asynchronous thread cannot be responded to the interface, and an exception error will be reported, so a delegate is needed.
specific code
/// <summary> /// Download Data /// </summary> public RelayCommand LoadDataCommand { get { var command = new RelayCommand(() => { //Asynchronous thread, loading data to prevent lagging 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; } }
Interface loading event binding command
The code related to the video list is almost finished here. This step refers to executing the command to load data in the background when the interface opens the event. 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="Yizai Player" Height="768" Width="1366"> <i:Interaction.Triggers> <i:EventTrigger EventName="Loaded"> <i:InvokeCommandAction Command="{Binding LoadDataCommand}" /> </i:EventTrigger> </i:Interaction.Triggers>
Running Effect
The effect is good! Looking at the block diagram, it is basically the same.