C#のWPFでアイコン付きでファイルの一覧表示するリストビューを作る2。「コンバーターを使ってファイルサイズと更新日付を表示する」

コンピュータ

ソースコード

ファイル名:DataSizeConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;


namespace ListViewWithIcon.converter;

public class DateSizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        long length = (long)value;
        long x = System.Convert.ToInt64(Math.Ceiling((double)length/1024));
        return length < 0 ? "" : string.Format("{0:#,0} KB", x);
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ファイル名:DateTimeConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;


namespace ListViewWithIcon.converter;

public class DateTimeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        DateTime date = (DateTime)value;
        return date.ToString("yyyy/MM/dd HH:mm");
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ファイル名:FileInfoEntity.cs

using System.Windows.Media.Imaging;

namespace ListViewWithIcon;

public class FileInfoEntity
{
    public BitmapSource Icon { get; set; } = new BitmapImage();
    public string Name { get; set; } = "";
    public DateTime LastModifed { get; set; } = new DateTime();
    public long Length { get; set; } = -1;
}

ファイル名:IconManager.cs

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;

public class IconManager
{
    static public BitmapImage GetIcon(string path)
    {
        string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
        string dllPath = Path.Join(systemRoot, @"System32\SHELL32.dll");

        BitmapImage bi = new();
        System.Drawing.Icon? icon;
        if (File.Exists(path))
        {
            icon = Icon.ExtractAssociatedIcon(path);
        }
        else
        {
            icon = Icon.ExtractIcon(dllPath, 3, 64);
        }
        if (icon is null) return bi;

        var ms = new MemoryStream();
        icon.ToBitmap().Save(ms, ImageFormat.Png);
        icon.Dispose();
        ms.Seek(0, SeekOrigin.Begin);

        bi.BeginInit();
        bi.StreamSource = ms;
        bi.CacheOption = BitmapCacheOption.OnLoad;
        bi.CreateOptions = BitmapCreateOptions.None;
        bi.EndInit();
        bi.Freeze();

        ms.SetLength(0);

        return bi;
    }
}

ファイル名:MainWindow.xaml

<Window x:Class="ListViewWithIcon.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:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        xmlns:local="clr-namespace:ListViewWithIcon"
        xmlns:converter="clr-namespace:ListViewWithIcon.converter"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.Resources>
        <converter:DateTimeConverter x:Key="DateTimeConverter"/>
        <converter:DateSizeConverter x:Key="DateSizeConverter"/>
    </Window.Resources>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <interactivity:EventToReactiveCommand Command="{Binding WindowLoadedCommand}" />
        </i:EventTrigger>
        <i:EventTrigger EventName="Closed">
            <interactivity:EventToReactiveCommand Command="{Binding WindowClosedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <ListView
            ItemsSource="{Binding FileInfos}">
            <ListView.Resources>
                <Style x:Key="listviewHeaderStyle" TargetType="GridViewColumnHeader">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView ColumnHeaderContainerStyle="{StaticResource listviewHeaderStyle}">
                    <GridViewColumn Width="300" Header="名前">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <Image Width="16" Height="16" Source="{Binding Icon}"></Image>
                                    <TextBlock Width="Auto" TextAlignment="Left" Text="{Binding Name}" />
                                </StackPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="Auto" Header="更新日時">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Width="100" TextAlignment="Right" Text="{Binding LastModified, Converter={StaticResource DateTimeConverter}}" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="Auto" Header="サイズ">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Width="100" TextAlignment="Right" Text="{Binding Length, Converter={StaticResource DateSizeConverter}}" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

ファイル名:MainWindowViewModel.cs

using System.ComponentModel;
using Reactive.Bindings;
using System.Reactive.Disposables;

using System.IO;

namespace ListViewWithIcon;

public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    private CompositeDisposable Disposable { get; } = new ();
    public void Dispose() => Disposable.Dispose();
    public ReactiveCommand<EventArgs> WindowLoadedCommand { get; }
    public ReactiveCommand<EventArgs> WindowClosedCommand { get; }

    public ReactiveCollection<FileInfoEntity> FileInfos { get; set; } = [];
    public MainWindowViewModel()
    {
        WindowClosedCommand = new ReactiveCommand<EventArgs>()
            .WithSubscribe(e => Disposable.Dispose());
        WindowLoadedCommand = new ReactiveCommand<EventArgs>()
            .WithSubscribe(e =>
            {
                string targetDir = @"F:\csharp\dotnet8\wpf\ListViewWithIcon";
                foreach(var fullname in Directory.EnumerateFileSystemEntries(targetDir))
                {
                    if (fullname is null) continue;
                    var info = new FileInfo(fullname);
                    bool flag = ((info.Attributes & FileAttributes.Directory) == FileAttributes.Directory);
                    FileInfos.AddOnScheduler
                    (
                        new FileInfoEntity
                        {
                            Icon = IconManager.GetIcon(fullname),
                            Name = info.Name,
                            LastModified = info.LastWriteTime,
                            Length = flag ? -1 : info.Length,
                        }
                    );
                }                
            });
    }
}

実行

コンバーターはDateSizeConverterとDateTimeConverterの2つを用意してみました。ConvertとConvertBackのうちConvertのみ(たぶんバンドされたデータ⇒表示文字列)実装しています。
xamlでxmlns:converter="clr-namespace:ListViewWithIcon.converter"を追加してネームスペースが見えるようにして、

    <Window.Resources>
        <converter:DateTimeConverter x:Key="DateTimeConverter"/>
        <converter:DateSizeConverter x:Key="DateSizeConverter"/>
    </Window.Resources>

リソースとしてコンバーターを定義しました。
バインド部分は以下のような感じになります。

<TextBlock Width="100" TextAlignment="Right" Text="{Binding LastModified, Converter={StaticResource DateTimeConverter}}" />
<TextBlock Width="100" TextAlignment="Right" Text="{Binding Length, Converter={StaticResource DateSizeConverter}}" />

相変わらずよく理解していた状態で使っていますが、一応作成者の思った通りなので良しとします。

ファイルサイズの部分でフォルダの場合表示しない仕様にすることを忘れており、サイズが-1の場合表示しないようにしてあります。あとKB表示は単純に1024で割って余りを切り捨てにしてみたのですが、1024以下が0KBになり0バイトでないのに0KBはなんとなく変なので小数点以下を切り上げにしてあります。
作ってみて気が付いたのですが、KB以外の単位の対応や小数点以下の表示などを考える必要がありそうです。

コメント