テクニカル ノート 14: カスタム コントロール
ここでは、MFC でサポートされているカスタム コントロールと自己描画コントロールについて説明します。 さらに、動的なサブクラス化について取り上げ、CWnd オブジェクトと HWND の関係について説明します。
MFC サンプル アプリケーション CTRLTEST によって、多くのカスタム コントロールの使用方法を学習できます。 MFC の「標準のサンプル」の CTRLTEST のソース コードとオンライン ドキュメントを参照してください。
オーナー描画コントロール/メニュー
Windows では、Windows メッセージを使用したオーナー描画のコントロールとメニューがサポートされています。 コントロールまたはメニューの親ウィンドウは、これらのメッセージを受け取り、応答として関数を呼び出します。 これらの関数をオーバーライドして、オーナー描画のコントロールまたはメニューの外観と動作をカスタマイズできます。
MFC は、次の関数によるオーナー描画を直接サポートしています。
CWnd 派生クラスにあるこれらの関数をオーバーライドして、カスタム描画動作を実装できます。
しかし、この方法ではコードを再利用できません。 よく似た 2 つのコントロールが別々の CWnd クラスにある場合は、両方の場所でカスタム コントロール動作を実装する必要があります。 MFC がサポートしている自己描画コントロール アーキテクチャを使うと、この問題を解決できます。
自己描画コントロールとメニュー
MFC では、標準のオーナー描画メッセージの既定の動作が CWnd クラスと CMenu クラスに用意されています。 この既定の動作では、オーナー描画パラメーターをデコードし、オーナー描画メッセージをコントロールまたはメニューに渡します。 この方法が自己描画と呼ばれるのは、描画用のコードがコントロールまたはメニューのクラスにあり、オーナー ウィンドウにはないからです。
自己描画コントロールを使用すると、オーナー描画セマンティックスによってコントロールを表示する再利用可能コントロール クラスを作成できます。 コントロールの描画用コードは、コントロールの親ではなく、そのコントロールのクラスに属します。 これは、オブジェクト指向的なカスタム コントロール プログラミングです。 以下に挙げる関数を自己描画クラスに追加してください。
自己描画ボタン
CButton:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw this button
自己描画メニュー
CMenu:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this menu CMenu:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this menu
自己描画リスト ボックス
CListBox:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this list box CListBox:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this list box CListBox:CompareItem(LPCOMPAREITEMSTRUCT); // insert code to compare two items in this list box if LBS_SORT CListBox:DeleteItem(LPDELETEITEMSTRUCT); // insert code to delete an item from this list box
自己描画コンボ ボックス
CComboBox:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this combo box CComboBox:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this combo box CComboBox:CompareItem(LPCOMPAREITEMSTRUCT); // insert code to compare two items in this combo box if CBS_SORT CComboBox:DeleteItem(LPDELETEITEMSTRUCT); // insert code to delete an item from this combo box
オーナー描画構造体 (DRAWITEMSTRUCT、MEASUREITEMSTRUCT、COMPAREITEMSTRUCT、DELETEITEMSTRUCT) の詳細については、MFC のドキュメントで CWnd::OnDrawItem、CWnd::OnMeasureItem、CWnd::OnCompareItem、CWnd::OnDeleteItem を参照してください。
自己描画のコントロールおよびメニューの使用
自己描画メニューの場合は、メソッド OnMeasureItem と OnDrawItem の両方をオーバーライドします。
自己描画のリスト ボックスとコンボ ボックスの場合は、OnMeasureItem と OnDrawItem をオーバーライドします。 ダイアログ テンプレートで、リスト ボックスの場合は LBS_OWNERDRAWVARIABLE スタイルを指定し、コンボ ボックスの場合は CBS_OWNERDRAWVARIABLE スタイルを指定します。 OWNERDRAWFIXED スタイルを自己描画アイテムに使用できないのは、アイテムの固定高を決めてから、自己描画コントロールをリスト ボックスに結び付けるためです。 この制限に対処するには、CListBox::SetItemHeight メソッドと CComboBox::SetItemHeight メソッドを使用します。
OWNERDRAWVARIABLE スタイルに切り替えると、コントロールに NOINTEGRALHEIGHT スタイルが強制的に適用されます。 コントロールは可変サイズ アイテムの高さの合計を計算できないので、INTEGRALHEIGHT の既定のスタイルは無視され、コントロールは必ず NOINTEGRALHEIGHT になります。 固定高のアイテムの場合は、コントロールのサイズをアイテム サイズの整数倍と指定しておけば、アイテムが一部しか描画されないということは起こりません。
LBS_SORT スタイルまたは CBS_SORT スタイルの自己描画リスト ボックスとコンボ ボックスの場合は、OnCompareItem メソッドをオーバーライドします。
自己描画リスト ボックスとコンボ ボックスの場合は通常、OnDeleteItem をオーバーライドしません。 ただし、特別な処理を実行する場合は、OnDeleteItem をオーバーライドできます。 たとえば、それぞれのリスト ボックス アイテムやコンボ ボックス アイテムで追加のメモリや他のリソースを格納する場合などが考えられます。
自己描画のコントロールおよびメニューの例
MFC の「標準のサンプル」の CTRLTEST には、自己描画メニューと自己描画リスト ボックスのサンプルが用意されています。
自己描画ボタンの最も典型的な例は、ビットマップ ボタンです。 ビットマップ ボタンとは、それぞれの状態に応じて、1 つ、2 つ、または 3 つのビットマップ イメージを表示するボタンです。 その例が MFC クラス CBitmapButton にあります。
動的なサブクラス化
場合によっては、既存のオブジェクトの機能を変更できると便利です。 これまでに取り上げた例では、コントロールを作成する前にカスタマイズする必要がありました。 動的なサブクラス化という機能を使用すると、作成済みのコントロールをカスタマイズできます。
サブクラス化とは Windows 用語であり、ウィンドウの WndProc をカスタマイズされた WndProc に置き換え、既定の機能については WndProc を呼び出すことです。
この機能は、C++ のクラス派生とは違います。 C++ の用語である基本クラスと派生クラスは、Windows オブジェクト モデルのスーパークラスとサブクラスに似ています。 MFC における C++ の派生と Windows のサブクラス化は機能的に似ていますが、C++ では動的なサブクラス化をサポートしていません。
CWnd クラスによって、C++ のオブジェクト (CWnd からの派生) と Windows のウィンドウ オブジェクト (つまり HWND) 間が接続されます。
CWnd と HWND の間は通常以下の 3 とおりの関係があります。
CWnd が HWND を作成します。 CWnd から派生したクラスを作成することによって、派生クラスの動作を変更できます。 アプリケーションから CWnd::Create を呼び出すと、HWND が作成されます。
アプリケーションは、CWnd を既存の HWND に結び付けます。 既存ウィンドウの動作は変わりません。 これは処理の代行の一例であり、この場合は、CWnd::Attach を呼び出して既存の HWND を CWnd オブジェクトのエイリアスにします。
CWnd を既存の HWND に結び付けると、派生クラスの動作を変更できます。 この手法が動的なサブクラス化と呼ばれるのは、実行時に Windows オブジェクトの動作とクラスを変更するためです。
動的なサブクラス化を実装するには、CWnd::SubclassWindow メソッドとCWnd::SubclassDlgItem メソッドを使用します。
この 2 つのルーチンは CWnd オブジェクトを既存の HWND に結び付けます。 SubclassWindow は HWND を直接受け取ります。 SubclassDlgItem はコントロール ID と親ウィンドウを使用するヘルパー関数です。 SubclassDlgItem は、C++ オブジェクトをダイアログ テンプレートから作成したダイアログ コントロールに結び付けます。
SubclassWindow と SubclassDlgItem の使用例については、サンプルの「CTRLTEST サンプル : カスタム コントロールの実装」を参照してください。