2009-01-22 89 views
4

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

至于为什么我想要一个禁用菜单项的提示,让我们只是说我是in agreement with Joel

所有的TMenuItem都有一个提示属性,但最好我可以告诉他们只使用TApplicationEvent.OnHint事件处理程序将提示粘贴到TStatusBar或其他特殊处理中。 I found an article关于如何为TMainMenu的TMenuItems创建自己的窗口,但它不适用于TPopupMenu的TMenuItem。它通过处理WM_MENUSELECT消息来工作,就我所知,该消息不会在TPopupMenu上发送。

+1

如果你真的想为用户提供最好的体验,你应该重新考虑。状态栏帮助(立即出现)和提示窗口之间的可发现性存在重要差异。用户不会看到的帮助文本并不真正有用。特别是如果它是非标准的。 – mghie 2009-01-22 21:38:48

+1

我同意,在一定程度上。大多数用户希望提示他们的眼睛是:通过鼠标指针。我已经使用了仅使用状态栏提示的应用程序,并且非常烦恼必须查找提示。 此应用程序在任何数百形式的不使用状态栏提示,我想是一致的。 – 2009-01-22 21:48:10

回答

4

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

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

在问题中的链接文章中使用演示应用程序进行快速测试应该能够揭示这是否会起作用。

编辑:它确实工作。我更改了the linked example以显示弹出式菜单的提示。以下是具体步骤:

添加处理程序的OnDestroy,旧的窗口过程的一个成员变量和新的窗口过程到窗体的方法:

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处理程序正确恢复:

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; 

这是针对循环中的所有弹出菜单完成的,直到找到第一个匹配项目或子菜单。

2

不知道它是否有帮助,但我创建了自己的多行提示窗口(对于Delphi7)能够显示更多,然后只显示一行文本。 它是开源的,你可以找到它here

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

+0

是的,我认为,但我希望有一种方法不涉及管理和移动我自己的提示窗口。谢谢! – 2009-01-22 21:03:18