Exibindo BalloonTips
Alerta!!
Para compreender bem este artigo e seu código fonte, devemos ter algum conhecimento das funções WinAPI, das constantes e das estruturas. Se não souber do que estamos falando, será difícil acompanhar os conceitos aqui mencionados. Recomendamos olhar na seção "Points of Interest", para descobrir onde iniciar a procura por este conhecimento.
No Início…
Ultimamente, temos trabalhado em um “Felix the Cat Desktop Companion”. Ficamos interessados em tooltips de balão no estilo desenho animado (cartoon), quando desejamos que o Felix "falasse com” o usuário. Do momento em que o C# não tem uma classe de controle Balloon e não desejávamos realmente fazer o Felix "falar", começamos a imaginar como fazer isto acontecer. Sabíamos que haviam pelo menos dois caminhos a serem seguidos:
O primeiro seria implementar uma classe Balloon utilizando o GDI+;
O segundo seria ver o que o Windows utiliza para exibir estes balões e verificar se poderíamos utilizá-lo também.
Ambos os caminhos eram igualmente escuros:
Conhecíamos um pouco de GDI+, porém não o bastante. Sabíamos também que as regiões e os gráficos exigem muito do processador e não achamos nenhum código fonte pequeno nos resultados das buscas, e o pior de tudo, achamos difícil de dominar. Não estávamos receosos, apenas não achamos interessante aprofundarmos no GDI+, somente para obtermos um balão. Apenas para constar, o GDI+ é uma grande ferramenta, uma vez que começamos a dominá-lo, poderemos gerar algum material bem interessante.
Sabíamos também o que o WinAPI é, o que pode ser feito e o quão poderoso é. Portanto, não tínhamos nenhuma dúvida de que haveria uma maneira mais fácil de obtermos Balloons com ele. A partir de estes fatos, decidimos arriscar com o WinAPI.
E Fez-se a Luz…
O primeiro lugar que procuramos para ver se alguém tinha implementado algum código foi no CodeProject. Encontramos dois artigos: um implementado com o GDI+ e o outro utilizando o WinAPI.
Fizemos o download do programa de demonstração do WinAPI e o código, achando que o problema estava resolvido.
Achamos que Não…
Como toda pessoa curiosa faria normalmente, executamos antes o programa de demonstração, e começamos a clicar nos botões aqui e ali. Lemos também o que os formulários diziam, esperando imaginar o que o programa fazia. Os cliques foram em vão; de muitos botões em três formulários, somente um deles exibiu um tooltip de balão, o qual ficou pendurado na tela até que a aplicação encerrou, com uma mensagem de que tinha ocorrido uma má gerência de recursos. Depois disso, decidimos olhar o código; havia apenas alguns comentários, diversas classes inúteis que não funcionavam como deveriam ou nem funcionavam e o pior, não entendemos o que o autor tinha feito nem porque ou como o tinha feito, ou seja, não tínhamos nenhuma idéia clara como ou onde começar. Ficamos muito infelizes...
Decidimos olhar a solução GDI+, portanto, fizemos o download e executamos o projeto do programa de demonstração. Desta vez fiquei surpreso. Alguém tinha feito um tooltip multi-functional de balão legal, na verdade demasiado legal e conseqüentemente demasiado complexo. Tinha um monte de material extra não necessário. Não nos sentimos confortáveis para modificar o código e não tínhamos o domínio necessário do GDI+. Neste ponto, A única solução seria fazer algo melhor utilizando o WinAPI.
Quando alguém faz algo melhor, existe sempre um ponto inicial: explorar o mesmo projeto que tinha me decepcionado, o artigo Ballons Tips Galore. Isto não significa que roubamos o código, de modo nenhum, podemos ver que a implementação é diferente. Para criar a janela do balão, por exemplo, ele utiliza a classe NativeWindow e nós utilizamos a função CreateWindowEx WinAPI. Portanto não, não se trata nem ao menos de uma reprodução. Utilizamos o código apenas para ter uma idéia de como criaríamos uma janela tooltip de balão e onde poderíamos iniciar a procura.
A questão é que todas as vezes que desejamos fazer algo novo com o WinAPI, não encontramos alguma ajuda real no MSDN; sim, os nomes das constantes estão lá, porém não os seus valores reais; sim, o API está explicado, porém não há nenhum código útil que mostre como fazer a coisa toda funcionar. Portanto, a única maneira que funcionou foi olhar o código de outras pessoas e aprender com ele. É por esta razão que o nosso código fonte está disponível e bem documentado, de modo que qualquer um poderá aprender com ele, compreender o que, o porquê e o como é feito.
Onde Começamos…
O MSDN tem um motor de pesquisa. Digitamos tooltip sem filtro e obtivemos mais de 500 resultados; digitamos Tooltip controls e novamente obtivemos mais de 500 resultados, porém o primeiro resultado era um casamento exato: Tooltip Controls in the Windows Shell and Controls section.
Começamos a ler, e ficamos surpresos ao encontrar uma quantidade de informação considerável sobre como criar e exibir tipos diferentes de tooltips com a mesma classe de controle. Daremos uma breve explicação sobre o que encontramos e o que entendemos.
Tecnicamente falando, um tooltip de balão é uma janela> Naturalmente, não é uma janela normal como a que todos conhecemos, porém uma janela derivada de uma classe janela. Meio confuso não é? Vamos passo a passo...
Como mencionado antes, o tooltip de balão gerado neste programa de demonstração é criado após ter chamado a função CreateWindowEx da WinAPI.
De acordo com o MSDN:
A função CreateWindowEx cria uma janela sobreposta pop-up ou filha com um estilo estendido de janela.
Como podemos ver, recebe muitos parâmetros:
// Sintaxe MSDN:
HWND CreateWindowEx(
DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth,
int nHeight, HWND hWndParent, HMENU hMenu,
HINSTANCE hInstance, LPVOID lpParam
);
----------------
// Declaracao C# (somente neste demo)
[DllImport("User32", SetLastError=true)]
internal static extern int CreateWindowEx (
int dwExStyle, string lpClassName, string lpWindowName, int dwStyle,
int X, int Y, int nWidth, int nHeight, int hWndParent, int hMenu,
int hInstance, IntPtr lpParam);
----------------
// Uso Direto (somente neste demo)
m_lTTHwnd = CreateWindowEx(0, TOOLTIP_CLASS, string.Empty,
lWinStyle, 0, 0, 0, 0, m_lParentHwnd, 0, 0, IntPtr.Zero);
Cada parâmetro tem sua própria descrição (ver no MSDN), só que desta vez, focaremos apenas no segundo, o LPCTSTR lpClassName.
De acordo com a descrição do MSDN:
O lpClassName deve ser um Pointer para uma string terminado em nulo ou para uma classe átomo criada por uma chamada precedente à função RegisterClass ou RegisterClassEx. O átomo deve estar na palavra de mais baixa ordem do lpClassName; a palavra de mais alta ordem deve ser zero. Se o lpClassName for uma string, especificará o nome da classe janela. O nome da classe pode ser qualquer nome registrado com RegisterClass ou RegisterClassEx, contanto que o módulo que registra a classe seja também o módulo que cria a janela. O nome da classe pode também ser qualquer um dos nomes predefinidos da classe do sistema.
Esqueçamos a parte do átomo e vamos ler novamente a parte da string... Significa que CreateWindowEx pode criar manipuladores para classes diferentes de janela. Existem três tipos de classes da janela:
· Classes System
· Classes Application Global
· Classes Application Local
Estes tipos diferirem no escopo e quando e como são registrados e destruídos. Lendo um pouco mais do MSDN, encontramos que o Button, o ComboBox, o ListBox e outros controles são classes do sistema. Isto mostra claramente que uma classe ToolTip existe e é chamada "TOOLTIPS_CLASS".
Como a função CreateWindowEx claramente o indica, esta classe deve ser registrada e isto deve acontecer quando a common control dynamic-link library (Comctl32.dll) for carregada.
Se a função for bem sucedida, o valor de retorno é um manipulador para o novo tooltip. Com este número único, podemos comunicar-nos com o balão utilizando mensagens windows e configurando-o.
A Exibição do Balloon
A comunicação ocorre graças à existência das Windows Procedures. Cada janela tem uma função associada que processa todas as mensagens enviadas ou postadas a todas as janelas da classe. Todos os aspectos da aparência e do comportamento da janela dependem da resposta das Windows Procedures para estas mensagens, e a janela tooltip de balão não é exceção.
Estas mensagens são emitidas normalmente pelo sistema, porém o usuário pode utilizar a função SendMessage para chamar as Windows Procedures para uma janela especificada.
//Sintaxe MSDN:
LRESULT SendMessage(
HWND hWnd, UINT Msg,
WPARAM wParam, LPARAM lParam
);
· hWnd - é o manipulador da janela cujo window procedure receberá a mensagem.
· msg - especifica a mensagem a ser enviada.
· wParam - específica informações de mensagens específicas adicionais.
· lParam - específica informações de mensagens específicas adicionais.
O valor de retorno especifica o resultado da mensagem processada e depende da mensagem emitida. Todas as mensagens que podem ser emitidas para o Tooltip estão declaradas e podem ser encontradas em "CommCtrl.h". Vejamos algumas:
#define TTM_ACTIVATE (WM_USER + 1)
#define TTM_SETDELAYTIME (WM_USER + 3)
#define TTM_ADDTOOLA (WM_USER + 4)
#define TTM_ADDTOOLW (WM_USER + 50)
#define TTM_DELTOOLA (WM_USER + 5)
#define TTM_DELTOOLW (WM_USER + 51)
#define TTM_NEWTOOLRECTA (WM_USER + 6)
#define TTM_NEWTOOLRECTW (WM_USER + 52)
#define TTM_RELAYEVENT (WM_USER + 7)
//* WM_USER is just a constant. It's value is 0x400.
//* Messages ranging from [WM_USER-0x7FFF] are for use
// by private window classes, like the ToolTip class.
Como a função SendMessage claramente declara, cada mensagem tem seus próprios parâmetros, da mesma forma que TTMessage. Este projeto mostra como utilizar a maioria delas, e ainda mais, mostra como trabalhar com elas utilizando o C#.
Desvantagens
Existem diversas razões para não utilizarmos os tips de Balloon, e achamos correto mencioná-las.
Somente três tipos de ícones podem ser exibidos. Se não houver um título, nenhum ícone será exibido. Colocá-los via código em um ponto particular da tela pode ser um pouco difícil.
Não é possível escolher a posição do tip, normalmente é exibido acima. A posição do tip é calculada quando o balão é exibido e depende da posição da tela do balão. Descobrimos uma maneira de exibir o balão em qualquer lugar para baixo, utilizando a função MoveWindow do WinAPI.
Pontos de Interesse
A fim de compreendermos bem este artigo e seus códigos fonte, deveremos ter algum conhecimento das funções WinAPI, das constantes e estruturas. Se tudo isto for novidade, existem alguns artigos no CodeProject e muita informação na Web. Recomendamos dos sites, sendo que as aplicações que hospedam tem sido enormemente úteis quando desenvolvemos o material do WinAPI.A página home de API Guide, com exemplos de VB incluidos.
A home da APIViewer, incluindo valores das constantes, estruturas e definições de enumerações, bem como a sintaxe das funções da API.
Conclusão
Este artigo descreve apenas superficialmente um controle tooltip de balão, o código fonte fornecido ajudar-lhe-á a compreendê-lo muito melhor, portanto não duvide em fazer o download e aprender. Existe sempre algo mais a ser feito, o MSDN tem a informação e há muita ajuda de usuários como nós na Web, apenas temos que mergulhar na ajuda e no código fornecido e encontrar nosso caminho. Não é fácil nem rápido. Este artigo (inclusive o projeto) tomou-me muito tempo para obter a rodar, porém no final, todos saem ganhando.