quinta-feira, 13 de janeiro de 2011

Hints para Itens de Menu

Quando o mouse passa por cima de um componente (um TButton, por exemplo) se a propriedade ShowHint for True e houver algum texto na propriedade Hint, a janela Hint/ToolTip será exibida para o componente.
Por design do Windows, mesmo se fixamos o valor da propriedade Hint para um item de Menu, o popup do Hint não será exibido. Porém, os itens de menu Iniciar exibem Hints, e o menu Favoritos do Internet Explorer também exibe hints de itens de menu.
Está bastante normal utilizar o evento OnHint da variável global Application, em aplicações Delphi, para exibir hints  (longos) de itens de menu em uma barra de estado.
O Windows não expõe as mensagens necessárias para suportar um evento OnMouseEnter tradicional.
Se quisermos adicionar hints popups de item de menu (tooltips) aos menus de aplicações Delphi, precisamos “apenas” controlar apropriadamente a mensagem WM_MenuSelect.
A classe TMenuItemHint - Hints para itens  de menu! 
Já que não podemos confiar no método Application.ActivateHint para exibir a janela de hint para itens de menu (pois o tratamento de menus é completamente controlando pelo Windows), para exibir a janela de hint, temos que criar nossa própria versão da mesma - derivando uma nova classe THintWindow.
Vejamos como criar uma classe TMenuItemHint - uma viúva de hint, que de fato é exibida para itens de menu! Em primeiro lugar, precisamos controlar a mensagem Windows WM_MENUSELECT:

type
  TForm1 = class(TForm)
  ...
  private
    procedure WMMenuSelect(var Msg: TWMMenuSelect) ; message WM_MENUSELECT;
  end
...
implementation
...
procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect) ;
var
  menuItem : TMenuItem;
  hSubMenu : HMENU;
begin
  inherited; // from TCustomForm (so that Application.Hint is assigned)

  menuItem := nil;
  if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
  begin
    if Msg.MenuFlag and MF_POPUP = MF_POPUP then
    begin
      hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem);
      menuItem := Self.Menu.FindItem(hSubMenu, fkHandle);
    end
    else
    begin
      menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand);
    end;
  end;
  miHint.DoActivateHint(menuItem);
end; (*WMMenuSelect*)

Informação rápida: a mensagem WM_MENUSELECT é enviada para janela owner do menu (Form1!), quando o usuário seleciona (não clica!) um item de menu. Usando o método FindItem da classe TMenu, podemos obter o item de menu atualmente selecionado. Os parâmetros do FindItem tem relação com as propriedades da mensagem recebida.
Uma vez que saibamos qual o item de menu por onde o mouse está passando, chamamos o método DoActivateHint da classe TMenuItemHint.

Nota: a variável miHint está definida como “var miHint: TMenuItemHint" e é criada no tratador de evento OnCreate do Formulário.

Agora, só resta a implementar a classe TMenuItemHint. Vejamos a parte da interface:

TMenuItemHint = class(THintWindow)
private
  activeMenuItem: TMenuItem;
  showTimer: TTimer;
  hideTimer: TTimer;
  procedure HideTime(Sender: TObject);
  procedure ShowTime(Sender: TObject);
public
  constructor Create(AOwner: TComponent); override;
  procedure DoActivateHint(menuItem: TMenuItem);
  destructor Destroy; override;
end;

Basicamente, a função DoActivateHint chama o método ActivateHint do THintWindow que usa a propriedade Hint  do TMenuItem (se for designada).
O showTimer é usado para garantir que o HintPause (da Application) ocorra antes do hint ser exibido. O hideTimer usa Application.HintHidePause para esconder a janela hint depois de um intervalo especificado.
Quando usaríamos Hints de Itens de Menu ? 
Apesar de que alguém poderia dizer que não é um bom projeto para exibir hints de itens de menu, há situações onde a exibição de hints  de itens de menu, de fato é muito melhor do que usar uma barra de estado. Uma lista de itens de menus “mais recentemente usados” (Most Recently Used - MRU) é tal caso. Um menu de barra de tarefa personalizado é outro.
Crie uma nova aplicação Delphi. No formulário principal coloque um MainMenu ("Menu1") (paleta Standard), um StatusBar (paleta Win32) e um ApplicationEvents (paleta Aditional).
Acrescente vários itens de menu ao menu. Designe uma propriedade Hint para alguns itens de menu, deixe alguns itens de menu "livres" de Hint.


A seguir, o código de fonte completo da unit, junto com a implementação da classe TMenuItemHint:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms,Dialogs, Menus, AppEvnts,
  StdCtrls, ExtCtrls, ComCtrls;


type
  TMenuItemHint = class(THintWindow)
  private
    activeMenuItem: TMenuItem;
    showTimer: TTimer;
    hideTimer: TTimer;
    procedure HideTime(Sender: TObject);
    procedure ShowTime(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure DoActivateHint(menuItem: TMenuItem) ;
    destructor Destroy; override;
  end;

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

var
  Form1: TForm1;

implementation
{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  miHint := TMenuItemHint.Create(self);
end; (*FormCreate*)

procedure TForm1.ApplicationEvents1Hint(Sender: TObject) ;
begin
  StatusBar1.SimpleText := 'App.OnHint : ' + Application.Hint;
end; (*Application.OnHint*)

procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect) ;
var
  menuItem: TMenuItem;
  hSubMenu: HMENU;
begin
  inherited; // from TCustomForm (ensures that Application.Hint is assigned)

  menuItem := nil;
  if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
  begin
    if Msg.MenuFlag and MF_POPUP = MF_POPUP then
    begin
      hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem) ;
      menuItem := Self.Menu.FindItem(hSubMenu, fkHandle) ;
    end
    else
    begin
      menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand) ;
    end;
  end;

  miHint.DoActivateHint(menuItem) ;
end; (*WMMenuSelect*)


{ TMenuItemHint }
constructor TMenuItemHint.Create(AOwner: TComponent) ;
begin
inherited;
showTimer := TTimer.Create(self) ;
showTimer.Interval := Application.HintPause;

hideTimer := TTimer.Create(self) ;
hideTimer.Interval := Application.HintHidePause;
end; (*Create*)

destructor TMenuItemHint.Destroy;
begin
hideTimer.OnTimer := nil;
showTimer.OnTimer := nil;
self.ReleaseHandle;
inherited;
end; (*Destroy*)

procedure TMenuItemHint.DoActivateHint(menuItem: TMenuItem) ;
begin
//force remove of the "old" hint window
hideTime(self) ;

if (menuItem = nil) or (menuItem.Hint = '') then
begin
   activeMenuItem := nil;
   Exit;
end;

activeMenuItem := menuItem;

showTimer.OnTimer := ShowTime;
hideTimer.OnTimer := HideTime;
end; (*DoActivateHint*)

procedure TMenuItemHint.ShowTime(Sender: TObject) ;
var
r: TRect;
wdth: integer;
hght: integer;
begin
if activeMenuItem <> nil then
begin
   //position and resize
   wdth := Canvas.TextWidth(activeMenuItem.Hint) ;
   hght := Canvas.TextHeight(activeMenuItem.Hint) ;

   r.Left := Mouse.CursorPos.X + 16;
   r.Top := Mouse.CursorPos.Y + 16;
   r.Right := r.Left + wdth + 6;
   r.Bottom := r.Top + hght + 4;
    ActivateHint(r,activeMenuItem.Hint) ;
end;
  showTimer.OnTimer := nil;
end; (*ShowTime*)

procedure TMenuItemHint.HideTime(Sender: TObject) ;
begin
//hide (destroy) hint window
self.ReleaseHandle;
hideTimer.OnTimer := nil;
end; (*HideTime*)

end.

Nenhum comentário:

Postar um comentário