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.

Incluir mais de uma linha no Hint

Para incluir mais de uma linha no Hint você deve utilizar o evento OnMouseMove de cada componente.

Veja abaixo como ficará o código em um Edit por exemplo:

procedure TForm1.Edit1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
Edit1.hint := ‘Primeira Linha’+#13+’Segunda Linha’+#13+
       ‘Terceira Linha’+#13+’Quarta Linha’;
end;

Obs. Não esquecer de mudar para TRUE a propriedade ShowHint.

Exibir bolinhas num campo de senha

Pesquisando no assunto e explorando pude encontrar alguns componentes, mas não é recomendável, tanto por causa da instalação como pelo tamanho do programa final. Uma forma bastante simples:

O Windows vem com a fonte "Wingdings", que contém caracteres desenhados, assim como a "Webdings" também. Utilizaremos esta fonte para o campo de senha.

- Coloque um edit no form, e deixe a propriedade "text" vazia
- Altere a fonte do edit, escolha "Wingdings" (pelo inspetor de objetos)
- Na propriedade "PasswordChar", coloque a letra "l", um "L" minúsculo.

O "l" corresponde ao caractere da bolinha nesta fonte. Se você copiar a bolinha (por exemplo, pelo mapa de caracteres) e colá-la num editor de textos puro (como o Mep Texto ou o Bloco de notas), o resultado colado será a letra "l", o caractere que na fonte "Wingdings" é representado por uma bolinha.

Mudando texto do TEdit no OnChange

Se o texto de um TEdit for mudado no seu evento OnChange, este evento será chamado recursivamente até
acabar com o espaço de pilha. Para fazer isso, deve-se setar o evento OnChange para NIL antes de mudar o texto,
voltando ao original depois, desta maneira:

procedure Edit1Change(Sender : TObject);
begin
Edit1.OnChange := NIL;
if Edit1.Text = 'Texto' then
Edit1.Text := 'Novo Texto';
Edit1.OnChange := Edit1Change;
end;

Esta dica também vale para evento OnValidate.

Fazer um TEdit aceitar apenas números

Na rotina abaixo, o TEdit só aceitará números de 0 a 9 e o BackSpace (Chr(8)). Se você quiser a vírgula também, coloque dentro do colchete DecimalSeparator

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in['0'..'9',Chr(8)]) then Key:= #0;
end;

quarta-feira, 12 de janeiro de 2011

Adaptando para resoluções de video diferentes

Resolução de Vídeo:

Quando criamos formulários, ãs vezes é útil escrever um código para que a tela e todos os seus objetos sejam mostrados no
mesmo tamanho, não importando qual a resolução da tela. Aqui esta um código que mostra como isso é feito:

Implementation

const
ScreenWidth: LongInt = 800; {I designed my form in 800x600 mode.}
ScreenHeight: LongInt = 600;

{$R *.DFM}

procedure TForm1.FormCreate (Sender: Tobject);

begin
   scaled := true;
   if (screen.width <> ScreenWidth) then
   begin
       height := longint(height) * longint(screen.height) DIV ScreenHeight;
       width := longint(width) * longint(screen.width) DIV ScreenWidth;
       scaleyBy(screen.width, ScreenWidth);
   end;
end;

Agora, você vai querer checar, se o tamanho dos fontes(de letra) estão OK. Antes de trocar p tamanho do fonte, você precisará ter
certeza de que o objeto realmente tem a propriedade fonte pela checagem da RTTI. Isso pode ser feito assim:

USES tyinfo; {Add this to your USES statement.}

var

i:integer;

begin
   for i := componentCount - 1 downto 0 do
   with components[i] do
   begin
       if GetPropInfo(ClassInfo, ´font´) <> nil then
       font.size := (NewFormWidth DIV OldFormWidth) * font.size;
   end;
end;



{Esta é a maneira longa de fazer a mesma coisa}

var

i:integer;

p:PPropInfo;

begin
   for i := componentCount - 1 downto 0 do
   with components [i] do
   begin
       p := GetPropInfo (ClassInfo, ´font´);
       if assigned (p) then
       font.size := (NewFormWidth DIV OldFormWidth) * font.size;
   end;
end;

Atenção: Nem todos os objetos tem a propriedade FONT. Isso deve ser o suficiente para você começar.

Atenção: A seguir, algumas dicas para ter em mente quando representar aplicações Delphi (formulários) em diferentes resoluções
de Tela:

* Decida antecipadamente, na etapa de criação do formulário, se ele será escalável ou não. A vantagem de um não escalável é
que nada muda em tempo de execução. A desvantagem é equivalente (seu formulário pode ser muito pequeno ou grande para
alguns sistemas se nào for usada escala).

* Se você não for usar formulário escalável, configure o set scaled to False.

* Ou então, configure a propriedade scaled do formulário para True.

* Configure a propriedade AutoScroll para False. AutoScroll = True quer dizer "não mexa no tamanho do frame do formulário em
tempo de execução", o que não parece bom quando o conteúdo do formulário muda de tamanho.

* Configure a fonte do formulário para uma True Type escalável, como a Arial MS. San Serif é uma boa alternativa, mas lembre que
ainda é uma fonte bitmapped. Só a Arial dará uma fonte dentro de um pixel da altura desejada.ATENÇÃO: Se a fonte usada em
uma aplicação não estiver instalada no computador, o Windows selecionará uma fonte alternativa da mesma família para utilizar. O
tamanho dessa fonte pode não corresponder ao da fonte original, podendo causar problemas.

* Configure a propriedade position do formulário para uma opção diferente de poDesigned. poDesigneddeixa o formulário onde você
o deixou ( no design Time), o que sempre termina fora da margem, à esquerda da minha tela 1280 x 1024 - e completamente fora
da tela 640 x 480.

* Não amontoe controles no formulário - deixe pelo menos 4 pixels entre else, para que uma mudança de um pixel nas margens
(devido a apresentação em escala) não mostre controles sobrepostos.

* Para labels de uma linha alinhadas ã esquerda ou à direita, configure o AutoSize para True. Para outras formas de alinhamento
configure o AutoSize para False.

* Tenha certeza de que há espaço em branco suficiente num componente de labels para alterações no tamanho da fonte - um
espaço de 25% do comprimento da linha de caracteres mostrada é um pouco a mais do que se precisa, mas é mais seguro.
(Você vai precisar de um espaço equivalente a 30% de espansão para string labels se você pretende traduzir sua aplicação para
outra linguagem). Se o Autosize estiver em False, tenha certeza de que realmente configurou o tamanho do label corretamente.
Se o Autosize estiver em True, esteja certo de que há espaço suficiente para que o label se amplie.

* Em labels de múltiplas linhas ou de termos ocultos, deixe pelo menos uma linha em branco na base. Isso vai ser necessário
para incluir o que estiver sobrando quando o texto for oculto de maneira diferente, pela mudança do tamanho da fonte com a
escala. Não assuma isso porque está usando fontes grandes. Você não tem que deixar sobra de texto - as fontes (grandes) de
outros usuários podem ser maiores que as suas!

* Tenha cuidado quando abrir um projeto em IDEs com resoluções diferentes. Assim que o formulário for aberto, sua propriedade
Pixel per Inch será moditificada, e gravada para o DFM se você salvar o projeto. É melhor testar a aplicação rodando sozinho, e
editar o formulário em apenas uma resolução. Editar em várias resoluções e tamanhos de fonte provoca problemas de fluxo e
tamanho dos componentes.

*Falando em fluxo de componentes, não represente o formulário em escala muitas vezes, quando estiver sendo criado ou quando
tiver sendo executado. Cada escala introduz erros de roundoff que se acumulam muito rapidamente, uma vez que as coordenadas
são rigorosamente interias. Quando valores fracionários forem retirados das origens e tamanhos do controle com cada sucessiva
representação em escala, os conttroles parecerão deslizar para noroeste e ficar menores. Se você quer deixar seus usuários
representarem o formulários em escala quantas vezes quiserem, comece com um formulário recentemente criado para que erros
de escala não se acumulem.

* Definitivamente, não mexa na propriedade Pixel pre Inch do formulário.

* Em geral, não é necessário criar formulários em uma resolução específica, mas é essencial que você os revise em 640 x 480
com fontes pequenas e/ou grandes, e em alta resolução com fontes pequenas e/ou grandes antes de liberar suas aplicações. Isso
deverser parte de sua lista de conferência para testar a compatibilidade do sistema regularmente.

* Preste bastante atenção em todos os componentes que são basicamamente TMemo de uma linha - com oTDBLookupCombo. O
controle de edição (multi-linhas) do Windows sempre mostra apenas linhas inteiras de texto. Se o controle for muito curto para
sua fonte, um TMemo não mostrará coisa alguma, e um TEdit mostrará um pedaço do texto. É melhor fazer esses componentes
um pouco maiores do que deixá-los um pixel menores e não aparecer nada do texto.

* Tenha em mente que toda representação em escala é proporcional à diferença da altura da fonte entre o modo de execução e o
   modo de desenho, NÃO à resolução ou ao tamanho do monitor. Lembre também que as origens dos seus controles serão
  alteradas quando o formulário for representado em escala. Você não pode aumentar componentes muito bem sem também
                                   movê-los um pouco, novamente.


Obtendo e modificando a posição do cursor em um TMemo

Modificando a posição:

ActiveControl:=Memo1;
MemoCursorTo(Memo1,2,3);

Obtendo a Posição:

GetMemoLineCol(Memo1,Linha,Coluna);

Mostrando arquivos GIF

O Delphi, por padrão, não suporta nativamente arquivos GIFs. Existe uma classe modificada de TGifImage em clootie.narod.ru/delphi/download_vcl.html (com suporte a Delphi 6 e 7), que nos possibilita a inclusão de arquivos GIFs em nossos projetos.
Basta adicionar o arquivo GifImage.pas ao projeto ou até mesmo apenas declarar no uses do formulário, a unit GifImage para abrir um arquivo GIF (lembre-se de adicionar o arquivo GifImage.dcu em alguma pasta que esteja no Library Path do Delphi), através de um Image no Delphi. Caso deseje instalar o arquivo como componente, apenas tome cuidado para conflitos, pois algumas suítes, como a RXLib, tem o seu próprio visualizador de arquivos GIF.