C#のWPFでアイコン付きでファイルの一覧表示するリストビューを作る。

コンピュータ

ファイルマネージャを作っていまして、ファイルの一覧のUIをリストビューで試作してみました。

ソースコード

ファイル名:FileInfoEntity.cs

using System.Windows.Media.Imaging;

namespace ListViewWithIcon;

public class FileInfoEntity
{
    public BitmapSource Icon { get; set; } = new BitmapImage();
    public string Name { get; set; } = "";
}

ファイル名: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);
        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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <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="Auto">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Image Source="{Binding Icon}"></Image>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="Auto" Header="名前">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Width="260" TextAlignment="Left" Text="{Binding Name}" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="Auto" Header="サイズ">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Width="100" TextAlignment="Right" Text="100" />
                            </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 name = Path.GetFileName(fullname);
                    FileInfos.AddOnScheduler(new FileInfoEntity { Icon = IconManager.GetIcon(fullname), Name = name });
                }                
            });
    }

}

実行


概ねこんな感じですが、アイコンとファイル名が別のセルになっている点と一つのセルが大きい点が気になります。

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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <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="Auto" Header="名前">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <WrapPanel>
                                    <Image Width="16" Height="16" Source="{Binding Icon}"></Image>
                                    <TextBlock Width="300" TextAlignment="Left" Text="{Binding Name}" />
                                </WrapPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="Auto" Header="サイズ">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Width="100" TextAlignment="Right" Text="100" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

アイコンの列をやめて、名前の列にWrapPanelを設置その上にアイコン用のImage、名前用のTextBlockを配置しました。
ImageアイコンのWidthとHeightを小さめにすることで結果セルのサイズを小さくしています。

アイコンの作成でSystem.Drawing.CommonパッケージをインストールしてSystem.Drawing.Icon⇒System.Drawing.Bitmap⇒BitmapImage=BitmapSourceとかなり回りくどい変換処理をおこなっています。

追記

ListView
ListView コントロールは、データを 1 つの列に垂直に積み重ねて表示します。 ListView は、テキストをフォーカル ポイントとして持つ項目や、上から下に読み取るように意図されたコレクション (アルファベット順など) の場合に適しています。 ListView の一般的な使用例としては、メッセージや検索結果の一覧などがあります。 複数の列またはテーブルのような形式でコレクションを表示する必要がある場合は、ListView を使用 しないでください 。 代わりに、 DataGrid コントロールの使用を検討してください。

コメント