ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MFC对话框添加工具栏:原理、代码与常见问题

MFC对话框添加工具栏:原理、代码与常见问题 简介这是一份面向Visual C与MFC开发者的完整示例工程聚焦对话框窗体如何集成工具栏这一常见需求覆盖CDialog派生类搭建、工具栏资源设计、控件关联以及按钮消息映射等关键环节适合正在学习MFC界面开发或需要为对话框添加快捷工具入口的Windows开发者。压缩包共19个文件、约17KB文件类型以头文件、C源文件、资源脚本和位图图标为主。.h和.cpp负责对话框、视图及文档类逻辑.rc/.rc2维护界面资源.ico/.bmp提供程序与按钮图标dsp/dsw保存工程配置。目录结构简洁便于对应MFC各模块逐一研读。目前已有181人学习/下载。通过该示例读者可以看到从资源编译到消息响应的完整路径理解CDialog与CToolBar的关联方法并快速套用到自己的对话框项目中还可以借鉴其工程组织方式为课堂作业或小型工具软件提供参考。1. ダイアログベースの Visual C アプリにツールバーを追加するコントロールバーは「貼る」ではなく「組み込む」話「Visual C でダイアログにツールバーを追加したい」という検索でこのタイトルにたどり着いた人は、おそらく MFC のダイアログベースアプリを触っている。リソースエディタでツールバーをぽんと置ければ楽だが、Visual C のダイアログエディタには「ツールバー」という標準コントロールがない。結局のところ、CToolBar をコードで生成して、コントロールバーとして組み込むのが正解になる。ただし、SDI/MDI のフレームウィンドウと違って、CDialog はツールバーを自動配置しない。しかも Visual C のバージョンによってリソースの作り方が微妙に異なるため、検索で見つけた VC6 の記事が VS2015 でそのまま通じないことも珍しくない。ここでは、VC6.0 から VS2019 のダイアログプロジェクトで動く最小手順を、リソース設計、コード、失敗パターンまで含めて整理する。ツールバーを正しく組み込めば、ダイアログは「ただの入力フォーム」から「操作するアプリ」に見た目が変わる。2. ダイアログはツールバーを「知らない」原理とリソース設計の準備2.1 コントロールバーという存在SDI/MDI との決定的な違いMFC の SDI/MDI アプリでは、フレームウィンドウが CToolBar を子ウィンドウとして作成し、ドッキング、フロート、リサイズ時の再配置をすべて自動的に処理する。一方、CDialog は CFrameWnd ではなく、コントロールバーを管理する仕組みを持っていない。つまり、ダイアログ上の CToolBar は「ただの子ウィンドウ」でしかない。この前提が抜け落ちると、例えばツールバーは表示されるものの、ダイアログのリサイズでツールバーが上端の一部にしか表示されなかったり、後ろのコントロールに隠れたりする。ダイアログでツールバーを使うときの基本的な流れは次のとおりだ。リソースエディタでツールバーリソースを作成する。ダイアログクラスに CToolBar のメンバ変数を追加する。OnInitDialog で Create して、LoadToolBar でリソースを読み込む。RepositionBars を呼び、ツールバーが占有する高さ分だけダイアログのクライアント領域を調整する。OnSize で同じ RepositionBars を呼び、リサイズに追従させる。SDI では 3 まで書けばあとはフレームがやってくれる。ダイアログでは 4 と 5 を忘れると、期待通りのレイアウトにならない。特に 5 は、ダイアログのサイズを変更できる場合に必須だ。ここで、コントロールバーの ID が重要になる。RepositionBars は、AFX_IDW_CONTROLBAR_FIRST から AFX_IDW_CONTROLBAR_LAST の範囲の ID を持つコントロールバーの位置を計算する。ツールバーを Create する際、第 3 引数で ID を指定できる。省略した場合は AFX_IDW_TOOLBAR が使われる。ただし、ダイアログに複数のコントロールバーを置く場合は、ID を重複させないようにしよう。VC6 の時代の記事では、リソース ID に IDR_MAINFRAME を流用する例が多いが、ダイアログに専用のツールバーリソースを割り当てる場合、IDR_MAINFRAME を流用するとメニューやアクセラレータと ID が衝突してコマンドルーティングが混乱することがある。私はダイアログ用に IDR_DLG_TOOLBAR のような ID を新設する。2.2 ツールバーリソースの作成とボタン ID の振り方Visual C でリソースを追加する手順は、バージョンで少し違う。VC6 はリソースビューでプロジェクトを右クリックし、「挿入」→「リソース」でツールバーを選ぶ。VS2015 以降は、ソリューションエクスプローラーのリソースファイルを右クリックし、「追加」→「リソース」でツールバーを選ぶ。ここで生成されるのは .rc 内のツールバーセクションと、リソースフォルダ内のビットマップファイルだ。ツールバーエディタでは、新しいボタンを追加して ID とプロンプトを設定する。プロンプトは、ステータスバーに表示する文字列とツールチップの両方に使われる。ここで設計を間違えると、あとで全ボタンがグレーアウトしたり、クリックしても何も反応しなかったりする。最初のうちは、ボタン ID を次のように統一しておくと分かりやすい。ボタン名IDコマンドハンドラ新規IDC_TB_NEWOnTbNew開くIDC_TB_OPENOnTbOpen保存IDC_TB_SAVEOnTbSave区切り(なし)-コマンド ID を使うか、コントロール ID を使うかの判断は、メニューと連動させるかどうかで変える。メニューがあるなら ID_FILE_NEW のようなコマンド ID を使って ON_COMMAND を共通化する。ダイアログだけで完結するなら IDC_TB_NEW のような専用 ID でよい。ただし、専用 ID でも ON_COMMAND は普通に使えるので、動作的な差はない。私の経験では、リソース上の ID と区別する意味で、最初から「TB」という接頭辞を付けた専用 ID にしておくと、どこで使われているかが分かりやすい。ビットマップのサイズは、デフォルトで 16x15 ピクセルが用意される。VS2019 でも同様。高解像度のアイコンを使いたい場合は 32x32 を新規に描くか、ビットマップのサイズを 16x15 から 16x16 に直す。サイズがずれるとボタンが変に余白を持つ、あるいは隣のボタンと重なる。ここはツールバーエディタのグリッドを確認しておきたい。もう一つ、気をつけるのは、リソースエディタ上でツールバーを追加したあとに、.rc ファイルを手で編集しないことだ。特に、ビットマップファイルのパスを直接書き換えると、LoadToolBar がリソースを読み込めず FALSE を返す。過去に、res フォルダのビットマップを差し替えた際、ファイル名が他の bmp と衝突して、意図しないツールバーが表示されたことがあった。リソースには、一意な ID とファイル名を割り当てるのが原則だ。3. ダイアログクラスにツールバーを組み込む最小コードとパラメータの意味3.1 ヘッダーへのメンバ追加とメッセージマップ宣言まず、ダイアログクラスのヘッダーに CToolBar メンバを追加する。// MyDlg.h #pragma once #include afxext.h // CToolBar の定義 class CMyDlg : public CDialog { DECLARE_DYNAMIC(CMyDlg) public: CMyDlg(CWnd* pParent nullptr); virtual ~CMyDlg(); enum { IDD IDD_MYDLG }; protected: CToolBar m_wndToolBar; virtual BOOL OnInitDialog(); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnTbNew(); afx_msg void OnUpdateTbNew(CCmdUI* pCmdUI); DECLARE_MESSAGE_MAP() };ここで困るのが、afxcontrolbars.h の有無。VC6 にはこのヘッダがない。CToolBar を単独で使うだけなら afxext.h のインクルードで足りる。Visual C のプロジェクトテンプレートは、MFC を使うと afxwin.h をインクルードするが、CToolBar の完全な定義は afxext.h にある。VS2008 以降の MFC Feature Pack 以降を入れると、afxcontrolbars.h が追加され、CMFCToolBar などの新クラスが定義される。ここでは旧来の CToolBar をそのまま使いたいので、afxext.h だけをインクルードしておけば、VC6 から VS2019 まで同じコードで通る。ヘッダーに追加するメンバ変数は、ダイアログのウィンドウプロシージャで使われる。CToolBar オブジェクトは、ダイアログが生きている間は有効なメンバでなければならない。OnInitDialog 内にローカル変数で作ると、その関数を抜けた瞬間にデストラクトされ、ツールバーが消える。必ずクラスのメンバ変数にすること。これはツールバーに限らず、ダイアログに貼る子ウィンドウの一般原則だ。3.2 OnInitDialog での Create と RepositionBars次に、OnInitDialog でツールバーを作成する。BOOL CMyDlg::OnInitDialog() { CDialog::OnInitDialog(); // ツールバー生成 if (!m_wndToolBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS | CBRS_FLYBY) || !m_wndToolBar.LoadToolBar(IDR_DLG_TOOLBAR)) { TRACE0(ツールバーの生成に失敗しました\n); EndDialog(IDCANCEL); return FALSE; } // クライアント領域をツールバーの高さ分だけ再配置 RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0); return TRUE; }Create の引数を詳しく見ておこう。第 1 引数は親ウィンドウ、第 2 引数はスタイル、第 3 引数はツールバー ID だ。WS_CHILD: ダイアログの子ウィンドウとして作成する。WS_VISIBLE: 作成後すぐ表示する。CBRS_TOP: ダイアログの上端に配置する。CBRS_TOOLTIPS: ボタンにツールチップを表示する。CBRS_FLYBY: ツールチップがマウスの移動に合わせて遅れて表示される「フライバイ」表示を有効にする。CBRS_TOOLTIPS と一緒に使う。LoadToolBar は、リソース ID を指定してツールバーデータを読み込む。この中でビットマップの読み込み、ボタンの分割、各ボタンへの ID 関連付けが行われる。LoadToolBar が FALSE を返すのは、リソース ID が存在しない、ビットマップの色深度がおかしい、ボタンの数とビットマップの領域が一致しないといった場合だ。RepositionBars は、指定範囲の ID を持つコントロールバーが今どこにいるかを見て、クライアント領域を再計算する。第 3 引数に 0 を渡すと、親ウィンドウのクライアント領域全体を調整対象にする。ダイアログでは、上部にツールバーが来て、その下にダイアログのコントロールが来るようにする。ここで OnSize を実装していないと、ダイアログのサイズを変えたときにツールバーは上端に張り付いたまま、下のコントロールと重なってしまう。リサイズ可能なダイアログなら、次のコードを忘れないこと。void CMyDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); if (m_wndToolBar.GetSafeHwnd()) RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0); }GetSafeHwnd は、まだツールバーが作られていない状態で OnSize が呼ばれたときの保護だ。OnInitDialog の前にも OnSize は呼ばれる可能性がある。これを入れておかないと、デバッグ実行時に突然クラッシュする。OnSize はウィンドウ生成直後にも呼ばれるので、必要になる。3.3 コマンド応答: ON_COMMAND と ON_UPDATE_COMMAND_UIツールバーのボタンが押されたときの処理は、メッセージマップに ON_COMMAND を追加する。BEGIN_MESSAGE_MAP(CMyDlg, CDialog) ON_WM_SIZE() ON_COMMAND(IDC_TB_NEW, CMyDlg::OnTbNew) ON_UPDATE_COMMAND_UI(IDC_TB_NEW, CMyDlg::OnUpdateTbNew) END_MESSAGE_MAP() void CMyDlg::OnTbNew() { AfxMessageBox(_T(新規作成)); } void CMyDlg::OnUpdateTbNew(CCmdUI* pCmdUI) { pCmdUI-Enable(TRUE); }ポイントは、ON_UPDATE_COMMAND_UI を追加することだ。MFC のツールバーは、各ボタンを表示する前に、そのボタンが有効かどうかを更新ハンドラで問い合わせる。ON_UPDATE_COMMAND_UI ハンドラが存在しない場合でも、デフォルトで有効になる。しかし、別の場所で同じ ID に対して Enable(FALSE) を呼んでいるコードがあると、ハンドラが追加されずに無効のまま固定されたりする。新しく ID を作ったら、使わなくても ON_UPDATE_COMMAND_UI を一応登録しておくのが安全だ。また、ダイアログベースのアプリでは、メッセージルーティングがフレームウィンドウと異なる。ツールバーのコマンドは、まずダイアログ本体に送られる。ダイアログがハンドラを持っていない場合、CWinApp にルーティングされる。なので、ハンドラを書く場所はダイアログクラスで正しい。CWinApp 側にハンドラを書いたのにダイアログに書いていない、というミスは初心者によくある。4. 避坑・常見問題ツールバーが消える、ボタンが灰になる、位置が崩れるこの章では、ダイアログにツールバーを追加したときに遭遇する典型的な 5 つの問題を挙げる。どれも「なんで?」と思う類のものだけど、原因はだいたい決まっている。4.1 ツールバーが一切表示されない現象: ダイアログを起動しても、ツールバーが見えない。原因: Create か LoadToolBar が FALSE を返している。OnInitDialog で EndDialog を呼んでいなくても、TRACE にもエラーが出ていないなら、WS_VISIBLE を付け忘れている可能性が高い。解決: Create のスタイルに WS_VISIBLE を確認する。それでもだめなら、LoadToolBar の引数 IDR_DLG_TOOLBAR がリソースに存在するか確認する。リソースビューで IDR_DLG_TOOLBAR を開けるかどうか、という簡単なチェックでよい。4.2 ツールバーは出るが、ボタンが真っ黒な四角になる現象: ボタンのアイコンではなく黒または塗りつぶしの矩形が表示される。原因: ビットマップが正しく読み込まれていない。特に、ツールバーエディタで描いたボタンとビットマップ領域がずれている。解決: リソースエディタでツールバーを開き、「ボタンの数」と「ビットマップの幅」が一致しているか確認する。ツールバーのボタンは 16x15 ピクセルなので、ボタン 5 個なら 80x15 ピクセルになる。ビットマップが 16x16 や 24x24 に変更されている場合、LoadToolBar は正常でも表示が乱れる。このときは、ビットマップをリソースエディタで削除して、ツールバーリソースを新規作成し直すのが早い。リソースファイルを直編集で直そうとすると、色深度やパレットの情報が壊れて、さらにハマる。4.3 ボタンがグレーアウトしてクリックできない現象: ボタンは表示されるが、灰色のまま押せない。原因: その ID に対する ON_COMMAND ハンドラがない。MFC は、コマンド ID のハンドラがどこにもない場合、ボタンを無効化して表示する。とくにダイアログでは、メニューもないしアクセラレータもないため、ハンドラがルーティングされる保証が弱い。解決: ダイアログクラスのメッセージマップに ON_COMMAND を追加する。さらに、ON_UPDATE_COMMAND_UI で pCmdUI-Enable(TRUE) を明示する。これでも灰色のままなら、BEGIN_MESSAGE_MAP の親クラスが CDialog になっているか確認する。ダイアログの派生クラスで BEGIN_MESSAGE_MAP を書く際に、基底クラスを CDialog ではなく CWnd にしてしまっているケースもある。4.4 ダイアログをリサイズするとツールバーの位置や幅が変になる現象: ダイアログを大きくすると、ツールバーが左端のまま、右側に空間が空く。または、ダイアログ内のコントロールと重なる。原因: OnSize で RepositionBars を呼んでいない。解決: 前述の OnSize ハンドラを追加する。さらに、ツールバーの幅を親ダイアログのクライアント幅に合わせる場合は、OnSize 内で次のようにサイズを調整してもよい。void CMyDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); if (m_wndToolBar.GetSafeHwnd()) { m_wndToolBar.MoveWindow(0, 0, cx, cy); RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0); } }MoveWindow でクライアント幅いっぱいに広げてから RepositionBars を呼ぶと、ツールバーの下にあるコントロールの位置が正しく再計算される。MoveWindow に与える x, y は親ウィンドウのクライアント座標基準なので、0, 0 から始めるのが正しい。4.5 高 DPI 環境でツールバーのアイコンがぼやける現象: 150% や 200% の表示スケールで、ツールバーのアイコンがぼやける。ボタンが大きくなるが、ビットマップが引き伸ばされている。原因: 従来の CToolBar は、システムの DPI 設定に応じたビットマップのスケーリングを自動では行わない。リソースのビットマップが 16x15 のままで、表示倍率だけが変わるとピクセル補間がかかり、ぼやける。解決: プロジェクトのマニフェストで DPI 認識を設定するのも手だが、ビットマップ自体を複数サイズ用意するのは面倒。Visual Studio 2010 以降なら、CMFCToolBar に切り替えるのが現実的だ。CMFCToolBar はビットマップを自動でスケーリングしてくれる。ただし、導入にはヘッダの追加や初期化の変更が発生する。プロトタイプ段階なら、まずは 32x32 ビットマップを使うと拡大時にある程度見た目が保たれやすい。5. ダイアログツールバーを実用レベルに仕上げるフラット化、表示切替、独自ビットマップ5.1 フラットツールバーとテーマ対応Visual C 6.0 の時代、デフォルトのツールバーは 3D の浮き上がり風だった。VS2019 の MFC アプリでも同じだが、モダンな UI にするならフラットスタイルに変えたい。// LoadToolBar のあとに追加 m_wndToolBar.GetToolBarCtrl().ModifyStyle(0, TBSTYLE_FLAT); m_wndToolBar.GetToolBarCtrl().ModifyStyle(0, TBSTYLE_TRANSPARENT);TBSTYLE_FLAT はボタンをフラットにし、マウスを乗せたときにだけ枠を表示する。TBSTYLE_TRANSPARENT はツールバーの背景を透明にする。ダイアログの背景色と合わせたい場合に使う。ただし、TBSTYLE_TRANSPARENT は親ウィンドウが独自の背景色を持つ場合、描画が崩れることがある。このときは、ダイアログの OnEraseBkgnd で背景色を塗っておくと安定する。もう一つ、コモンコントロール 6.0 を使うと、ツールバーが OS のテーマ描画に対応する。プロジェクトのプロパティで「依存ファイル」に comctl32.dll の manifest を入れる、あるいは MFC プロジェクトの場合は自動的に組み込まれる。テーマ対応しないと、フラット化してもボタンが XP 風のままというちぐはぐな見た目になる。VS2019 の MFC アプリでは標準でテーマ対応なので、気にしすぎなくてもよい。5.2 表示・非表示の切り替えとレイアウト再計算ダイアログのメニューやチェックボックスでツールバーの表示を切り替えるには、ShowWindow するだけでは不十分なことがある。ツールバーはコントロールバーとして RepositionBars に管理されているため、非表示にした直後に再配置を行わないと、その分のスペースが空いたままになる。void CMyDlg::OnToggleToolbar() { BOOL bVisible m_wndToolBar.IsWindowVisible(); m_wndToolBar.ShowWindow(bVisible ? SW_HIDE : SW_SHOW); RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0); }このとき、ShowWindow しただけでは、ツールバーが占めていたスペースは親ダイアログに返らない。RepositionBars を呼んで、クライアント領域を再計算する。逆に、表示に切り替えた場合も同様だ。これで、ツールバーの高さ分だけダイアログの本体領域が伸び縮みする。ここで注意したいのが、ダイアログのコントロールを配置する領域との兼ね合いだ。ツールバーの下にコントロールを置くなら、ダイアログエディタでは、ツールバーの高さ標準で約 30 ピクセルを見越してデザインする。OnInitDialog 直後に RepositionBars が呼ばれるため、エディタ上で上部に配置したコントロールが、実行時にツールバーの下にずれ込む。だから、ダイアログエディタでは最初から上部 30 ピクセルを空けておくか、コントロール配置を OnSize で動かす設計にしておく。5.3 独自ビットマップを使う場合の色深度と透過ツールバーのビットマップは、原則 4 ビット16色か 8 ビット256色の DIB が使われる。VC6 のときは 16 色で十分だったが、VS2019 で作ったツールバーは、リソースエディタが自動的に 24 ビットのビットマップを生成する。ここで重要なのが、背景色だ。ボタンのアイコンに背景色が写り込んでいる場合、LoadToolBar はビットマップの左上の 1 ピクセルの色を背景色として識別し、その色を透明化する。だから、アイコンを描くときは、背景を必ず「ツールバーの背景色と同じ色」にしておく。実際に筆者が初めてダイアログにツールバーを追加したとき、アイコンを青い背景で描いて、ボタンを浮かせてみたところ、青い四角がそのまま表示されてしまった。背景色を COLOR_3DFACE の標準グレーに塗り直すと、透明化された。この背景色のルールは、フォトショップやペイントでビットマップを直接編集するときに一番よく踏む落とし穴だ。6. zip でプロジェクトを配布するときの「暗黙の前提」リソース同梱とランタイムの話ソースコードを zip でまとめて渡すとき、Visual C のプロジェクトは単体では動かない。まず、MFC ダイアログアプリには、リソーススクリプト (.rc)、resource.h、res フォルダ内のビットマップなどが必要だ。ツールバーを追加したプロジェクトなら、res フォルダに toolbar1.bmp が入っている。これを zip に忘れると、相手の環境で IDR_DLG_TOOLBAR が見つからず、ビルドは通ってもツールバーだけ出ないという、再現性の低いバグになる。もう一つ無視できないのが、ランタイム DLL だ。リリースビルドの exe を配布する場合、MFC を動的リンクで使っていると、相手の PC に Microsoft Visual C Redistributable が入っていないと、起動直後に「MSVCP140.dll が見つからない」というエラーになる。Visual C のバージョンによって、VC6 なら MSVCRT.dll、VS2019 なら VCRUNTIME140.dll と vcruntime140_1.dll、そして MFC 用の mfc140u.dll が必要になる。配布 zip にこれらのランタイムを同梱するか、インストーラ用のブートストラップを同梱するのが普通だ。リリースビルドの設定として、プロジェクトのプロパティで「MFC の使用」を「静的ライブラリで MFC を使用する」にすれば、ランタイム DLL は不要になる。ただし、exe サイズが数 MB 増えるし、MFC のバージョンが古いとライセンス的にも面倒が出る。いまどきなら、動的リンクのまま、Redistributable インストーラへのリンクを zip 内の README に貼る、もしくは配布 zip 内に vc_redist.exe を入れておくのが実用的だ。私自身、ダイアログのツールバーを追加したプロジェクトを zip で送ったとき、ツールバーのボタンがグレーアウトしたまま動かないという連絡をもらった。原因は、私がリソース ID を resource.h で重複させていて、IDC_TB_NEW が別のコントロール ID と衝突していたことだった。リソースエディタ上では問題が見えず、リソースファイルを手で追ってようやく分かった。それ以来、リソース ID は一覧表にして、追加するたびにチェックするようにしている。ツールバーを足すのは 30 分の作業だが、この ID 管理を怠ると数日消耗する。この記事の手順をそのまま試せば、そういう事故は避けられるはずだ。希望帮到你。本文还有配套的精品资源点击获取
返回列表