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

C# コンピュータ
C#

ListViewにアイコンを載せることが出来るのですが、画像サイズが256×256と結構大き目なサイズで表示できるようです。

この機能を使えば画像ファイルのサムネイルを表現することが出来るので試してみたいと思います。

はじめこのサンプルはコンストラクタで特定のディレクトリ内の画像ファイルをサムネイル表示するように作っていました。
画像ファイルの読み込み処理は比較的時間がかかる処理であり、複数の画像を読み込み処理をすればファイル数分だけ待ち時間が発生します。
初回実行時にウィンドウが表示されるまでだいぶ待たされることになり、起動していないかと思いました。
コードをシンプルにするためコンストラクタで実行するようにしていたわけですが、今回はそれがよろしく無いようです。
ということでメニューに実行ボタンをつけてそちらを押すとサムネイルが表示するようにしてみました。

ListViewにアイコンを表示させる場合。
まず項目の値をnew ListViewItem(<"項目の値">, <画像番号>)で作成し、
ListView.Items.Add()で追加してあげます。
また、ImageList.Images.Add(<ビットマップオブジェクト>)で表示するアイコンを追加してあげます。
ListViewItem画像番号はImageListの追加した画像の位置を表しますので、同じループ内で処理してあげたくなります。
ディレクトリ内のファイル名を取得→ListViewItemにファイルを追加→ImageListにビットマップ読み込み追加…以下繰り返し

実際これでも動きます。しかし一件一件処理するとちらつきが発生します。対策としてはListViewに値を追加する処理中はコントロールの描画処理を一時停止させる方法が思いつきます。
調べたところListViewにはBeginUpdateEndUpdateというメソッドがあり、それを使えばちらつきが抑制出来そうです。
ただその情報元のサイトにはAddRangeで一括処理した場合でもちらつきが抑制出来るとの記述がありました。
ケースバイケースではありますが、今回はAddRangeを使ってみたいと思います。

実行ボタンをつけちらつきも改善されましたが、ボタンを押した後ListViewが表示されるまでフォームがロックされてしまいます。
これまでこの手の問題が発生してほしくなかったのですが、asyncawaitTask.Runを施してみたいと思います。
ようは時間がかかる処理を別スレッドで実行(今回の場合はListViewの更新)しフォームをロックさせない非同期処理になるわけですが、この非同期処理というかスレッドのことはよくわかっていません。
同期処理しかやってこなかった人間としてはなんとも気持ち悪い。
とりあえず学習したことは、
Task.Runで別スレッドにしたいコードを記述する。
awaitTask.Runの実行結果を受け取り、その後は普通に同期処理のように記述していく。
Task.Run及びawaitを含むメソッドはasyncを付与する。
今回はこれで思った通りの動作をしてくれましたが、自分の物になっていない感がものすごいです。

あと、LINQを使った処理を少し導入してみました。SQLに似たというか個人的になじみ深いのはPowerShellの一連のパイプライン処理に似ています。
型のあるタイプの言語でこれを実現しているところを見ると時代は進んだなぁと感じる次第でございます。

ところで、C#のサンプルの記事をよく上げますが、実際は作りたいアプリケーションがあってそれのコードの断片をテストや学習している内容になります。
本来やりたいことは、画像ファイルだけでなくサブディレクトリとzipファイル内の画像ファイルでサムネイルを表示させたいので、さらに処理が重くなります。
画像の縦横比もおかしいですが、これも対応しようとすれば処理が重くなります。
実用的なレスポンスを確保するためにはもうひとひねりする必要がありそうです。

ソース

ファイル名:TaskWithThumbnail.cs

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using System.Linq;
// 
// タスクのサンプル
// 
namespace MyTask
{
    class Form1 : Form
    {
        MenuStrip menubar;    // メニューバー
        ToolStripMenuItem menuItem_Exec;    // 実行

        ListView listView;    // リストビュー
        ImageList imgList;    // イメージリスト
        /////////////////////////////
        // Main
        /////////////////////////////
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }
        // コンストラクタ
        public Form1()
        {
            InitControls();
        }
        // コントロールの初期化
        void InitControls()
        {
            // メニュー関連

            menubar = new MenuStrip();
            menuItem_Exec = new ToolStripMenuItem {
                Text = "実行"
            };
            menuItem_Exec.Click += menuItem_Exec_Click;
            menubar.Items.Add(menuItem_Exec);
            Controls.Add(menubar);

            // リストビュー関連

            listView = new ListView {    // リストビュー
                Dock = DockStyle.Fill,   // クライアント領域全体に広げる
            };
            listView.Columns.Add("NAME");    // // カラムの追加
            imgList = new ImageList {   // イメージリスト
                ImageSize = new Size(256, 256)    // 画像サイズ256x256
            };
            listView.LargeImageList = imgList;
            Controls.Add(listView);    // フォームにリストビューを登録
        }
        // 実行ボタンのクリック処理
        async void menuItem_Exec_Click(object s, EventArgs e)
        {
            string path = @"H:\Pictures\20200423";    // 画像ファイルが保存されたディレクトリ

            // リストビューをクリア
            listView.Items.Clear();
            imgList.Images.Clear();

            Image[] b = {};
            ListViewItem[] a = {};

            Task<int> task = Task.Run(() =>{

                var dirInfo =  new DirectoryInfo(path);
                var i = 0;

                /* オリジナル(ちらつきます)
                foreach (var f in dirInfo.EnumerateFiles("*.png")) {
                    listView.Items.Add(new ListViewItem(f.Name, i));
                    using(var bmp = Bitmap.FromFile(f.FullName)) {
                        imgList.Images.Add(bmp);
                    }
                    i++;
                }
                */
                // LINQで書き直し
                var d = dirInfo.EnumerateFiles("*.png");
                b = d.Select(x => Bitmap.FromFile(x.FullName))
                    .ToArray();
                a = d.Select(x => new ListViewItem(x.Name, i++))
                    .ToArray();
                
                return i;
            });
            int result = await task;
            if (result > 0) {
                imgList.Images.AddRange(b);
                listView.Items.AddRange(a);
            }
        }
    }
}

コンパイル

.NetFramework

PS>csc ./TaskWithThumbnail.cs

実行

.NetFramework

PS>./TaskWithThumbnail.exe


コメント