自分でシステムメニューを出す方法

解決


KOT  2013-08-12 21:04:02  No: 73779

MFCで、タイトルバーを持たないダイアログを表示させています。
OnNcHitTest()を用意し、HTCAPTIONを返すことで、
タイトルバーと同じようにダイアログをドラッグ移動できるようにしました。

このとき、ALT+SPACEを押すと、特になにもしなくても
タイトルバーがあるときと同じようにシステムメニューが表示されるのですが、
右クリックをしたときには表示されなくなってしまいました。

この場合、どのようにNC系のメッセージを処理すれば、
右クリックでシステムメニューが表示されるようになるのでしょうか?

bSysMenu=TRUEのOnInitMenuPopup()が呼ばれるようにしたいと思っています。
よろしくお願いいたします。


forty-five  2013-08-15 06:38:55  No: 73780

「システムメニュー 表示」でぐぐると出てきますが・・・例えばこのサイトの一番下。
http://eternalwindows.jp/winbase/menu/menu09.html


KOT  2013-08-19 18:55:29  No: 73781

if (nHitTest == HTCAPTION) {
        SendMessage(0x0313, 0, GetMessagePos());
        return;
    }

    CDialog::OnNcRButtonUp(nHitTest, point);

OnNcRButtonUp()を上のように書いたところ、
右クリックでシステムメニューが出てくるようになりました。
ありがとうございます。

ちなみになのですが、ALT+SPACEで自動で出てくるシステムメニューは、
タイトルバーが表示されている前提の位置(ちょっと下)に出てくるのですが、
これもうまく上部に合わせることは可能なのでしょうか。


forty-five  2013-08-20 05:44:03  No: 73782

WM_SYSCHAR をハンドルして 0x313 を使えば良いと思います。
アニメーションが若干違うことになそうですが、これはどうしようもないと思います。


KOT  2013-08-20 18:44:44  No: 73783

ヒントをいただきありがとうございます。
ダイアログクラスのOnSysChar()には来ないようですが、
PreTranslateMessage()に、

    if (pMsg->message == WM_SYSCHAR && pMsg->wParam == VK_SPACE) {
        CRect rect;
        GetClientRect(&rect);
        ClientToScreen(&rect);
        SendMessage(0x0313, 0, MAKELPARAM(rect.left, rect.top));
        return TRUE;
    }

上記のような処理を入れてみたところ、ALT+SPACEを押したときに、
クライアント領域の左上にシステムメニューを表示させることができました。

ただ、標準の処理と違って、メニューの先頭項目が初期選択状態にならないようです。
(IE10なども同じように初期選択されないので、大きな問題ではないかもしれませんが)

教えていただいたようにアニメーションの問題もあるようなので、
どこまでやっておきべきか、いろいろ吟味してみたいと思います。

ありがとうございます。


forty-five  2013-08-21 05:27:15  No: 73784

BOOL CNoCaptionDlg::PreTranslateMessage(MSG* pMsg) 
{
    if (pMsg->message == WM_SYSCHAR && pMsg->wParam == TEXT(' '))
    {
        TRACE("Alt+Space\n");

        CRect rc; GetWindowRect(&rc);
        CMenu* menu = GetSystemMenu(FALSE);

        //HiliteMenuItem(menu, 0, MF_BYPOSITION | MF_HILITE);
        menu->SetDefaultItem(SC_CLOSE);

        UINT id = menu->TrackPopupMenu(TPM_VERPOSANIMATION | TPM_NONOTIFY | TPM_RETURNCMD, rc.left, rc.top, this);

        PostMessage(WM_SYSCOMMAND, id, MAKELPARAM(rc.left, rc.top));

        return TRUE;
    }

    return CDialog::PreTranslateMessage(pMsg);
}

こうすればアニメーションは解決できそうです。
でも最初の項目をキーフォーカスを与えるのは難しいようですね。

あと気になったのは WM_SYSCHAR の wParam は TCHAR なので、
VK_SPACE と比較すると UNICODE の時うまく行かないような気がします。


forty-five  2013-08-21 05:30:00  No: 73785

あ、id が 0 の時は PostMessage したらダメですね。


KOT  2013-08-21 18:41:39  No: 73786

いろいろ調べていただきありがとうございます。
自前でTrackPopupMenu()を出して結果をWM_SYSCOMMANDで送る方法だと、
当初の「bSysMenu=TRUEのOnInitMenuPopup()」が来ないようなのです。

最初に教えていただいた0x0313の方法だと、ちゃんと来てくれました。

独自に追加したコマンドのチェック状態などをここで一括管理しているため、
できればこの仕組みを残せないかと思っています。

標準のシステムメニューがちゃんとWS_CAPTIONを考慮してくれればよかったのですが、
表示位置をちょっとだけ変えようとするだけで、
アニメーションや初期選択など、いろいろ異なる動きになってしまいますね。

ありがとうございます。


forty-five  2013-08-24 06:35:32  No: 73787

HWND g_wnd = 0;
HHOOK g_hook = 0;

LRESULT CALLBACK hookProc(int code, WPARAM wParam, LPARAM lParam)
{
    CWPSTRUCT* cwp = (CWPSTRUCT*)lParam;

    if (cwp->message == 0x000001E5) // メニューウィンドウに通知されるメッセージの一つ
    {
        // フックをアンインストールする
        ::UnhookWindowsHookEx(g_hook), g_hook = 0;

        // 基準となるウィンドウの座標を取得する
        RECT rc; ::GetWindowRect(g_wnd, &rc);

        // メニューウィンドウの位置を変更する
        ::SetWindowPos(
            cwp->hwnd,
            0,
            rc.left, rc.top,
            0, 0,
            SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

        // フックチェインが無効になっているので 0 を返す
        return 0;
    }

    return ::CallNextHookEx(g_hook, code, wParam, lParam);
}

BOOL CNoCaptionDlg::PreTranslateMessage(MSG* pMsg) 
{
    if (pMsg->message == WM_SYSCHAR)
    {
        if (pMsg->wParam == _T(' '))
        {
            TRACE("Alt+Space\n");

            // フックをインストールする
            g_wnd = GetSafeHwnd();
            g_hook = ::SetWindowsHookEx(
                WH_CALLWNDPROC, hookProc, 0, ::GetCurrentThreadId());

            BOOL retValue = CDialog::PreTranslateMessage(pMsg);

            if (g_hook)
            {
                // 念のためフックをアンインストールする
                ::UnhookWindowsHookEx(g_hook), g_hook = 0;
            }

            return retValue;
        }
    }

    return CDialog::PreTranslateMessage(pMsg);
}

無理矢理だけど、これでどうかな〜。
0x0313 もそうだけど、0x01E5 も非公式メッセージ。
一応 WinXP と Win7 64bit でちゃんと動作しているように見えました。
あと MFC なら AfxGetMainWnd() を使えば g_wnd は要らないかもです。


forty-five  2013-08-24 06:49:33  No: 73788

それと分かったことはキーからシステムメニューを出す時には
WM_SYSCOMMAND が使われているということです。こんな感じ↓

SendMessage(WM_SYSCOMMAND, SC_KEYMENU, VK_SPACE);

なので PreTranslateMessage() で処理するより
WM_SYSCOMMAND をハンドルした方が確実だと思われます。


KOT  2013-08-27 05:16:19  No: 73789

ありがとうございます。
自分のテストアプリでAlt+Spaceを押したときも、
bSysMenu=TRUEのOnInitMenuPopup()が来た上で
綺麗に左上に表示されました。

メニューそのものの座標をSetWindowPos()で動かせてしまえるのですね。
フック自体は全くの無知なので、隠しメッセージも含めて、
一度仕組みを勉強させていただきます。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加