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.