C#リストビューで画像ファイルのサムネイル表示3

コンピュータ
前回作成したサンプルプログラムでは表示する件数が多くなると待ち時間が発生していたので対策をしてみました。
C#リストビューで画像ファイルのサムネイル表示2
サムネイル表示する画像の縦横の比率を保持した状態で縮小します。 .NET7でコンパイルが通るように修正しています。 実行環境構築 プロジェクトの作成 mkdir プロジェクト名 cd プロジェクト名 dotnet new winforms ...

プログラムソース

ファイル名:Form1.cs

using System.Diagnostics;
using System.Linq;

namespace ThumSample3;

public partial class Form1 : Form
{
    string _path = @"C:\Users\karet\Pictures";
    List<ListViewItem> _listitem = new();

    ListView _listView = new()
    {
        Dock = DockStyle.Fill,
        OwnerDraw = true,
        VirtualMode = true,
        LargeImageList = new ImageList
        {
            ImageSize = new Size(Thumbnail1.Width, Thumbnail1.Height),
        },
    };
    MenuStrip _menuStrip = new();
    ToolStripMenuItem _menuItem = new()
    {
        Text = "実行",
    };
    public Form1()
    {
        InitializeComponent();

        _menuStrip.Items.Add(_menuItem);
        Controls.AddRange(new Control[] {_menuStrip, _listView});

        _menuItem.Click += _menuItem_Click;
        _listView.RetrieveVirtualItem += _listView_RetrieveVirtualItem;
        _listView.DrawItem += _listView_DrawItem;

        Size = new Size(1980, 1080);
    }
    async void _menuItem_Click(Object? sender, EventArgs e)
    {
#if DEBUG
        Debug.Print($"{Thread.CurrentThread.ManagedThreadId}:Click Start");
        var sw = new Stopwatch();
        sw.Start();
#endif
        _menuItem.Enabled = false;

        IEnumerable<string> imgFiles = System.IO.Directory.EnumerateFiles(_path, "*.png", System.IO.SearchOption.AllDirectories);

        int w = _listView.Size.Width / Thumbnail1.Width;
        int h = _listView.Size.Height / Thumbnail1.Height;
        int takeValue = (w * (h+1));
        if (takeValue > 0)
            await Task.Run(()=>Thumbnail1.LoadCache(imgFiles.Take(takeValue)));

        if (_listitem.Count > 0) _listitem.Clear();
        foreach(var file in imgFiles)
        {
            _listitem.Add(new ListViewItem(file));
        }
        _listView.VirtualListSize = _listitem.Count;

        if (takeValue > 0)
            await Task.Delay(1000);
            await Task.Run(()=>Thumbnail1.LoadCache(imgFiles.Skip(takeValue)));
        
        _menuItem.Enabled = true;
#if DEBUG
        sw.Stop();
        Debug.Print($"{Thread.CurrentThread.ManagedThreadId}:Click End 計測時間:{sw.ElapsedMilliseconds}msec");
#endif
    }
    void _listView_RetrieveVirtualItem(Object? sender, RetrieveVirtualItemEventArgs e)
    {
        if (e.Item == null) e.Item = _listitem[e.ItemIndex];
    }
    void _listView_DrawItem(Object? sender, DrawListViewItemEventArgs e)
    {
        string filename = e.Item.Text;
        Image? thumbnail = Thumbnail1.LoadImage(filename);
        if (thumbnail == null)
            return;

        Rectangle imagerect = new Rectangle(new Point(e.Bounds.X + ((e.Bounds.Width - thumbnail.Width) / 2), e.Bounds.Y), new Size(thumbnail.Width, thumbnail.Height));

        e.DrawDefault = false;
        e.DrawBackground();
        e.Graphics.DrawImage(thumbnail, imagerect);

        var stringFormat = new StringFormat() {
            Alignment = StringAlignment.Center,
            LineAlignment = StringAlignment.Center,
        };
        e.Graphics.DrawString(e.Item.Text, _listView.Font, Brushes.Black, new RectangleF(e.Bounds.X, e.Bounds.Y + imagerect.Height + 5, e.Bounds.Width, e.Bounds.Height - imagerect.Height - 5), stringFormat);
        e.DrawFocusRectangle();
    }
}//class

ファイル名:Thumbnail1.cs

using System.Diagnostics;
public class Thumbnail1
{
    static public readonly int Width = 384;
    static public readonly int Height = 384;

    static Object _lockObj = new();
    static Dictionary<string, Image> _thumbnailCache = new();

    static public Bitmap CreateThumbnail(string filename)
    {
        using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read);

        Bitmap canvas = new Bitmap(Width, Height);
        using var original = Bitmap.FromStream(fs);
        using var g = Graphics.FromImage(canvas);
        g.Clear(Color.White);

        double fw = (double)Width / (double)original.Width;
        double fh = (double)Height / (double)original.Height;

        double scale = Math.Min(fw, fh);

        int w2 = (int)(original.Width * scale);
        int h2 = (int)(original.Height * scale);

        g.DrawImage(original, (Width-w2)/2, (Height-h2)/2, w2, h2);

        return canvas;
    }

    public static Image? LoadFile(string filename)
    {
        if (!File.Exists(filename)) return null;
        var thumbnail = CreateThumbnail(filename);
        lock(_lockObj)
        {
            if (!_thumbnailCache.ContainsKey(filename))
                _thumbnailCache.Add(filename, thumbnail);
        }
        return thumbnail;
    }
    public static Image? LoadImage(string filename)
    {
        if (_thumbnailCache.ContainsKey(filename))
        {
            return _thumbnailCache[filename];
        }
        return LoadFile(filename);
    }
    public static void LoadCache(IEnumerable<string> files)
    {
        Parallel.ForEach(files, filename=>
        {
            var _ = LoadImage(filename);
        });
    }
}

実行


実行ボタンを押すと1ページ分を先に読み込み表示しその後別スレッドで残りの画像を読み込むようにしてみました。
前回のプログラムから1/3程度の時間で表示されるようになりましたが、取り扱う画像の件数が多い場合やファイルサイズが大きい場合それなりに効果がありますが、動作は前回のプログラムのほうが安定感があります。

コメント