Отобразить подсказку к всплывающему меню в отключенном пункте меню - PullRequest
6 голосов
/ 22 января 2009

Итак, у меня есть TMenuItem, присоединенный к TAction в TPopupMenu для TDBGrid (на самом деле сторонний, но вы поняли идею). На основе выбранной строки в сетке TAction включается или отключается. Я хочу, чтобы у пользователя была подсказка, объясняющая, почему этот элемент отключен.

Что касается того, почему мне нужна подсказка для отключенного пункта меню, то давайте просто скажем, что я согласен с Джоэлом .

Все TMenuItem имеют свойство подсказки, но, насколько я могу судить, они используют только обработчик события TApplicationEvent.OnHint, чтобы вставить подсказку в TStatusBar или какой-либо другой специальной обработке. Я нашел статью о том, как создать собственное четное окно для TMenuItems TMainMenu, но он не работает на TMenuItem TPopupMenu. Он работает, обрабатывая сообщение WM_MENUSELECT, которое, насколько я могу судить, не отправляется в TPopupMenu.

Ответы [ 2 ]

6 голосов
/ 23 января 2009

WM_MENUSELECT действительно обрабатывается и для пунктов меню во всплывающих меню, но не Windows Proc формы, содержащей (всплывающее) меню, а невидимым вспомогательным окном, созданным Menus.PopupList. К счастью, вы можете (по крайней мере, в Delphi 5) получить доступ к этому HWND через Menus.PopupList.Window.

Теперь вы можете использовать старомодный способ для создания подкласса окна, как описано, например, в этой статье CodeGear , для обработки WM_MENUSELECT также для всплывающих меню. HWND будет действительным с момента создания первого TPopupMenu до того, как будет уничтожен последний объект TPopupMenu.

Быстрый тест с демонстрационным приложением в связанной статье в вопросе должен показать, будет ли это работать.

Редактировать: Это действительно работает. Я изменил связанный пример , чтобы показать подсказки также для всплывающего меню. Вот шаги:

Добавьте в форму обработчик 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;

Это делается для всех всплывающих меню в цикле, пока не будет найден первый соответствующий элемент или подменю.

3 голосов
/ 22 января 2009

Не уверен, поможет ли это, но я создал свое собственное многострочное окно подсказок (для Delphi7), чтобы можно было отображать более одной строки текста. Это открытый исходный код, и вы можете найти его здесь .

Требуется некоторая работа, чтобы показать его в нужном месте на экране, но вы имеете полный контроль над ним.

...