プログレスバーと聞くと馴染みが無いかもしれませんが、見れば一目瞭然でしょう。
何かの処理が終了するまで待つ間、状況をビジュアル的に表すものです。
横長の長方形の枠の中に、左から青い四角が埋まっていって、全部埋まれば終了!
プログレスバーはテキストボックスやボタンと同様、ダイアログに表示して使います。
リソースのダイアログを開き、コントロールの一覧の中から
「プログレスバー」を選んで貼り付けるだけで簡単に準備できます。
ダイアログを最初から作るのが面倒なので(笑)、 「バージョン情報」のダイアログにプログレスバーを付けてみました。
ちなみにCopyrightの表示が1902になっていますが、このサンプルを作成したのは2002年なので、間違いですね。 『2000年問題』が対応されていないのでしょう(苦笑) もっとも、「西暦2000年を過ぎてるのにVC++4.0なんていう古いものを使い続けるなよ」って話もありそうですが(汗)
さて、プログレスバーをダイアログに貼り付けただけでは
当然何も動きません。
まずは、ClassWizardでCAboutDlgクラスの変数を割り当てておきます。
class CAboutDlg : public CDialog
{
〜
// ダイアログ データ
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
CProgressCtrl m_Progress1;
//}}AFX_DATA
〜
};
プログレスバーは CProgressCtrlクラスで処理されます。
基本的な使い方は、まずSetRangeで全体の範囲を指定しておき、状況の進展をその範囲内の数値でSetPosを使って指定するというものです。
プログレスバーの範囲をSetRangeを使って設定します。初回に1回設定すればいいので、 WM_INITDIALOGのハンドラで行います。
test.cpp:class CAboutDlg : public CDialog { 〜 // Implementation protected: //{{AFX_MSG(CAboutDlg) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() 〜 }; 〜 const int MAX=1000; BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: この位置に初期化の補足処理を追加してください m_Progress1.SetRange(0,MAX); return TRUE; // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります // 例外: OCX プロパティ ページの戻り値は FALSE となります }
次は状況の進展を表示する場所ですが、
タイマー割り込みを使って一定周期で描き換えるなり、
描き換え用のイベントを作って連続して呼び出すなり、
自由です。
ここでは大分手抜きをして、WindowProcの中で描き換えることにします。
WindowProcはイベントの処理を行う関数なので、全イベントがこの関数を経由します。
つまり、一番よく呼ばれる関数だということです。
class CAboutDlg : public CDialog { 〜 // ClassWizard は仮想関数を生成しオーバーライドします。 //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV のサポート virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); //}}AFX_VIRTUAL 〜 }; 〜 LRESULT CAboutDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください if(m_Progress1.m_hWnd){ m_Progress1.SetPos(状況の進展に伴う値); UpdateData(FALSE); } return CDialog::WindowProc(message, wParam, lParam); }
SetPosに渡す値がここでは未定ですが、サンプルとして 0から始まってMAXまで1ずつ増加していく様子をプログレスバーで表すことにします。 この増加用の変数として、CAboutDlgにm_nValueを追加します。
test.cpp:class CAboutDlg : public CDialog { private: int m_nValue; }; 〜 BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: この位置に初期化の補足処理を追加してください m_Progress1.SetRange(0,max); m_nValue=0; return TRUE; // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります // 例外: OCX プロパティ ページの戻り値は FALSE となります } 〜 LRESULT CAboutDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください if(m_Progress1.m_hWnd){ if(m_nValue<MAX){ m_nValue++; } m_Progress1.SetPos(m_nValue); UpdateData(FALSE); } return CDialog::WindowProc(message, wParam, lParam); }
やあ、表示された表示された…と喜んでいましたが、よく見ると
プログレスバー全体の枠線が表示されていません。
どうやら枠線が表示される前にSetPosとUpdateDataを実行しているのがまずいようです。
仕方がないので、枠線が表示されてからSetPosを始めるような処理を加えます。
class CAboutDlg : public CDialog { 〜 private: int m_nValue; BOOL m_bDraw; }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT m_bDraw=FALSE; } 〜 LRESULT CAboutDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください if(message==WM_PAINT) m_bDraw=TRUE; if(m_Progress1.m_hWnd && m_bDraw){ if(m_nValue<MAX){ m_nValue++; } m_Progress1.SetPos(m_nValue); UpdateData(FALSE); } return CDialog::WindowProc(message, wParam, lParam); }
これにより、WM_PAINTの後だけSetPosが実行されるようになりました。
そして、ダイアログバーの枠線もちゃんと出るようになりました。
ちなみに何故WM_PAINTなのかと問われれば、答は「試行錯誤の結果」です…
だから、本当はWM_PAINTではまずいかもしれません。
ところで、WindowProcでm_nValueを増加させる処理を行ってきましたが、
何のイベントも無いと WindowProcは余り呼ばれない様です。
試しにダイアログの上でマウスを動かしてみると、とても早く
プログレスバーが進みます(苦笑)
ちなみに、プログレスバーで状況を表示する計算処理ですが、普通は別スレッドを起こして そちらで行います。 そのやり方はマルチスレッドを参照して下さい。