WPFとNAudioで音楽プレイヤーを作る~第5回:フォルダ内の楽曲を読み込んでListViewに表示させる~

第5回:フォルダ内の楽曲を読み込んでListViewに表示させる


今回のポイント

  • TagLibで音楽の情報を読み取る

  • NotifyPropertyChangedメソッドでViewに変更を通知する

TagLibを導入する

mp3ファイルに書き込まれた情報を読み取るためにTagLibを使用します。
プロジェクト > NuGetパッケージの管理 から TagLibSharp と検索してインストールするだけです。
下側のものをインストールしました。なんか他にもいろいろ種類があったんですがまあいいでしょう。

フォルダ内の楽曲を読み込む

今回は起動時に特定のフォルダのmp3ファイルを探索してListViewに表示するようにしたいと思います。
まずはCommonフォルダ配下にMusic.csファイルを作成します。

Musicクラスには6つのプロパティがあります。

Music.cs

using System;

namespace MusicPlayer.MVVM.Common
{
    public class Music
    {
        /// <summary>
        /// 音楽ファイルの絶対パス
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// タイトル
        /// </summary>
        public string? Title { get; set; }

        /// <summary>
        /// アーティスト名
        /// </summary>
        public string? Artist { get; set; }

        /// <summary>
        /// アルバム名
        /// </summary>
        public string? Album { get; set; }

        /// <summary>
        /// 曲の長さ
        /// </summary>
        public TimeSpan Time { get; set; }

        /// <summary>
        /// トラック番号
        /// </summary>
        public uint Track { get; set; }

    }
}

次にModelフォルダの配下にMainWindowModel.csファイルを作成します。

MainWindowModel.cs

using MusicPlayer.MVVM.Common;
using System;
using System.Collections.Generic;

namespace MusicPlayer.MVVM.Model
{
    public class MainWindowModel
    {
        /// <summary>
        /// 音楽ファイルの読込処理
        /// </summary>
        public List<Music> LoadMusicFiles()
        {
            // MUSIC配下のmp3ファイルのパスを全取得
            string[] filePaths = System.IO.Directory.GetFiles(@"C:\ユーザー名\Music\MUSIC", "*.mp3", System.IO.SearchOption.AllDirectories);

            // 音楽の情報を読み込んでリストに入れる
            List<Music> musicList = new List<Music>();
            foreach (string filePath in filePaths)
            {
                TagLib.File file = TagLib.File.Create(filePath);
                Music music = new Music()
                {
                    Path = filePath,
                    Title = file.Tag.Title,
                    Album = file.Tag.Album,
                    Artist = file.Tag.Performers[0],
                    Track = file.Tag.Track,
                    Time = new TimeSpan(0, 0, file.Properties.Duration.Minutes, file.Properties.Duration.Seconds),
                };
                musicList.Add(music);
            }

            return musicList.OrderBy(m => m.Album)
                           .ThenBy(m => m.Track)
                           .ToList();

        }
    }
}

LoadMusicFilesメソッドではまず指定したフォルダ配下のmp3ファイルを探索します。
System.IO.Directory.GetFilesメソッドの第一引数に探索したいディレクトリ、第二引数には探索するファイルの拡張子を指定します。
今回はマイミュージックフォルダ配下にMUSICフォルダを作成してその中にmp3ファイルを配置しています。
第三引数にSystem.IO.SearchOption.AllDirectoriesを指定するとサブフォルダ内も探索してくれます。
探索が完了するとfilePaths変数にファイルの完全パスが格納されます。

// MUSIC配下のmp3ファイルのパスを全取得
string[] filePaths = System.IO.Directory.GetFiles(@"C:\ユーザー名\Music\MUSIC", "*.mp3", System.IO.SearchOption.AllDirectories);

完全パスが取得できたら先程作成した`Music`クラスのリストを作成します。
TagLib.File file = TagLib.File.Create(filePath);で一曲分の情報を読み込みます。
読み込んだ情報から`Music`クラスの各プロパティを設定しつつオブジェクトを作成します。

// 音楽の情報を読み込んでリストに入れる
List<Music> musicList = new List<Music>();
foreach (string filePath in filePaths)
{
    TagLib.File file = TagLib.File.Create(filePath);
    Music music = new Music()
    {
        Path = filePath,
        Title = file.Tag.Title,
        Album = file.Tag.Album,
        Artist = file.Tag.Performers[0],
        Track = file.Tag.Track,
        Time = new TimeSpan(0, 0, file.Properties.Duration.Minutes, file.Properties.Duration.Seconds),
    };
    musicList.Add(music);
}

最後にmusicListをLINQで並び替えてから返却しています。

return musicList.OrderBy(m => m.Album)
                .ThenBy(m => m.Track)
                .ToList();

起動時に音楽ファイルを読み込む

MainWindowModelに作成したLoadMusicFilesメソッドを起動時に呼ぶためにMainWindowViewModelのコンストラクタを修正します。

MainWindowViewModel.cs

using MusicPlayer.MVVM.Common;
using MusicPlayer.MVVM.Model;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Input;

namespace MusicPlayer.MVVM.ViewModel
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        // 変数の更新通知用
        public event PropertyChangedEventHandler PropertyChanged;

        private MainWindowModel _model = new MainWindowModel();

        // 音楽の一覧
        private List<Music> _musicList;
        public List<Music> MusicList
        {
            get { return this._musicList; }
            set 
            { 
                this._musicList = value;
                NotifyPropertyChanged("MusicList");
            }
        }

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        /// <summary>再生ボタンが押されたときのコマンド</summary>
        public ICommand OnPlayButtonClickCommand { get; set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel() 
        {
            // フォルダ内の音楽の読込
            MusicList = this._model.LoadMusicFiles();
            // 再生ボタン用のコマンド
            OnPlayButtonClickCommand = new MyRelayCommand(OnPlayButtonClick);
        }

        private void OnPlayButtonClick()
        {
            var dialog = new OpenFileDialog();
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                WaveOutEvent outputDevice = new WaveOutEvent();
                AudioFileReader afr = new AudioFileReader(dialog.FileName);
                outputDevice.Init(afr);
                outputDevice.Play();
            }
        }
    }
}

MainWindowModelのインスタンスを用意します。

private MainWindowModel _model = new MainWindowModel();

Musicのリストを扱うプロパティを用意します。
NotifyPropertyChanged("MusicList");を記述することで、MusicListプロパティに値の変化があったときにViewに通知されます。
めちゃくちゃ重要ポイントです。

// 音楽の一覧
private List<Music> _musicList;
public List<Music> MusicList
{
    get { return this._musicList; }
    set 
    { 
        this._musicList = value;
        NotifyPropertyChanged("MusicList");
    }
}

最後にコンストラクタでMusicListプロパティを初期化します。

/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel() 
{
    // フォルダ内の音楽の読込
    MusicList = this._model.LoadMusicFiles();
    // 再生ボタン用のコマンド
    OnPlayButtonClickCommand = new MyRelayCommand(OnPlayButtonClick);
}

音楽を一覧表示する

MusicListの中身をListViewで一覧表示します。
まずは`ListView`の見た目をカスタムしやすくするためにListViewLayoutManagerというプラグインを導入します。
例によってプロジェクト > NuGetパッケージの管理からListViewLayoutManagerと検索してインストールします。

それではListViewを修正します。

MainWindow.xaml

<Window x:Class="MusicPlayer.MVVM.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:local="clr-namespace:MusicPlayer"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        xmlns:viewmodels="clr-namespace:MusicPlayer.MVVM.ViewModel"
        xmlns:ui="http://schemas.modernwpf.com/2019" 
        ui:WindowHelper.UseModernWindowStyle="True"
        d:DataContext="{d:DesignInstance Type=viewmodels:MainWindowViewModel}"
        Background="#262626"
        Foreground="White"
        MinHeight="600" 
        MinWidth="950"
        xmlns:ctrl="clr-namespace:Itenso.Windows.Controls.ListViewLayout;assembly=Itenso.Windows.Controls.ListViewLayout"
        >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>

        <Button 
            Grid.Column="0" Grid.Row="1"
            Width="30" Height="30"
            Content="▶"
            Command="{Binding OnPlayButtonClickCommand}"
            />

        <ui:NavigationView  Background="#131313" 
                            Grid.Column="0" Grid.Row="0" 
                            x:Name="NaviView"
                            IsBackButtonVisible="Collapsed"
                            IsSettingsVisible="False"
                            IsTitleBarAutoPaddingEnabled="False"
                            IsPaneToggleButtonVisible="False"
                            PaneDisplayMode="Left"
                            Width="150"
                            >
            <ui:NavigationView.MenuItems>
                <ui:NavigationViewItem
                    Content="Music"
                    Icon="Audio"
                    IsSelected="True" />
            </ui:NavigationView.MenuItems>
        </ui:NavigationView>

        <Grid Background="#131313" 
              Grid.Column="1" Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="80"/>
                <RowDefinition />
            </Grid.RowDefinitions>

            <TextBlock Grid.Row="0"
                       Text="Music"
                       Margin="30,30,30,0"
                       FontWeight="Bold"
                       FontSize="24" />
            <ListView Grid.Row="1"
                      Margin="30,0,0,0"
                      x:Name="MusicList" 
                      SelectionMode="Single"
                      ctrl:ListViewLayoutManager.Enabled="true"
                      ItemsSource="{Binding MusicList}"
                      >
                <ListView.View>
                    <GridView ScrollViewer.VerticalScrollBarVisibility="Auto">
                        <GridViewColumn Header="Title" 
                                        DisplayMemberBinding="{Binding Title}"
                                        ctrl:ProportionalColumn.Width="8"/>
                        <GridViewColumn Header="Artist" 
                                        DisplayMemberBinding="{Binding Artist}"
                                        ctrl:ProportionalColumn.Width="4"/>
                        <GridViewColumn Header="Album" 
                                        DisplayMemberBinding="{Binding Album}"
                                        ctrl:ProportionalColumn.Width="5"/>
                        <GridViewColumn Header="Track" 
                                        DisplayMemberBinding="{Binding Track}"
                                        ctrl:ProportionalColumn.Width="2"/>
                        <GridViewColumn Header="Time" 
                                        DisplayMemberBinding="{Binding Time}"
                                        ctrl:ProportionalColumn.Width="3"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </Grid>
    </Grid>
</Window>

まずWindowタグ内に以下を追加します。
先程インストールしたListViewLayoutManagerが使えるようになります。

xmlns:ctrl="clr-namespace:Itenso.Windows.Controls.ListViewLayout;assembly=Itenso.Windows.Controls.ListViewLayout"

ListViewにはctrl:ListViewLayoutManager.Enabled="true"ItemsSource="{Binding MusicList}"を追加しました。
ListViewLayoutManagerの有効化とViewModelのMusicListプロパティのバインドをしています。
またx:Name="MusicList"とViewModelのNotifyPropertyChanged("MusicList");で設定している名前が一致していることを確認してください。

 <ListView Grid.Row="1"
           Margin="30,0,0,0"
           x:Name="MusicList" 
           SelectionMode="Single"
           ctrl:ListViewLayoutManager.Enabled="true"
           ItemsSource="{Binding MusicList}"
           >

ListView.Viewでは表示するものの設定をしています。
DisplayMemberBindingでは表示するMusicクラスのプロパティをバインドしています。
ctrl:ProportionalColumn.Width=""ではListViewLayoutManagerを使用して列の幅を割合で指定しています。
GridのようにWidth="8*"とは記述できないのでListViewLayoutManagerを使用しています。

<ListView.View>
    <GridView ScrollViewer.VerticalScrollBarVisibility="Auto">
        <GridViewColumn Header="Title" 
                        DisplayMemberBinding="{Binding Title}"
                        ctrl:ProportionalColumn.Width="8"/>
        <GridViewColumn Header="Artist" 
                        DisplayMemberBinding="{Binding Artist}"
                        ctrl:ProportionalColumn.Width="4"/>
        <GridViewColumn Header="Album" 
                        DisplayMemberBinding="{Binding Album}"
                        ctrl:ProportionalColumn.Width="5"/>
        <GridViewColumn Header="Track" 
                        DisplayMemberBinding="{Binding Track}"
                        ctrl:ProportionalColumn.Width="2"/>
        <GridViewColumn Header="Time" 
                        DisplayMemberBinding="{Binding Time}"
                        ctrl:ProportionalColumn.Width="3"/>
    </GridView>
</ListView.View>

ここまででこんな感じの見た目になっているでしょうか。

次回は再生ボタンの修正をしたいと思います。

参考

https://www.urablog.xyz/entry/2018/05/02/070000


この記事が気に入ったらサポートをしてみませんか?