TrackPopupMenu、TPM_NONOTIFY

TrackPopupMenu (MSDN) の TPM_NONOTIFY フラグの説明には、「ユーザーが 1 つのメニュー項目をクリックしたとき、この関数は通知メッセージを送信しません。」とありますが、挙動が変だったので実験してみました。

※ コンパイルオプションの例: cl /EHsc 1.cpp

#include <Windows.h>
#include <Tchar.h>
#include <iostream>

#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Gdi32.lib")

using namespace std;
static int mode = 0;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static HMENU menu = NULL;
  static bool onTrackPopupMenu = false;

  if (onTrackPopupMenu && msg != WM_ENTERIDLE)
    cout << "msg(hex): " << hex << msg << endl;

  BOOL ok;
  switch (msg) 
  {
  case WM_COMMAND:
    cout << "WM_COMMAND: " << LOWORD(wParam) << endl;
    break;
  case WM_CREATE:
    menu = CreatePopupMenu();
    if (!menu)
      cerr << "CreatePopupMenu failed" << endl;
    {
      MENUITEMINFO item;
      ZeroMemory(&item, sizeof(item));
      item.cbSize = sizeof(item);
      item.fMask = MIIM_FTYPE|MIIM_ID|MIIM_STRING;
      item.fType = MFT_STRING;
      item.wID = 1;
      item.dwTypeData = (LPTSTR)_T("TEST");
      ok = InsertMenuItem(menu, 0, TRUE, &item);
      if (!ok)
        cerr << "InsertMenuItem failed" << endl;
    }
    break;
  case WM_DESTROY:
    DestroyMenu(menu);
    PostQuitMessage(0);
    break;
  case WM_RBUTTONDOWN:
    {
      UINT flags = TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON;
      switch (mode)
      {
      case 0:
        cout << "[START] NONE" << endl;
        break;
      case 1:
        flags |= TPM_RETURNCMD;
        cout << "[START] TPM_RETURNCMD" << endl;
        break;
      case 2:
        flags |= TPM_NONOTIFY;
        cout << "[START] TPM_NONOTIFY" << endl;
        break;
      case 3:
        flags |= TPM_RETURNCMD;
        flags |= TPM_NONOTIFY;
        cout << "[START] TPM_RETURNCMD|TPM_NONOTIFY" << endl;
        break;
      }

      DWORD posW = GetMessagePos();
      int x = (int)LOWORD(posW);
      int y = (int)HIWORD(posW);

      onTrackPopupMenu = true;
      ok = TrackPopupMenu(menu, flags, x, y, 0, hWnd, NULL);
      onTrackPopupMenu = false;

      if (!ok)
        cerr << "TrackPopupMenu failed: " << GetLastError() << endl;
    }
    break;
  default:
    return DefWindowProc(hWnd, msg, wParam, lParam);
  }

  return 0;
}

void main(int argc, char *argv[])
{
  if (argc > 1)
    mode = (int)(argv[1][0] - '0');

  HINSTANCE hInst = GetModuleHandle(NULL);
  LPCTSTR className = _T("SssTestClass");

  WNDCLASSEX wc;
  ZeroMemory(&wc, sizeof(wc));
  wc.cbSize = sizeof(WNDCLASSEX);
  wc.lpfnWndProc = WndProc;
  wc.hInstance = hInst;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszClassName = className;

  if (!RegisterClassEx(&wc)) 
  {
    cerr << "RegisterClass failed." << endl;
    return;
  }

  HWND hwnd = CreateWindow(
    className, _T("TrackPopupMenu Test"), WS_OVERLAPPEDWINDOW, 
    0, 0, 640, 480, NULL, NULL, hInst, NULL
  );
  if (!hwnd)
  {
    cerr << "CreateWindow failed." << endl;
    return;
  }

  ShowWindow(hwnd, SW_SHOWNORMAL);

  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0) > 0)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

出力結果

[START] NONE
msg(hex): 211
msg(hex): 20
msg(hex): 116
msg(hex): 117
msg(hex): 93
msg(hex): 94
msg(hex): 11f
msg(hex): 215
msg(hex): 125
msg(hex): 11f
msg(hex): 212
msg(hex): 84
WM_COMMAND: 1

上の出力結果は、コマンドラインオプション無しで実行するか、オプションとして "0" を指定して実行したときのものです。実験方法は、実行するとウィンドウが表示されるので、右クリックメニューから「TEST」を選択した後に×ボタンで閉じます。

この結果から、フラグに TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON を指定した場合、TrackPopupMenu 呼び出し中に、以下のメッセージが発生したことがわかります。

※ 実は、WM_ENTERIDLE も何度も発生しますが、重要じゃないので、プログラムで出力をスキップしてます。WndProc 内の 4 行目参照。

当然ながら、WM_COMMAND メッセージも発生することがわかります

※ WM_COMMAND の処理が走るのは、TrackPopupMenu の呼び出し後です。おそらく、TrackPopupMenu の中で、PostMessage されてるんでしょうね。

出力結果

[START] TPM_RETURNCMD
msg(hex): 211
msg(hex): 20
msg(hex): 116
msg(hex): 117
msg(hex): 93
msg(hex): 94
msg(hex): 11f
msg(hex): 215
msg(hex): 125
msg(hex): 11f
msg(hex): 212
msg(hex): 84

上の出力結果は、コマンドラインオプションとして "1" を指定して実行したときのものです。実験方法は、同じで、メニューで選択して終了します。TPM_RETURNCMD を指定した場合、WM_COMMAND メッセージが発生しなくなることがわかります。

TPM_NONOTIFY フラグの説明には、「ユーザーが 1 つのメニュー項目をクリックしたとき、この関数は通知メッセージを送信しません。」とあるので、TPM_RETURNCMD と同時に指定すべきフラグのように感じますが、この実験によって、その必要が無いことがわかります。WM_COMMAND が発生しないからです。

出力結果

[START] TPM_NONOTIFY
msg(hex): 20
msg(hex): 93
msg(hex): 94
msg(hex): 11f
msg(hex): 215
msg(hex): 11f
msg(hex): 84
WM_COMMAND: 1

上の出力結果は、コマンドラインオプションとして "2" を指定して実行したときのものです。実験方法は、同じで、メニューで選択して終了します。TPM_NONOTIFY を指定した場合、以下のメッセージが発生しなくなることがわかります。

奇妙なことに、 WM_MENUSELECT (0x11f) は発生したままです。また、WM_COMMAND メッセージも発生しています。この結果から、「ユーザーが 1 つのメニュー項目をクリックしたとき、この関数は通知メッセージを送信しません。」の説明はかなり誤解をまねく表現だということがわかります。「ユーザーがメニュー項目をクリックしたときに発生する "副次的な" 通知メッセージを送信しません。」とかだったら、少しはよかったかもしれませんね。

出力結果

[START] TPM_RETURNCMD|TPM_NONOTIFY
msg(hex): 20
msg(hex): 93
msg(hex): 94
msg(hex): 11f
msg(hex): 215
msg(hex): 11f
msg(hex): 84

上の出力結果は、コマンドラインオプションとして "3" を指定して実行したときのものです。実験方法は、同じで、メニューで選択して終了します。TPM_NONTOFY に関係なく、TPM_RETURNCMD を指定すると、WM_COMMAND が発生しなくなることがわかりますが、それは既にわかっていることなので、蛇足でしたね。

となりのページ

このサイトについて

このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。

連絡先

ここをクリック すると表示されるページから作者へメールで連絡できます。

共有