作画資料管理ツールを作りたい #07 フォルダ内ファイルのサムネイル一覧【高速化の検討④】BackGroundWorker
はじめに
作りたいもの
作画資料管理ツール
- 画像ファイルをタグで分類できる(pixivやpinterestみたいに)
- サムネイル一覧できる
- 選択画像のプレビューができる
↑ここまでで一旦完成
- 動画も扱う
- 簡単な画像処理もできる(動画のフレームやトリミングもできたらいいな)
- LLM?とかで類似画像の検索ができる
↑これは発展
シリーズ記事一覧
前回
サムネイル一覧表示を高速化する。
- ListViewの仮想モードを改善
今回
サムネイル一覧表示を高速化する。
- BackGroundWorkerを使った非同期処理
能力
- 初心者
- C#とフォームアプリの作り方を勉強する
1. 環境
- Windows 11 pro
- Visual Studio Community 2022 (Version 17.14.12)
- プロジェクトの種類:Windowsフォームアプリ
2. フォームデザイン
主なコントロールと内容
| コントロール | プロパティ | 内容 |
|---|---|---|
| Form | (Name) | Form1 |
| TreeView | (Name) | treeViewDirectory |
| ListView | (Name) | listViewThumbnail |
| View | LargeIcon | |
| ImageList | (Name) | imageListThumbnail |
| PictureBox | (Name) | pictureBoxPreview |
| BackgroundWorker | (Name) | backgroundWorkerThums |
| WorkerReportsProgress | true | |
| WorkerSupportsCancellation | true | |
| ProgressBar | (Name) | progressBar1 |

3. ソースコード
フィールド
次のように変更した。
private string selectedDir = ""; //選択中のディレクトリ
private List<string> itemsFullPath = new(); //ファイルのフルパス格納用
private int width = 200, height = 200; //サムネイルサイズ
private Image[] bgImges; //バックグラウンドでサムネイル作成用
// RetrieveVirtualItemy用
private int lvItemsCount; // ListViewItem数
private ListViewItem[] lvVirtualItems; //ファイル名と画像インデックス
private int progressCnt; //プログレスバー用カウンタ
イベントハンドラ
-
Form1_Load
TreeViewの最上位にドライブ一覧を構成する。 -
treeViewDirectory_BeforeExpand
展開ドライブまたはフォルダの配下のフォルダ一覧を構成する。 -
treeViewDirectory_NodeMouseClick
選択フォルダ内の画像ファイル一覧を作り、サムネイルで表示する。 -
listViewThumbnail_SelectedIndexChanged
選択画像ファイルをプレビューする。 -
listViewThumbnail_RetrieveVirtualItem
\textcolor{red}{←変更}
前回、サムネイル作成とListViewのItem作成する処理をを分け、なんとか仮想モードを使えるようになった。
しかし、サムネイル作成はこのメソッドの中で毎回おこなっているのでもっさり重たかった。
そこで今回新たに追加した'backGroundWorkerThums_DoWork'側でサムネイル一覧を作成するようにした。
private void listViewThumbnail_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"■■■listViewThumbnail_RetrieveVirtualItem");
var data = lvVirtualItems[e.ItemIndex];
// 画像がまだダミーなら読み込み
if (data.ImageIndex == 0)
{
if (bgImges[e.ItemIndex] == null)
{
//何もしない
}
else
{
imageListThumbnail.Images.Add(bgImges[e.ItemIndex]);
data.ImageIndex = imageListThumbnail.Images.Count - 1;
}
}
e.Item = lvVirtualItems[e.ItemIndex];
}
- backGroundWorkerThums_DoWork
\textcolor{red}{←追加}
非同期処理を行う。
サムネイル一覧作成用のメソッドを呼び出す。
private void backGroundWorkerThums_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"■■■backGroundWorker1_DoWork");
bgLoadImgs();
}
- backgroundWorker1_ProgressChanged
\textcolor{red}{←追加}
サムネイル作成の進捗を表示するプログレスバー。
サムネイル一覧作成用のメソッドの中で値を更新していく。
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
メソッド
-
addTree_Drive
フォルダツリーの最上位(ドライブ一覧)を構成する。 -
addTree_Child
フォルダツリーの選択フォルダ内のサブフォルダを構成する。 -
resizeImg
画像をリサイズする。 -
makeListThumbs
\textcolor{red}{←変更}
サムネイル一覧を作成する。
ファイル名の一覧を別メソッドに分けた。
バックグラウンド処理を実行するbackgroundWorkerThums.RunWorkerAsync();を追加した。
//サムネ一覧構成
private void makeListThumbs(string dir)
{
System.Diagnostics.Debug.WriteLine($"-- makeListThumbs --");
try
{
itemsFullPath = getNameFullPath(dir);
}
catch
{
System.Diagnostics.Debug.WriteLine($"例外 -- makeListThumbs ");
return;
}
//RetrieveVirtualItem用
lvItemsCount = itemsFullPath.Count;
//RetrieveVirtualItem用
lvVirtualItems = new ListViewItem[lvItemsCount];
bgImges = new Image[lvItemsCount];
//サムネイル一覧の初期化
listViewThumbnail.Items.Clear();
imageListThumbnail.Images.Clear();
imageListThumbnail.ImageSize = new Size(width, height);
// Index 0: 透明なダミー画像
Bitmap dummy = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(dummy))
{
g.Clear(Color.Transparent);
}
imageListThumbnail.Images.Add(dummy);
//画像ファイルを1つずつ処理
for (int i = 0; i < itemsFullPath.Count; i++)
//Parallel.For(0, lvItemsCount, i =>
{
//フルパスからファイル名のみ抽出
string fileName = System.IO.Path.GetFileName(itemsFullPath[i]);
//サムネのファイル名追加
lvVirtualItems[i] = new ListViewItem(fileName, 0); //ファイル名, イメージインデックスを指定
//});
}
listViewThumbnail.LargeImageList = imageListThumbnail;
// 仮想モードON
listViewThumbnail.VirtualMode = true;
// Item数設定
listViewThumbnail.VirtualListSize = lvItemsCount;
//サムネ一覧作成のバックグラウンド
backgroundWorkerThums.RunWorkerAsync();
}
- getNameFullPath
\textcolor{red}{←追加}
ファイルパス(フルパス)の一覧を取得する。
//画像ファイルパス一覧取得
private List<string> getNameFullPath(string dir) {
//表示対象のファイル形式
string[] extensions = { ".jpg", ".jpeg", ".png", ".tif", ".jfif", ".bmp" };
List<string> fullpath = System.IO.Directory.GetFiles(dir, "*.*")
.Where(file => extensions.Any(pattern =>
file.ToLower().EndsWith(pattern))
).ToList();
return fullpath;
}
- bgLoadImgs
\textcolor{red}{←追加}
サムネイル一覧を作成する。
Parallel.Forを使って並列処理していく。
進捗状況はフォームのスレッドを使うためInvoke()によりプログレスバーを更新する。
private void bgLoadImgs()
{
progressCnt = 0; //プログレスバー用カウンタ
Parallel.For(0, itemsFullPath.Count, i =>
{
//for (int i = 0; i < itemsFullPath.Count; i++) {
bgImges[i] = resizeImg(itemsFullPath[i], width, height);
//プログレスバーの更新
progressCnt++;
int progressPerc = (int)((double)progressCnt / (double)itemsFullPath.Count * 100);
Invoke( () => backgroundWorkerThums.ReportProgress(progressPerc));
//}
});
}
実行結果
変更前(前回)
変更後(今回)
フォルダ展開直後や多少のもたつきはあるけれど、普通に許せるのではないだろうか。
スクロールバーや矢印キーにも前回よりだいぶ追いついてくれるようになった。
ただ、BackGroundWorkerは現在では非推奨ということらしいので、Task/async/awaitの方法に置き換えた方がよさそう。
あとおまけでプログレスバーを付けてみたが、Parallel.Forにすると処理は完了しても100%にならない。
今は重要ではないのでそのうち改善しよう。
参考にさせていただきました
ソースコード全体
(省略)
記事が長くなってしまうしコピペが面倒になってきた。github?を使った方がいいのかな…
おわり
世の中のツールと比べるとまだまだ及びませんが、個人的には使いものになりそうな感じです。
今回の内容をTask/async/awaitの方法に変更できたら、一旦サムネ一覧の高速化は終えて、データベース連携に着手していこうと思います。
Discussion