6

所以我有一个 TMenuItem 附加到 TDBGrid 的 TPopupMenu 上的 TAction (实际上是第 3 方,但你明白了)。根据网格中选定的行,启用或禁用 TAction。我想要的是能够向用户显示一个提示,解释为什么该项目被禁用。

至于为什么我想要一个禁用菜单项的提示,可以说我同意 Joel

所有 TMenuItem 都有一个提示属性,但尽我所能告诉他们,它们仅使用 TApplicationEvent.OnHint 事件处理程序将提示粘贴在 TStatusBar 或其他一些特殊处理中。我找到了一篇关于如何为 TMainMenu 的 TMenuItems 创建自己的偶数窗口的文章,但它不适用于 TPopupMenu 的 TMenuItem。它通过处理 WM_MENUSELECT 消息来工作,据我所知,该消息不是在 TPopupMenu 上发送的。

4

2 回答 2

6

WM_MENUSELECT 确实也为弹出菜单中的菜单项处理,但不是由包含(弹出)菜单的窗体的 windows proc 处理,而是由 Menus.PopupList 创建的不可见帮助窗口处理。幸运的是,您可以(至少在 Delphi 5 下)通过 Menus.PopupList.Window 获得这个 HWND。

现在,您可以使用老式方法对窗口进行子类化,如CodeGear 文章中所述,也可以为弹出菜单处理 WM_MENUSELECT。HWND 将在第一个 TPopupMenu 创建之后到最后一个 TPopupMenu 对象被销毁之前有效。

对问题中链接文章中的演示应用程序进行快速测试应该可以揭示这是否可行。

编辑:它确实有效。我更改了链接示例以显示弹出菜单的提示。以下是步骤:

将 OnDestroy 的处理程序、旧窗口 proc 的成员变量和新窗口 proc 的方法添加到表单:

TForm1 = class(TForm)
  ...
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
  procedure ApplicationEvents1Hint(Sender: TObject);
private
  miHint : TMenuItemHint;
  fOldWndProc: TFarProc;
  procedure WMMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
  procedure PopupListWndProc(var AMsg: TMessage);
end;

将表单的 OnCreate 处理程序更改为隐藏 PopupList 窗口的子类,并在 OnDestroy 处理程序中实现窗口 proc 的正确恢复:

procedure TForm1.FormCreate(Sender: TObject);
var
  NewWndProc: TFarProc;
begin
  miHint := TMenuItemHint.Create(self);

  NewWndProc := MakeObjectInstance(PopupListWndProc);
  fOldWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
    integer(NewWndProc)));
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  NewWndProc: TFarProc;
begin
  NewWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
    integer(fOldWndProc)));
  FreeObjectInstance(NewWndProc);
end;

实现子类窗口过程:

procedure TForm1.PopupListWndProc(var AMsg: TMessage);

  function FindItemForCommand(APopupMenu: TPopupMenu;
    const AMenuMsg: TWMMenuSelect): TMenuItem;
  var
    SubMenu: HMENU;
  begin
    Assert(APopupMenu <> nil);
    // menuitem
    Result := APopupMenu.FindItem(AMenuMsg.IDItem, fkCommand);
    if Result = nil then begin
      // submenu
      SubMenu := GetSubMenu(AMenuMsg.Menu, AMenuMsg.IDItem);
      if SubMenu <> 0 then
        Result := APopupMenu.FindItem(SubMenu, fkHandle);
    end;
  end;

var
  Msg: TWMMenuSelect;
  menuItem: TMenuItem;
  MenuIndex: integer;
begin
  AMsg.Result := CallWindowProc(fOldWndProc, Menus.PopupList.Window,
    AMsg.Msg, AMsg.WParam, AMsg.LParam);
  if AMsg.Msg = WM_MENUSELECT then begin
    menuItem := nil;
    Msg := TWMMenuSelect(AMsg);
    if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then begin
      for MenuIndex := 0 to PopupList.Count - 1 do begin
        menuItem := FindItemForCommand(PopupList.Items[MenuIndex], Msg);
        if menuItem <> nil then
          break;
      end;
    end;
    miHint.DoActivateHint(menuItem);
  end;
end;

对循环中的所有弹出菜单执行此操作,直到找到第一个匹配项或子菜单。

于 2009-01-22T22:18:20.680 回答
3

不确定它是否有帮助,但我创建了自己的多行提示窗口(用于 Delphi7),以便能够显示多于一行的文本。它是开源的,你可以在这里找到它。

在屏幕上的正确位置显示它涉及一些工作,但您可以完全控制它。

于 2009-01-22T20:53:57.200 回答