Programming Field - プログラミング Tips

WM_MEASUREITEMとリスト・コンボボックス

最近プログラムをしていて、リストボックスやコンボボックスをオーナードローしようと思い、WM_MEASUREITEMを実装したものの躓いた部分があったので、その内容を記します。

リストボックス・コンボボックスのオーナードローでは、LBS_OWNERDRAWFIXED(CBS_OWNERDRAWFIXED)スタイルかLBS_OWNERDRAWVARIABLE(CBS_OWNERDRAWVARIABLE; 以下LBS_のみ表記)スタイルを指定することでオーナードローを行うことができます。その際、親ウィンドウにはアイテムの高さを計算するための「WM_MEASUREITEM」メッセージ(英語MSDN)と、実際にアイテムの描画を行う「WM_DRAWITEM」メッセージ(英語MSDN)が送られますが、WM_MEASUREITEMが送られてくるタイミングに注意する必要があります。

どのタイミングでWM_MEASUREITEMメッセージが送られるかは、オーナードローで指定したスタイルによって異なります。

つまり、LBS_OWNERDRAWFIXEDのオーナードローの場合は以下のようなコードが失敗に終わります(正しく計算が行えません)。

HFONT hFontMain;

LRESULT CALLBACK MainWndProc(HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam)
{
    HWND hWndCtrl;
    LPMEASUREITEMSTRUCT lpmis;
    HDC hDC;
    HGDIOBJ hgdiobj;
    TEXTMETRIC tm;

    switch (message)
    {
        case WM_CREATE:

            .
            .
            .

            hWndCtrl = CreateWindowEx(0L, _T("ListBox"), NULL,
                WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL |
                LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED,
                10, 10, 100, 100, hWnd,
                (HMENU) UIntToPtr(1001), NULL, NULL);
            SendMessage(hWndCtrl, WM_SETFONT, (WPARAM) hFontMain, 0);

            .
            .
            .

        case WM_MEASUREITEM:
            lpmis = (LPMEASUREITEMSTRUCT) lParam);
            hWndCtrl = GetDlgItem(hWnd, lpmis->CtlID);
            hDC = GetDC(hWndCtrl);
            hgdiobj = SelectObject(hDC, (HGDIOBJ)
                SendMessage(hWndCtrl, WM_GETFONT, 0, 0));
            GetTextMetrics(hDC, &tm);
            SelectObject(hDC, hgdiobj);
            ReleaseDC(hWndCtrl, hDC);
            lpmis->itemHeight = tm.tmHeight;
            return 0;

            .
            .
            .

    }
}

下線を引いたところに注目すると、「WM_MEASUREITEMはコントロールが作成されたとき」に受け取るので、WM_MEASUREITEMを処理中にWM_GETFONTを呼び出すと、WM_SETFONTが呼び出される前に到達していることになり、意図したテキストの高さを得られなくなります。

上記のようなプログラムでは『WM_GETFONTを使わずに「hFontMain」を使えばいい』ということで解決しますが、これがダイアログボックスである場合、やはりコントロールからフォントを取得しようとすると失敗する(WM_INITDIALOGが呼び出される前にWM_MEASUREITEMが呼び出される)ので、ダイアログボックス自身からフォントを取得するかグローバル変数からフォントを取得するなどして、この問題を回避する必要があります。

なお、多言語アプリケーションなど実行時にフォントを変更できるアプリケーションでは、LBS_OWNERDRAWFIXEDを用いと、フォントの変更により高さが変わってもリストボックスの高さを再計算することができないので、その場合はLBS_OWNERDRAWVARIABLEを用い、リストボックスの項目を一度すべて削除してから再度追加し直す必要があります。(リストビューコントロールでは再計算させるようなアルゴリズムがありますが、リストボックス・コンボボックスで同じ手法は使えません。)

最終更新日: 2009/02/25