Baoma’s needs “Yizai Player” WPF open source project (three video lists, involving traversal, asynchronous Task, receiving RelayCommand, delegated Invoke, interactivity events)

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 VideoList to MainViewModel.cs to store the data. You may ask why it is not List VideoList. The reason is the array List. There is no way to dynamically respond to the interface

 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.