ウィンドウズを初めて使ったとき、おそらく一番最初に「スタートボタン」を押したことでしょう。「スタートボタン」からツリー表示されていくメニューにはアイコンが表示されています。普通のメニューよりも、見た目が綺麗で、かつ分かりやすくなっています。
では、このメニュー、どうやって作製するのでしょうか。
今回は、この機能を実現する「オーナードロー」というシステムを見ていきます。メニューを追加する部分はメニューを動的に追加すると同じ方法で行います。必ず「メニューを動的に……」を読んでから、このページを読んでください。
では下準備をしましょう。まず、メニュー関係をコーディングするクラス(ここではフレームウィンドウクラス(ここ修正してください))の直前に、次のような構造体のコードを書き込んでください。 |
//////////////////////////////////
// ODMENUITEM構造体:AppendMenu等で渡す、メニューアイテムについての情報。
typedef struct _ODMENUITEM
{
HICON hMenuIcon; //メニューに貼り付けるアイコンのハンドル。
CString cTextStr; //メニューに書き込む文字。
} ODMENUITEM;
この構造体の配列をメンバ変数として追加します。ここではm_stMenuItem[32]とします。
次に、メニューを追加します。メニューを動的に追加するとほとんど同じコーディングをしてください(ってゆーか、このとき作ったプログラムを流用した方が早いかも)。 ただし、メニューアイテムを追加するAppendMenu()の部分だけは、少し違います。実際には |
if( pcThisMenu && pcThisMenu->GetMenuItemID( 0 ) == ID_ADDMENU )
{
// メニューアイテムの情報を格納します。
m_stMenuItem[0].cTextStr = "あうあう";
m_stMenuItem[0].hMenuIcon = ;AfxGetApp()->LoadIcon( IDR_MAINFRAME );
// メニューを追加します。
pcThisMenu->AppendMenu( MF_OWNERDRAW, ID_NEWMENU, (LPCTSTR)0 );
}
オーナードローを使うため、1番目の引数にはMF_OWNERDRAWを渡します。2番目は同じです。3番目は、通常は書き込む文字ですが、オーナードローでは32ビットの整数を渡します。ここで渡している「0」は、配列のインデックスです。
この32ビット整数というのは、言ってみればオーナードローメニューの整理番号です。メニュー描画を行うときに、この整数が渡されます。その整数を元に、今描画しようとしているアイテムがどのメニューアイテムなのかを調べるわけです。 ここではひとつだけしかアイテムを作りませんが、通常は複数作るでしょう。そういうときにはもちろん配列のインデックスを変えて追加していきます。そういうことを考えれば、単なる配列ではなくCArrayやCMapなどを使う方がいいでしょう。
さて、ここからが、実際にオーナードローメニューを描画する部分です。
どんなアイテムも、オーナードローと指定した場合にはWM_MEASUREITEMとWM_DRAWITEMが送られます。このメッセージを処理するため、フレームウィンドウクラスにこれらふたつのメッセージに関連づけされた関数をクラスウィザードを使って作製してください。CWndクラスのメンバ関数OnMeasureItem()とOnDrawItem()が作製されます。このふたつの関数の中に、オーナードローのための機能をインプリメントします。
<バグリポート>
上記のふたつのメッセージは、メニューのメッセージです。そのため、普通のメッセージとは少し違い、メニューを管理するウィンドウに送られてきます。
これは、プログラムテストの時にはフレームウィンドウで行っていたものを、このページを書いたときに「こっちの方が解りやすいかな」とテストもせずに書き換えたため起きたミスでした。本当に申し訳ありませんでした!!
最後に、このレポートをしていただいた甲斐様、ありがとうございました!!
(1998/6/23)
まず、この関数の中に、親クラスの同じメンバ関数を呼び出す部分があると思いますが、この部分を削除してください。この関数は、単に「そのアイテムのクラス」を探し出してそこにメッセージを配送するだけです。例えば、この場合ではCMenuのMeasureItem()メンバ関数が呼び出されます。今回は、これをされても困ってしまうので削除しましょう。
ここで、このふたつの関数の役割について説明します。
OnDrawItem()では、実際に描画する段階で呼び出されます。この「描画する段階」というのは何度となく訪れます。まずメニューを開いたときに(OnMeasureItem()のあとで)オーナードローメニューアイテムの数だけ呼び出されます。
では、実際にコーディングしてみましょう。 |
////////////////////////////////////////////////////////////////////
// CMainFrameのオーナードロー用メンバ関数。
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
lpMeasureItemStruct->itemWidth = 300; //横幅です。
lpMeasureItemStruct->itemHeight = 100; //高さです。
}
void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// 書き込むデバイスコンテキストを準備します。
CDC* pcMenuDC = CDC::FromHandle( lpDrawItemStruct->hDC );
CRect cItemRect( lpDrawItemStruct->rcItem ); //項目の領域です。
CString cStr( m_stMenuItem[lpDrawItemStruct->itemData].cTextStr ); //書き込む文字です。
pcMenuDC->TextOut( cItemRect.left, cItemRect.top, cStr ); //文字を描画します。
}
OnMeasureItem()で渡される構造体のポインタ、そのitemWidthにはメニューアイテムの横幅を、itemHeightには高さを格納して、関数を終了させます。こうすることで、ウィンドウズがこのサイズでメニューを作ってくれます。
OnDrawItem()で渡される構造体のポインタ、そのhDCには、そのメニューの表面に貼り付けられているデバイスコンテキストのハンドルが入っています。これを使ってメニューを描画します。さらに、構造体からアイテムにあたる四角形と、AppendMenu()で渡した配列のインデックスを取得して、これらを元に文字を描画します。四角形の右上角は、ほとんどの場合0ではありません。これはつまり、渡されるデバイスコンテキストがメニュー全体だということです。スタートメニューの上へと伸びる帯は、このことを利用して描画しているのでしょう。
ここまでが大まかな流れです。基本的にはこれだけあれば自分の好きなようにメニューを変えられます。 |
(C)KAB-studio 1997, 1998 ALL RIGHTS RESERVED. |