昨日に続き、プログレスバーをマルチスレッドを用いて実装してみましょう。
作成の目的は、
- スレッド実行中はモーダルダイアログを表示させること。
- モーダルダイアログではプログレスバーを表示し進捗が分かること。
- 実行中のスレッドはダイアログのメンバーにアクセスできること。
- スレッド処理が完了次第、モーダルダイアログを閉じること。
でした。
昨日までで関数宣言などは完了したので、各関数の定義・実装を進めていきます。
関連リンク: 【MFC】マルチスレッドでプログレスバーを実装しよう その1
Contents
{プロジェクト名}Dlg.cpp
まずはメインダイアログであるDIALOG1のソースコードを編集していきます。
静的変数 static int nCountの実装
プログレスバーが表示されるダイアログから監視される変数として、
1 2 |
private: static int nCount; |
を宣言していました。
静的変数なので実装が必要です。
{プロジェクト名}Dlg.cppファイルの適当な場所に以下の実装を記述します。
1 2 |
// 静的変数の実装 int CMy002_PrgDlgDlg::nCount = 0; |
クラス名は適宜読み替えてくださいね。
ボタンのイベントハンドラ、OnBnClickedButton1()の実装
次に前回、メインダイアログ(DIALOG1)に追加したボタンのイベントハンドラ、OnBnClickedButton1()の実装を行いましょう。
機能としてはボタンを押下されたらスレッドを開始し、プログレスバーが表示されるダイアログを表示します。
1 2 3 4 5 6 7 8 9 10 |
void CMy002_PrgDlgDlg::OnBnClickedButton1() { AfxBeginThread(ThreadProc, (LPVOID)this); // スレッドの開始 CPrgDlg cPrgDlg; cPrgDlg.DoModal(); // メインダイアログの処理を止め // プログレスバーダイアログ(DIALOG2)を表示 AfxMessageBox(_T("Complete")); // スレッドの完了後呼ばれる } |
AfxBeginThread()関数がスレッドを開始する関数になります。
関連リンク:AfxBeginThread | MSDN
第6引数まである関数ですが、第3~第6まではデフォルト引数が指定されているので、2つの引数を渡します。
- 第一引数:別スレッドとして実行される関数のポインタ
(ワーカースレッドの制御関数) - 第二引数:制御関数に渡す引数
第二引数はvoid *型なのでポインタであれば何でも渡すことができます。
今回は制御関数の中でメインダイアログクラスのメンバー関数を呼び出すため、自分自身を引数として渡しています。
5行目 ~ 6行目はダイアログのクラスを宣言し、DoModal()関数を呼び出すことでプログレスバー付きのダイアログ(DIALOG2)を表示させています。
メインダイアログの処理はDIALOG2に移りましたので、DIALOG2のウィンドウを閉じるまではここで処理が止まります。
スレッドの処理が完了次第、DIALOG2は終了するように実装するため、処理が戻る8行目に完了を通知するメッセージボックスを表示します。
ワーカースレッドの制御関数
static UINT ThreadProc(LPVOID pParam)
AfxBeginThread()関数から呼び出される、ワーカースレッドの制御関数は以下のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
UINT CMy002_PrgDlgDlg::ThreadProc(LPVOID pParam) { // AfxBeginThread()の第二引数から受け取ったpParamを適切な型にキャストします。 CMy002_PrgDlgDlg *pTheDlg = dynamic_cast<CMy002_PrgDlgDlg*>(reinterpret_cast<CWnd*>(pParam)); // 正しくキャストできれば、メインダイアログのメンバー関数、 // ワーカースレッドで実際に実行される関数を呼び出します。 if (pTheDlg) { pTheDlg->ThreadProc(); } return 0; } |
まずは、AfxBeginThread()の第二引数に与えたvoid *型を正しい型(今回はダイアログクラスでした)に変換します。
関連リンク:reinterpret_cast 演算子 | MSDN
関連リンク:dynamic_cast 演算子 | MSDN
まず、reinterpret_castでポインタ型の変換、dynamic_castで正しいオブジェクトへと変換します。
正しく変換が完了できれば、実際に別スレッドで実行される関数を呼び出します。
基本的には重たい処理やユーザーを待たせたい処理の実行関数になるでしょうか。
実処理を行う関数、void ThreadProc()
この実処理を行う関数は人によってそれぞれ異なるのでどんな内容でも構わないのですが、以下のようにしました。
1 2 3 4 5 6 7 8 9 |
void CMy002_PrgDlgDlg::ThreadProc() { while (nCount <= 10000000) { nCount++; if (!(nCount % 100)) { Sleep(100); } } } |
メンバー変数である、nCount(今回は静的変数ですがメンバー変数であってもアクセス可能です)をインクリメントし、100回に一度、100ミリ秒のスリープを実行します。
nCountが10,000,000に到達した時点でループから抜けて処理が終了、スレッドもほぼ同時に終了となります。
ここまででメインのダイアログの記述は終了です。
長くなってしまったのでプログレスバーが表示されるダイアログクラスの実装は明日行いましょう。
まとめ
スレッドが目にも止まらぬ速度で終わってはスレッドの進行が分からないので無意味なスリープなどを挿入していますが、実際にはファイルの書き出しやローディングなどが実行されることになるでしょうか。
クラスの設計をしっかり行っていればメインのダイアログで処理に必要な変数はできる限り少なくしておくべきですがコントロール変数などを持っているダイアログのメンバーにアクセスできるため、利用しやすいですよね。