菜单

创建菜单
HMENU CreatePopupMenu(VOID); //弹出菜单
HMENU CreateMenu(VOID);  //普通菜单
以上两函数是WINODWS创建菜单的惟一API,它们会产生一个空项的菜单句柄.在Delphi中请查看Menu单元TMenuItem.GetHandle.
可以通过InsertMenu或AppendMenu插入或添加菜单项,也可以通过InsertMenuItem.在98后都用InsertMenuItem函数,它是InsertMenu升级版.Delphi也是如此(请参看Menu单元的TMenuItem.AppendTo).

CreatePopupMenu和CreateMenu路径:user32.dll中调用 --> NtUserCallNoParam无参数存根函数,进入系统调用(内核模式) --> 进入win32k.sys中的UserCreateMenu函数,此函数创建一个菜单对象然后原路返回到Ring3中.

HMENU FASTCALL UserCreateMenu(BOOL PopupMenu)
{
   PWINSTATION_OBJECT WinStaObject;   //窗体站对象,在多线程里如果系统窗体站不是当前线程,那么该线程将不能操作界面.
   HANDLE Handle;
   PMENU_OBJECT Menu;
   NTSTATUS Status;
   PEPROCESS CurrentProcess = PsGetCurrentProcess();

   if (CsrProcess != CurrentProcess)     //不是子系统进程,0号进程,它不能拥有窗体站,因为winlogon还要靠它启动.
   {
      /*
       * CsrProcess does not have a Win32WindowStation
       *
       */
       //验证当前是否拥有窗体站
      Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
                     KernelMode,
                     0,
                     &WinStaObject);

       if (!NT_SUCCESS(Status))
       {
          DPRINT1("Validation of window station handle (0x%X) failed\n",
             CurrentProcess->Win32WindowStation);
          SetLastNtError(Status);
          return (HMENU)0;
       }
       Menu = IntCreateMenu(&Handle, !PopupMenu);
       ObDereferenceObject(WinStaObject);
   }
   else
   {
       Menu = IntCreateMenu(&Handle, !PopupMenu);
   }

   if (Menu) UserDereferenceObject(Menu);
   return (HMENU)Handle;
}

真正的创建函数:
PMENU_OBJECT FASTCALL
IntCreateMenu(PHANDLE Handle, BOOL IsMenuBar)
{
   PMENU_OBJECT Menu;          //菜单对象
   PW32PROCESS CurrentWin32Process;          //注意这里并不是EPROCESS,请参看以前的"进程与线程"

   Menu = (PMENU_OBJECT)UserCreateObject(      //创建用户对象,通用函数
             gHandleTable, Handle,
             otMenu, sizeof(MENU_OBJECT));

   if(!Menu)
   {
      *Handle = 0;
      return NULL;
   }

   Menu->Process = PsGetCurrentProcess();   //以下设置对象各个属性
   Menu->RtoL = FALSE; /* default */           //????
   Menu->MenuInfo.cbSize = sizeof(MENUINFO); /* not used */
   Menu->MenuInfo.fMask = 0; /* not used */
   Menu->MenuInfo.dwStyle = 0; /* FIXME */
   Menu->MenuInfo.cyMax = 0; /* default */
   Menu->MenuInfo.hbrBack = NULL; /* no brush */
   Menu->MenuInfo.dwContextHelpID = 0; /* default */
   Menu->MenuInfo.dwMenuData = 0; /* default */   //在Delphi里这里是填充Caption
   Menu->MenuInfo.Self = *Handle;                 //菜单对象句柄
   Menu->MenuInfo.FocusedItem = NO_SELECTED_ITEM;   //默认无选中
   Menu->MenuInfo.Flags = (IsMenuBar ? 0 : MF_POPUP);   //类型:普通或弹出菜单
   Menu->MenuInfo.Wnd = NULL;                 //????  
   Menu->MenuInfo.WndOwner = NULL;          //拥有者窗体句柄,在Delphi可以看下Menu单元的popupList对象
   Menu->MenuInfo.Height = 0;               
   Menu->MenuInfo.Width = 0;
   Menu->MenuInfo.TimeToHide = FALSE;         //停留时间

   Menu->MenuInfo.MenuItemCount = 0;      //0个子节点,菜单本身是一棵树
   Menu->MenuItemList = NULL;                       

   /* Insert menu item into process menu handle list */
   CurrentWin32Process = PsGetCurrentProcessWin32Process();
   InsertTailList(&CurrentWin32Process->MenuListHead, &Menu->ListEntry);   //将菜单连接到进程MenuListHead里去.

   return Menu;
}

用户对象创建函数:四个参数,第一个句柄表,第二个输出句柄值,第三个输入对象类型(用户的),第四个大小
PVOID FASTCALL
UserCreateObject(PUSER_HANDLE_TABLE ht, HANDLE* h,USER_OBJECT_TYPE type , ULONG size)
{

   HANDLE hi;   //可以看到,上个版本是直接使用ExAlloctePool函数分配可分页内存,这里使用的是UserHeapAlloc.注意这里还有USER_OBJECT_HEADER
   PUSER_OBJECT_HEADER hdr = UserHeapAlloc(size + sizeof(USER_OBJECT_HEADER));//ExAllocatePool(PagedPool, size + sizeof(USER_OBJECT_HEADER));
   if (!hdr)
      return NULL;


   hi = UserAllocHandle(ht, USER_HEADER_TO_BODY(hdr), type );  //分配句柄,以前说过,那次是内核对象分配,下次再继续
   if (!hi)
   {
      //ExFreePool(hdr);
       UserHeapFree(hdr);
      return NULL;
   }

   RtlZeroMemory(hdr, size + sizeof(USER_OBJECT_HEADER));
   hdr->hSelf = hi;
   hdr->RefCount = 2; // we need this, because we create 2 refs: handle and pointer!

   if (h)
      *h = hi;
   return USER_HEADER_TO_BODY(hdr);  //从对象头直接通过指针转换到body
}

用户对象头:
typedef struct _USER_OBJECT_HEADER
/*
 * Header for user object
 */
{
//  USER_OBJECT_TYPE Type;
  LONG RefCount;   //引用计数
  BOOL destroyed;  //释放方法
  HANDLE hSelf;    //句柄
//  CSHORT Size;
} USER_OBJECT_HEADER, *PUSER_OBJECT_HEADER;


用户对象类型:
typedef enum _USER_OBJECT_TYPE
{
  otFree = 0,              //?//
  otWindow,                //窗口
  otMenu,                    //菜单
  otCursorIcon,            //光标
  otDWP,            //???
  otHook,            //勾子
  otCallProc = 7,        //回调
  otAccel,            //快捷键
  otMonitor = 12,        //???   
  otEvent = 15,            //???    //按理应该在内核对象里,请查看以前一篇关于"内核对象"
  otTimer            //???

} USER_OBJECT_TYPE;

再看具体菜单对象:
typedef struct _MENU_ITEM  //菜单项结构
{
  struct _MENU_ITEM *Next;
  UINT fType;
  UINT fState;
  UINT wID;
  HMENU hSubMenu;
  HBITMAP hbmpChecked;
  HBITMAP hbmpUnchecked;
  ULONG_PTR dwItemData;
  UNICODE_STRING Text;
  HBITMAP hbmpItem;
  RECTL Rect;
  UINT XTab;
} MENU_ITEM, *PMENU_ITEM;

typedef struct tagROSMENUINFO  //菜单信息
{
    /* ----------- MENUINFO ----------- */
    DWORD cbSize;
    DWORD fMask;
    DWORD dwStyle;
    UINT cyMax;
    HBRUSH  hbrBack;
    DWORD dwContextHelpID;
    ULONG_PTR dwMenuData;
    /* ----------- Extra ----------- */
    HMENU Self;         /* Handle of this menu */
    WORD Flags;         /* Menu flags (MF_POPUP, MF_SYSMENU) */
    UINT FocusedItem;   /* Currently focused item */
    UINT MenuItemCount; /* Number of items in the menu */
    HWND Wnd;           /* Window containing the menu */
    WORD Width;         /* Width of the whole menu */
    WORD Height;        /* Height of the whole menu */
    HWND WndOwner;     /* window receiving the messages for ownerdraw */
    BOOL TimeToHide;   /* Request hiding when receiving a second click in the top-level menu item */
    SIZE maxBmpSize;   /* Maximum size of the bitmap items in MIIM_BITMAP state */
} ROSMENUINFO, *PROSMENUINFO;

typedef struct _MENU_OBJECT  //菜单用户对象
{
  PEPROCESS Process;
  LIST_ENTRY ListEntry;
  PMENU_ITEM MenuItemList;         //菜单项
  ROSMENUINFO MenuInfo;
  BOOL RtoL;
} MENU_OBJECT, *PMENU_OBJECT;


弹出菜单显示
USER32中函数如下:
BOOL TrackPopupMenu(
  HMENU hMenu,         // handle to shortcut menu
  UINT uFlags,         // options
  int x,               // horizontal position
  int y,               // vertical position
  int nReserved,       // reserved, must be zero
  HWND hWnd,           // handle to owner window
  CONST RECT *prcRect  // ignored
);


//弹出菜单
BOOL
WINAPI
TrackPopupMenu(
  HMENU Menu,
  UINT Flags,
  int x,
  int y,
  int Reserved,
  HWND Wnd,
  CONST RECT *Rect)
{
  BOOL ret = FALSE;

  MenuInitTracking(Wnd, Menu, TRUE, Flags);  //进入时候初始化

  /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
  if (0 == (Flags & TPM_NONOTIFY))   //当有Flags有TPM_NONOTIFY无通知
    {
      SendMessageW(Wnd, WM_INITMENUPOPUP, (WPARAM) Menu, 0);
    }

  if (MenuShowPopup(Wnd, Menu, 0, x, y, 0, 0 ))           //创建一个窗体,拥有者是wnd
    {
      ret = MenuTrackMenu(Menu, Flags | TPM_POPUPMENU, 0, 0, Wnd, Rect);  //进入窗口消息循环
    }
  MenuExitTracking(Wnd); //离开时候恢复

  return ret;
}


//创建弹出菜单的窗口函数
static BOOL FASTCALL
MenuShowPopup(HWND WndOwner, HMENU Menu, UINT Id,
              INT X, INT Y, INT XAnchor, INT YAnchor )
{
  ROSMENUINFO MenuInfo;
  ROSMENUITEMINFO ItemInfo;
  UINT Width, Height;

  TRACE("owner=%x hmenu=%x id=0x%04x x=0x%04x y=0x%04x xa=0x%04x ya=0x%04x\n",
         WndOwner, Menu, Id, X, Y, XAnchor, YAnchor);

  if (! MenuGetRosMenuInfo(&MenuInfo, Menu))  //取得菜单信息,查看前面的ROSMENUINFO
    {
      return FALSE;
    }

  if (NO_SELECTED_ITEM != MenuInfo.FocusedItem)  //判断有无选中的
    {
      MenuInitRosMenuItemInfo(&ItemInfo);
      if (MenuGetRosMenuItemInfo(MenuInfo.Self, MenuInfo.FocusedItem, &ItemInfo))
        {
          ItemInfo.fMask |= MIIM_STATE;
          ItemInfo.fState &= ~(MF_HILITE|MF_MOUSESELECT);
          MenuSetRosMenuItemInfo(MenuInfo.Self, MenuInfo.FocusedItem, &ItemInfo);
        }
      MenuCleanupRosMenuItemInfo(&ItemInfo);
      MenuInfo.FocusedItem = NO_SELECTED_ITEM;
    }

  /* store the owner for DrawItem */
  MenuInfo.WndOwner = WndOwner;   //拥有者窗口,
  MenuSetRosMenuInfo(&MenuInfo);

  MenuPopupMenuCalcSize(&MenuInfo, WndOwner);  //计算大小

  /* adjust popup menu pos so that it fits within the desktop *///防止菜单超出桌面

  Width = MenuInfo.Width + GetSystemMetrics(SM_CXBORDER);
  Height = MenuInfo.Height + GetSystemMetrics(SM_CYBORDER);

  if (GetSystemMetrics(SM_CXSCREEN ) < X + Width)
    {
      if (0 != XAnchor && X >= Width - XAnchor)
        {
          X -= Width - XAnchor;
        }
      if (GetSystemMetrics(SM_CXSCREEN) < X + Width)
        {
          X = GetSystemMetrics(SM_CXSCREEN) - Width;
        }
    }
  if (X < 0 )
    {
      X = 0;
    }

  if (GetSystemMetrics(SM_CYSCREEN) < Y + Height)
    {
      if (0 != YAnchor && Y >= Height + YAnchor)
        {
          Y -= Height + YAnchor;
        }
      if (GetSystemMetrics(SM_CYSCREEN) < Y + Height)
        {
          Y = GetSystemMetrics(SM_CYSCREEN) - Height;
        }
    }
  if (Y < 0 )
    {
      Y = 0;
    }


  /* NOTE: In Windows, top menu popup is not owned. */     //注意这里的classname = POPUPMENU_CLASS_ATOMW = 32768(16位)
  MenuInfo.Wnd = CreateWindowExW(0, POPUPMENU_CLASS_ATOMW, NULL,
                                 WS_POPUP, X, Y, Width, Height,
                                 WndOwner, 0, (HINSTANCE) GetWindowLongPtrW(WndOwner, GWLP_HINSTANCE),
                                 (LPVOID) MenuInfo.Self);
  if (NULL == MenuInfo.Wnd || ! MenuSetRosMenuInfo(&MenuInfo))
    {
      return FALSE;
    }
  if (NULL == TopPopup)
    {
      TopPopup = MenuInfo.Wnd;
    }

  /* Display the window */            //设置窗口为top_level类型
  SetWindowPos(MenuInfo.Wnd, HWND_TOPMOST, 0, 0, 0, 0,
               SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
  UpdateWindow(MenuInfo.Wnd);         //显示

  return TRUE;
}

真正重要代码:
static INT FASTCALL
MenuTrackMenu(HMENU Menu, UINT Flags, INT x, INT y,
              HWND Wnd, const RECT *Rect )
{
  MSG Msg;
  ROSMENUINFO MenuInfo;
  ROSMENUITEMINFO ItemInfo;
  BOOL fRemove;
  INT ExecutedMenuId = -1;
  MTRACKER Mt;
  BOOL EnterIdleSent = FALSE;

  Mt.TrackFlags = 0;
  Mt.CurrentMenu = Menu;
  Mt.TopMenu = Menu;
  Mt.OwnerWnd = Wnd;
  Mt.Pt.x = x;
  Mt.Pt.y = y;

  TRACE("Menu=%x Flags=0x%08x (%d,%d) Wnd=%x (%ld,%ld)-(%ld,%ld)\n",
         Menu, Flags, x, y, Wnd, Rect ? Rect->left : 0, Rect ? Rect->top : 0,
         Rect ? Rect->right : 0, Rect ? Rect->bottom : 0);

  fEndMenu = FALSE;
  if (! MenuGetRosMenuInfo(&MenuInfo, Menu))
    {
      return FALSE;
    }

  if (0 != (Flags & TPM_BUTTONDOWN))
    {
      /* Get the result in order to start the tracking or not */
      fRemove = MenuButtonDown(&Mt, Menu, Flags);
      fEndMenu = ! fRemove;
    }

  SetCapture(Mt.OwnerWnd);   //拥有者关联鼠标
  (void)NtUserSetGUIThreadHandle(MSQ_STATE_MENUOWNER, Mt.OwnerWnd);   //将菜单消息关联以拥有者线程里去

  while (! fEndMenu)
    {
      /* we have to keep the message in the queue until it's
       * clear that menu loop is not over yet. */

      for (;;)
        {
          if (PeekMessageW(&Msg, 0, 0, 0, PM_NOREMOVE))
            {
              if (! CallMsgFilterW(&Msg, MSGF_MENU))  //过滤菜单消息
                {
                  break;
                }
              /* remove the message from the queue */
              PeekMessageW(&Msg, 0, Msg.message, Msg.message, PM_REMOVE );
            }
          else
            {
              if (! EnterIdleSent)
                {
                  HWND Win = (0 != (Flags & TPM_ENTERIDLEEX)
                              && 0 != (MenuInfo.Flags & MF_POPUP)) ? MenuInfo.Wnd : NULL;
                  EnterIdleSent = TRUE;
                  SendMessageW(Mt.OwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM) Win);
                }
              WaitMessage();
            }
        }

      /* check if EndMenu() tried to cancel us, by posting this message */
      if (WM_CANCELMODE == Msg.message)  //当离开时候退出消息循环
        {
          /* we are now out of the loop */
          fEndMenu = TRUE;

          /* remove the message from the queue */
          PeekMessageW(&Msg, 0, Msg.message, Msg.message, PM_REMOVE);

          /* break out of internal loop, ala ESCAPE */
          break;
        }

      TranslateMessage(&Msg);
      Mt.Pt = Msg.pt;

      if (Msg.hwnd == MenuInfo.Wnd || WM_TIMER != Msg.message)
        {
          EnterIdleSent = FALSE;
        }

      fRemove = FALSE;
      if (WM_MOUSEFIRST <= Msg.message && Msg.message <= WM_MOUSELAST)  //鼠标键处理消息
        {
          /*
           * Use the mouse coordinates in lParam instead of those in the MSG
           * struct to properly handle synthetic messages. They are already
           * in screen coordinates.
           */
          Mt.Pt.x = (short) LOWORD(Msg.lParam);
          Mt.Pt.y = (short) HIWORD(Msg.lParam);

          /* Find a menu for this mouse event */
          Menu = MenuPtMenu(Mt.TopMenu, Mt.Pt);

          switch(Msg.message)
            {
              /* no WM_NC... messages in captured state */

              case WM_RBUTTONDBLCLK:
              case WM_RBUTTONDOWN:
                if (0 == (Flags & TPM_RIGHTBUTTON))
                  {
                    break;
                  }
                /* fall through */
              case WM_LBUTTONDBLCLK:
              case WM_LBUTTONDOWN:
                /* If the message belongs to the menu, removes it from the queue */
                /* Else, end menu tracking */
                fRemove = MenuButtonDown(&Mt, Menu, Flags);
                fEndMenu = ! fRemove;
                break;

              case WM_RBUTTONUP:
                if (0 == (Flags & TPM_RIGHTBUTTON))
                  {
                    break;
                  }
                /* fall through */
              case WM_LBUTTONUP:
                /* Check if a menu was selected by the mouse */
                if (NULL != Menu)
                  {
                    ExecutedMenuId = MenuButtonUp(&Mt, Menu, Flags);

                    /* End the loop if ExecutedMenuId is an item ID */
                    /* or if the job was done (ExecutedMenuId = 0). */
                    fEndMenu = fRemove = (-1 != ExecutedMenuId);
                  }
                else
                  {
                    /* No menu was selected by the mouse */
                    /* if the function was called by TrackPopupMenu, continue
                       with the menu tracking. If not, stop it */
                    fEndMenu = (0 != (Flags & TPM_POPUPMENU) ? FALSE : TRUE);
                  }
                break;

              case WM_MOUSEMOVE:
                if (Menu)
                  {
                    fEndMenu |= ! MenuMouseMove(&Mt, Menu, Flags);
                  }
                break;

        } /* switch(Msg.message) - mouse */
    }
      else if (WM_KEYFIRST <= Msg.message && Msg.message <= WM_KEYLAST)  //键盘消息处理函数
    {
          fRemove = TRUE;  /* Keyboard messages are always removed */
          switch(Msg.message)
            {
              case WM_SYSKEYDOWN:
              case WM_KEYDOWN:
                switch(Msg.wParam)
                  {
                    case VK_MENU:
                      fEndMenu = TRUE;
                      break;
                    case VK_HOME:
                    case VK_END:
                      if (MenuGetRosMenuInfo(&MenuInfo, Mt.CurrentMenu))
                        {
                          MenuSelectItem(Mt.OwnerWnd, &MenuInfo, NO_SELECTED_ITEM,
                                         FALSE, 0 );
                        }
                      /* fall through */

                    case VK_UP:
                      if (MenuGetRosMenuInfo(&MenuInfo, Mt.CurrentMenu))
                        {
                          MenuMoveSelection(Mt.OwnerWnd, &MenuInfo,
                                            VK_HOME == Msg.wParam ? ITEM_NEXT : ITEM_PREV);
                        }
                      break;

                    case VK_DOWN: /* If on menu bar, pull-down the menu */
                      if (MenuGetRosMenuInfo(&MenuInfo, Mt.CurrentMenu))
                        {
                          if (0 == (MenuInfo.Flags & MF_POPUP))
                            {
                              if (MenuGetRosMenuInfo(&MenuInfo, Mt.TopMenu))
                                {
                                  Mt.CurrentMenu = MenuShowSubPopup(Mt.OwnerWnd, &MenuInfo,   //弹出下级子菜单
                                                                    TRUE, Flags);
                                }
                            }
                          else      /* otherwise try to move selection */
                            {
                              MenuMoveSelection(Mt.OwnerWnd, &MenuInfo, ITEM_NEXT);
                            }
                        }
                      break;

                    case VK_LEFT:
                      MenuKeyLeft(&Mt, Flags);
                      break;

                    case VK_RIGHT:
                      MenuKeyRight(&Mt, Flags);
                      break;

                    case VK_ESCAPE:
                      fEndMenu = MenuKeyEscape(&Mt, Flags);
                      break;

                    case VK_F1:
                      {
                        HELPINFO hi;
                        hi.cbSize = sizeof(HELPINFO);
                        hi.iContextType = HELPINFO_MENUITEM;
                        if (MenuGetRosMenuInfo(&MenuInfo, Mt.CurrentMenu))
                          {
                            if (NO_SELECTED_ITEM == MenuInfo.FocusedItem)
                              {
                                hi.iCtrlId = 0;
                              }
                            else
                              {
                                MenuInitRosMenuItemInfo(&ItemInfo);
                                if (MenuGetRosMenuItemInfo(MenuInfo.Self,
                                                           MenuInfo.FocusedItem,
                                                           &ItemInfo))
                                  {
                                    hi.iCtrlId = ItemInfo.wID;
                                  }
                                else
                                  {
                                    hi.iCtrlId = 0;
                                  }
                                MenuCleanupRosMenuItemInfo(&ItemInfo);
                              }
                          }
                        hi.hItemHandle = Menu;
            hi.dwContextId = MenuInfo.dwContextHelpID;
            hi.MousePos = Msg.pt;
            SendMessageW(Wnd, WM_HELP, 0, (LPARAM) &hi);
                        break;
                      }

                    default:
                      break;
                  }
                break;  /* WM_KEYDOWN */

              case WM_CHAR:
              case WM_SYSCHAR:
                {
                  UINT Pos;

                  if (! MenuGetRosMenuInfo(&MenuInfo, Mt.CurrentMenu))
                    {
                      break;
                    }
                  if (L'\r' == Msg.wParam || L' ' == Msg.wParam)
                    {
                      ExecutedMenuId = MenuExecFocusedItem(&Mt, &MenuInfo, Flags);
                      fEndMenu = (ExecutedMenuId != -2);
                      break;
                    }

                  /* Hack to avoid control chars. */
                  /* We will find a better way real soon... */
                  if (Msg.wParam < 32)
                    {
                      break;
                    }

                  Pos = MenuFindItemByKey(Mt.OwnerWnd, &MenuInfo,
                                          LOWORD(Msg.wParam), FALSE);
                  if ((UINT) -2 == Pos)
                    {
                      fEndMenu = TRUE;
                    }
                  else if ((UINT) -1 == Pos)
                    {
                      MessageBeep(0);
                    }
                  else
                    {
                      MenuSelectItem(Mt.OwnerWnd, &MenuInfo, Pos, TRUE, 0);
                      ExecutedMenuId = MenuExecFocusedItem(&Mt, &MenuInfo, Flags);
                      fEndMenu = (-2 != ExecutedMenuId);
                    }
        }
              break;
            }  /* switch(msg.message) - kbd */
        }
      else
        {
          PeekMessageW( &Msg, 0, Msg.message, Msg.message, PM_REMOVE );
          DispatchMessageW(&Msg);
          continue;
        }

      if (! fEndMenu)
        {
          fRemove = TRUE;
        }

      /* finally remove message from the queue */

      if (fRemove && 0 == (Mt.TrackFlags & TF_SKIPREMOVE))
        {
          PeekMessageW(&Msg, 0, Msg.message, Msg.message, PM_REMOVE);
        }
      else
        {
          Mt.TrackFlags &= ~TF_SKIPREMOVE;
        }
    }

  (void)NtUserSetGUIThreadHandle(MSQ_STATE_MENUOWNER, NULL);  //菜单窗口消息线程不再关联拥有者线程
  SetCapture(NULL);  /* release the capture */  

  /* If dropdown is still painted and the close box is clicked on
     then the menu will be destroyed as part of the DispatchMessage above.
     This will then invalidate the menu handle in Mt.hTopMenu. We should
     check for this first.  */
  if (IsMenu(Mt.TopMenu))
    {
      if (IsWindow(Mt.OwnerWnd))
        {
          if (MenuGetRosMenuInfo(&MenuInfo, Mt.TopMenu))
            {
              MenuHideSubPopups(Mt.OwnerWnd, &MenuInfo, FALSE);

              if (0 != (MenuInfo.Flags & MF_POPUP))
                {
                  DestroyWindow(MenuInfo.Wnd);
                  MenuInfo.Wnd = NULL;
                }
              MenuSelectItem(Mt.OwnerWnd, &MenuInfo, NO_SELECTED_ITEM, FALSE, NULL);
            }

          SendMessageW(Mt.OwnerWnd, WM_MENUSELECT, MAKELONG(0, 0xffff), 0);
        }

      if (MenuGetRosMenuInfo(&MenuInfo, Mt.TopMenu))
        {
          /* Reset the variable for hiding menu */
          MenuInfo.TimeToHide = FALSE;
          MenuSetRosMenuInfo(&MenuInfo);
        }
    }

  /* The return value is only used by TrackPopupMenu */
  if (!(Flags & TPM_RETURNCMD)) return TRUE;
  if (ExecutedMenuId < 0) ExecutedMenuId = 0;
  return ExecutedMenuId;
}

请注意这个消息循环与应用程序消息循环等价的,所以与拥有者窗口通信只有通过SendMessage函数.
以上是消息循环,那么具体的窗口过程在那儿呢?

/*********************************************************************
 * PopupMenu class descriptor
 */
const struct builtin_class_descr POPUPMENU_builtin_class =   
{
    POPUPMENU_CLASS_ATOMW,                     /* name */      //就是弹出菜单窗口类名32678
    CS_SAVEBITS | CS_DBLCLKS,                  /* style  */
    (WNDPROC) NULL,                            /* FIXME - procA */
    (WNDPROC) PopupMenuWndProcW,               /* FIXME - procW */  //窗口过程
    sizeof(MENUINFO *),                        /* extra */
    (LPCWSTR) IDC_ARROW,                       /* cursor */
    (HBRUSH)(COLOR_MENU + 1)                   /* brush */
};

弹出菜单窗口过程:
static LRESULT WINAPI
PopupMenuWndProcW(HWND Wnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
  TRACE("hwnd=%x msg=0x%04x wp=0x%04lx lp=0x%08lx\n", Wnd, Message, wParam, lParam);

  switch(Message)
    {
    case WM_CREATE:
      {
        CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
        SetWindowLongPtrW(Wnd, 0, (LONG) cs->lpCreateParams);
        return 0;
      }

    case WM_MOUSEACTIVATE:  /* We don't want to be activated */
      return MA_NOACTIVATE;

    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        BeginPaint(Wnd, &ps);
        MenuDrawPopupMenu(Wnd, ps.hdc, (HMENU)GetWindowLongPtrW(Wnd, 0));  //画菜单,将
        EndPaint(Wnd, &ps);
        return 0;
      }

    case WM_ERASEBKGND:
      return 1;

    case WM_DESTROY:
      /* zero out global pointer in case resident popup window was destroyed. */
      if (Wnd == TopPopup)
        {
          TopPopup = NULL;
        }
      break;

    case WM_SHOWWINDOW:
      if (0 != wParam)
        {
          if (0 == GetWindowLongPtrW(Wnd, 0))
            {
              OutputDebugStringA("no menu to display\n");
            }
        }
      else
        {
          SetWindowLongPtrW(Wnd, 0, 0);
        }
      break;

    case MM_SETMENUHANDLE:
      SetWindowLongPtrW(Wnd, 0, wParam);
      break;

    case MM_GETMENUHANDLE:
      return GetWindowLongPtrW(Wnd, 0);

    default:
      return DefWindowProcW(Wnd, Message, wParam, lParam);
    }

  return 0;
}
MenuDrawPopupMenu此过程将执行:SendMessageW(WndOwner, WM_DRAWITEM, 0, (LPARAM) &dis);

今天刚好遇到一个问题,就是如何将弹出菜单隐藏掉,相信如果不深入研究以上知识,绝对不会做出来的,以下是我解决方法:

//继承menus的TPopupList并重写WndProc,此时接收的消息是针对拥有者窗口的.
procedure TPopupList.WndProc(var Message: TMessage);
var
  MenuRect,OwnerRect: TRect;
  CurPt :TPoint;
begin
  inherited;
  if Message.Msg = WM_ENTERIDLE then
  begin
    Windows.GetWindowRect(Message.LParam,MenuRect);   
    Windows.GetWindowRect(Form1.Button1.Handle,OwnerRect);  //取得我们想关闭窗口的RECT,这是是Form1.Button1
    OwnerRect := Form1.Button2.BoundsRect;
    CurPt := Form1.ScreenToClient(Mouse.CursorPos);
    if not (Windows.PtInRect(OwnerRect,CurPt) or Windows.PtInRect(MenuRect,Mouse.CursorPos)) then
      Windows.PostMessage(Menus.PopupList.Window,WM_CANCELMODE,0,0);
  end;
end;

initialization
  Menus.PopupList := Unit1.TPopupList.Create;

可以从SendMessageW(Mt.OwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM) Win)得到菜单窗体句柄,以此得到Rect.这个就相当于Application的OnMessage函数.
而后,我们根据:
      /* check if EndMenu() tried to cancel us, by posting this message */
      if (WM_CANCELMODE == Msg.message)  //当离开时候退出消息循环
        {
          /* we are now out of the loop */
          fEndMenu = TRUE;

          /* remove the message from the queue */
          PeekMessageW(&Msg, 0, Msg.message, Msg.message, PM_REMOVE);

          /* break out of internal loop, ala ESCAPE */
          break;
        }
他是个中断条件,以此我们可以:Windows.PostMessage(Menus.PopupList.Window,WM_CANCELMODE,0,0);注意不能是SendMessage这个昨天是一样原因,SendMessage拥有独立的消息队列.

-------------20100701,Henry

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值