iooiau.net

GetMenuState() の罠

GetMenuState はメニュー項目の種類や状態を取得する関数ですが、ある項目がセパレータ(区切り)であるかを判定しようとすると陥る罠があります。

ある位置のメニュー項目がセパレータであるか判定する関数を作ろうと素直に書くと、以下のようになるでしょう。


BOOL IsSeparator(HMENU hMenu, int Pos)
{
    return (GetMenuState(hMenu, Pos, MF_BYPOSITION) & MF_SEPARATOR) != 0;
}

しかし、このコードでは正しく判定できない場合があります。

メニュー項目がサブメニューの場合、GetMenuState は下位2バイト目にサブメニューの項目数を返します。
しかし MF_SEPARATOR は値が 0x00000800 であるため、このメニュー項目数と領域が被ってしまっているので、区切りでない項目が誤って区切りであると判定されてしまうことがあり得ます。

正しくは以下のようになります。


/* メニュー項目が区切りか判定 */
BOOL IsSeparator(HMENU hMenu, int Pos)
{
    return (GetMenuState(hMenu, Pos, MF_BYPOSITION) & (MF_SEPARATOR | MF_POPUP)) == MF_SEPARATOR;
}

MF_OWNERDRAW も 0x00000100 ですから、ある項目がオーナー描画項目かを判定したい場合も同じ問題があります。
しかも MF_POPUP と MF_OWNERDRAW は併用できますから、GetMenuState では判定が不可能ということになります。
代わりに GetMenuItemInfo を使いましょう。


/* メニュー項目がオーナー描画か判定 */
BOOL IsOwnerDraw(HMENU hMenu, int Pos)
{
    MENUITEMINFO mii;
    mii.cbSize = sizeof(MENUITEMINFO);
    mii.fMask = MIIM_FTYPE;
    return GetMenuItemInfo(hMenu, Pos, TRUE, &mii) && (mii.fType & MFT_OWNERDRAW) != 0;
}

サブメニューの項目数を返すという誰得機能がただ邪魔なだけですね。
なぜこのようなアホな仕様になってしまったのでしょうか…。