Desenvolvimento - Mobile

Funcionalidades Esquecidas do Compact Framework 3 - Adicionando ToolTips a uma ToolBar

Enquanto nossos clientes não trocam seus devices por modelos mais novos temos de conviver ainda com o Compact Framework 1.0 no nosso dia-a-dia. Porém, não é por causa disso que não podemos adicionar alguns recursos legais do Compact Framework 2.0 a essas aplicações mais “antigas”.

por José Antonio Leal de Farias



Introdução

Enquanto nossos clientes não trocam seus devices por modelos mais novos temos de conviver ainda com o Compact Framework 1.0 no nosso dia-a-dia. Porém, não é por causa disso que não podemos adicionar alguns recursos legais do Compact Framework 2.0 a essas aplicações mais "antigas".

Uma das mais legais, com certeza, são os "tooltips", aqueles balõezinhos que aparecem quando seguramos a caneta em cima de um botão na barra de botões...Impossível no Compact Framework 1.0? Não! Ele também foi apenas "esquecido".

Programando

Antes de tudo, vamos ver como essas tooltips são adicionadas se usássemos a API do Windows Mobile diretamente.

A maneira correta de fazer isso é criar um controle ToolTip para cada botão da Toolbar e passar seu handle para a mensagem TB_SETTOOLTIPS no parâmetro wParam. Sei que isso soa terrivelmente complexo, mas existe um meio simples de se fazer isso que está listado na documentação do Windows CE como "legacy", ou seja, legado, mas que ainda é suportado até no Windows CE 5.0.

Você pode enviar a mensagem TB_SETTOOLTIPS passando um array de tooltip strings no parâmetro lParam da mensagem e a quantidade de strings no parâmetro wParam e o controle de Toolbar irá criar os controles de tooltips automaticamente. Isso evita a complexidade de criar o controle de Tooltip em si, fazendo que trabalhemos apenas com strings. Bem mais fácil.

Finalmente, temos de lembrar de modificar o estilo (através da mensagem TB_SETSTYLE) da Toolbar para incluir TBS_TOOLTIP e fazer toda a operação listada aqui antes de incluir os botões na Toolbar.

De posse desse conhecimento, vamos criar algumas funções P/Invoke de que iremos precisar:

[DllImport("coredll")]
extern static IntPtr GetCapture();
[DllImport("coredll")]
extern static IntPtr LocalAlloc(int flags, int size);
[DllImport("coredll")]
extern static IntPtr LocalFree(IntPtr p);
[DllImport("aygshell")]
extern static IntPtr SHFindMenuBar(IntPtr hwnd);
[DllImport("coredll")]
extern static int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
[DllImport("coredll")]
extern static int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
// Constantes da API
const int TB_SETTOOLTIPS = (WM_USER + 81);
const int TB_SETSTYLE = (WM_USER + 56);
const int TB_GETSTYLE = (WM_USER + 57);
const int TBSTYLE_TOOLTIPS = 0x0100;
const int WM_USER = 0x0400;

Agora que temos os métodos e constantes necessárias podemos chamar as funções. Como já foi dito, o controle de Toolbox espera receber um array de strings, porém existe um problema em se trabalhar com esse tipo de objeto em código não-gerenciado: Como em C++ essa strings são na realidade apontadores, o Compact Framework não é capaz de criar esses apontadores por si. Ao invés disso, vamos criar um código especial que irá criar um apontador para um array de strings. Esse método está listado abaixo:

/// <summary>
/// Aloca um vetor de strings em uma memória não gerenciada
/// </summary>
/// <param name="array">Array de strings gerenciadas</param>
/// <returns>Apontador para o array não-gerenciado</returns>
private IntPtr AllocateStringArray(string[] array)
{
	const int ptrSize = 4;
	int bufferSize = ptrSize * array.Length;
	for( int i = 0; i < array.Length; i++ )
	{
		bufferSize += (array[i].Length + 1) * Marshal.SystemDefaultCharSize; 
	}
	IntPtr pBuffer = LocalAlloc(0x40, bufferSize);
	IntPtr pPtr = pBuffer;
	IntPtr pData = new IntPtr( pBuffer.ToInt32() + ptrSize * array.Length );
	for( int i = 0; i < array.Length; i++ )
	{
		Marshal.WriteInt32(pPtr, pData.ToInt32());
		byte[] arrStr = Encoding.Unicode.GetBytes(array[i]);
		Marshal.Copy(arrStr, 0, pData, arrStr.Length);
		pPtr = new IntPtr(pPtr.ToInt32() + ptrSize);
		pData = new IntPtr(pData.ToInt32() + arrStr.Length + Marshal.SystemDefaultCharSize);
	}
	return pBuffer;
}

Não vou entrar em detalhes de como copiar informações entre o mundo gerenciado e o não gerenciado..isso é assunto para outro artigo.

Então, para adicionar os tootips precisamos criar um array de strings, convertê-los em um array não-gerenciado e usar o parâmetro lParam da mensagem TB_SETTOOLTIPS. Lembre-se também de que o controle Toolbox precisa que esse array não-gerenciado permaneça em memória enquanto a Toolbar ainda estiver visível. Libere o array apenas quando sua aplicação ou formulário for fechado ou os Tooltips mudarem usando o método LocalFree.

Seguindo esse raciocínio, método OnLoad do formulário que contém a Toolbar é algo como o código abaixo:

private IntPtr m_pLabels;

private string[] m_labels = new string[] 
{ "Button1", "Button2", "Button3" };

// Obtem o handle da Toolbar
this.Capture = true;
IntPtr hWndForm = GetCapture();
this.Capture = false;
IntPtr hWndToolbar = SHFindMenuBar(hWndForm);

// Envia a mensagem para informar a Toolbar o uso das Tooltips
SendMessage(hWndToolbar, TB_SETSTYLE, 0, 
	SendMessage(hWndToolbar, TB_GETSTYLE, 0, 0) | TBSTYLE_TOOLTIPS);
SendMessage(hWndToolbar, TB_GETSTYLE, 0, 0);

// Envia os tooltips para a Toolbar
m_pLabels = AllocateStringArray(m_labels);
SendMessage(hWndToolbar, TB_SETTOOLTIPS, m_labels.Length, m_pLabels);

Que resulta em algo como a figura abaixo:

Legal!

É isso. Agora você pode dar um toque bastante sofisticado a suas aplicações. Até a próxima!

José Antonio Leal de Farias

José Antonio Leal de Farias - Bacharel em ciências da computação pela UFCG e atualmente é Gerente de Pesquisa e Desenvolvimento da Light Infocon S.A. É programador profissional nas linguagens C++ e C#, líder do grupo de usuários CGSharp e adora Smartphones!
Veja seu blog em
http://thespoke.net/blogs/jalf/default.aspx.