18 de março de 2011

Usando cursores personalizados em aplicações Delphi

Sistemas operacionais gráficos como o Windows fornecem nativamente um retorno visual para que o usuário perceba os diferentes tipos de interação que estão disponíveis numa aplicação. Esse retorno visual é conseguido mudando-se a imagem apresentada pelo ponteiro do mouse - o cursor. Com isso, o usuário consegue rapidamente identificar numa tela objetos que podem ser clicados (links) ou que um determinado objeto pode ser redimensionado ou arrastado.

Através da modificação do ponteiro do mouse podemos também notificar o usuário de que algum processamento diferente está ocorrendo. É o caso, por exemplo, do cursor com a ampulheta (indicando que uma operação demorada está em andamento) ou o cursor que indica se uma operação do tipo arrastar-e-soltar pode ou não ser completada. O fato de todas as aplicações do sistema compartilharem o mesmo tipo de relação entre uma funcionalidade e o respectivo formato do cursor facilita a familiarização do usuário, reduzindo a curva de aprendizado dele.

Apesar de o conjunto de cursores distribuídos junto com o sistema operacional ser de grande utilidade, há situações em que um programa pode se beneficiar de ter seu próprio cursor para representar uma situação que seja muito específica dele. Por causa disso, o Windows disponibiliza um conjunto de APIs para que uma aplicação possa criar e manipular cursores do mouse.

Em Delphi, o conjunto nativo de cursores disponíveis fica armazenado numa propriedade do objeto Screen, que mantém informações sobre a tela com a qual a aplicação está trabalhando. A propriedade em questão é a Cursors, um array acessível por constantes numéricas definidas na própria VCL. Para usarmos um cursor personalizado, teremos que adicioná-lo a essa lista e associá-lo a uma constante nossa. A forma mais fácil de fazer isso é carregando um arquivo externo com os dados do cursor, normalmente com extensão CUR (cursor normal) ou ANI (cursor animado):
const crMeuCursor = 1;
begin
Screen.Cursors[crMeuCursor] :=
LoadCursorFromFile('meuCursor.cur');
_Painel.Cursor := crMeuCursor;
end;

O código do quadro acima cria uma nova constante para representar meu cursor (crMeuCursor), usa a função LoadCursorFromFile da API do Windows para carregá-lo a partir do arquivo 'meuCursor.cur'. Depois, ele é atribuido a um painel na tela para que ela passe a exibir o meu cursor sempre que o ponteiro do mouse passar sobre ele. A constante usada pode ser qualquer número inteiro pois os cursores nativos são associados a valores negativos.

O inconveniente com essa abordagem é que o arquivo com o cursor deve ser distribuído junto com a aplicação pois ele deve estar presente para o programa funcionar. Uma alternativa é linkar o cursor como recurso da aplicação, o que embutirá esse cursor no executável final. Para implementar essa alternativa, duas alterações são necessárias. Primeiro crie um arquivo com extensão RC e o adicione ao projeto no Delphi. O conteúdo dele deve ter uma linha semelhante a esta:
meuCursor Cursor "meuCursor.cur"

Isso fará com que o cursor seja linkado com o programa. Agora, a carga tem que ser feita de outra maneira, referenciando o identificador meuCursor atribuído ao recurso:
Screen.Cursors[crMeuCursor] :=
LoadCursor(HInstance, 'meuCursor');

O HInstance usado na chamada de LoadCursor é uma variável global controlada pela VCL que armazena o handle atribuído pelo Windows à nossa aplicação Delphi.

Antes de prosseguir, uma nota sobre como o cursor é desenhado na tela. Um cursor é composto de duas imagens: o desenho do cursor propriamente dito e uma máscara de bits que é aplicada para obter o efeito de transparência quando o cursor é exibido. Isto é, a máscara garante que apenas o desenho real apareça na tela, fazendo com que os demais pontos da imagem estejam transparentes e permitam ver o conteúdo da tela que está por trás. O Windows realiza uma operação bit-a-bit envolvendo ambas as imagens fornecidas e o próprio conteúdo da tela para atingir o efeito citado. Abaixo está um exemplo de como essas duas imagens se parecem.
Cursor

Com isso, o método mais flexível de criação de um novo cursor é trabalhar diretamente com essas imagens bitmap. A flexibilidade está no fato de que você tem acesso total ao contéudo delas, podendo tratá-las por programação antes de usá-las como um cursor. Para trabalhar com as imagens mostradas acima, também podemos adicioná-las a um arquivo RC incluído no projeto Delphi:
penc Bitmap "penc.bmp"
penm Bitmap "penm.bmp"

Vamos precisar recuperar ambos os bitmaps por programação para podermos passá-los ao Windows. O código a seguir retrata como isso pode ser conseguido, levando em conta que os bitmaps estão linkados ao programa através do arquivo RC anterior:
var bmpMask : TBitmap;
bmpColor : TBitmap;
begin
bmpMask := TBitmap.Create;
bmpColor := TBitmap.Create;

bmpMask.LoadFromResourceName(hInstance, 'penm');
bmpColor.LoadFromResourceName(hInstance, 'penc');
{ ... }

Obviamente, os bitmaps podem ser obtidos da maneira mais apropriada para sua aplicação. Quero dizer com isso que você pode até mesmo desenhar em runtime o conteúdo que quiser usando o canvas embutido no TBitmap.

A API do Windows fornece uma função chamada CreateIconIndirect que nos permitirá criar o cursor informando as imagens carregadas. É o que faz o código abaixo:
var iconInfo: TIconInfo;
bmpMask: TBitmap;
bmpColor: TBitmap;
begin
{ ... }
With iconInfo do begin
fIcon := false;
xHotspot := 1;
yHotspot := 64;
hbmMask := bmpMask.Handle;
hbmColor := bmpColor.Handle;
end;

Screen.Cursors[crMyCursor] := CreateIconIndirect(iconInfo);

bmpMask.Free;
bmpColor.Free;
{ ... }

Além de receber os respectivos handles para os bitmaps, a estrutura que é passada como parâmetro para a CreateIconIndirect tem também campos para definirmos o hotspot do cursor, isto é, aquele ponto dentro do cursor usado para determinar o local exato onde um "clique" do mouse aconteceu. Veja também que o resultado da chamada a CreateIconIndirect é lançado diretamente na lista de cursores mantida pelo Delphi, do mesmo modo que as demais formas de se carregar um cursor. Uma diferença fundamental, entretanto, é que nessa forma de se criar o cursor é nossa responsabilidade também destruí-lo quando ele não for mais necessário:
DestroyIcon (Screen.Cursors [crMyCursor]);

Como eu disse, antes de criar o cursor podemos modificar por programação as imagens que o constituirão. Veja um exemplo usando os bitmaps instanciados no quadro mais acima:
{ ... }
for I := 0 to bmpColor.Width - 1 do
for J := 0 to bmpColor.Height - 1 do
if bmpColor.Canvas.Pixels[i,j] = clWhite then
bmpColor.Canvas.Pixels[i,j] := _Seletor1.SelectedColor;
{ ... }

Aqui, todos pontos (pixels) brancos da imagem real são substituídos por uma cor selecionada pelo usuário da aplicação. Isso faz com que o desenho do lápis assuma a cor definida, dando um retorno visual sobre qual cor está selecionada no momento.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.