Artigos Clube Delphi

Transcrição

Artigos Clube Delphi
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de
São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.
Explorando APIs do Windows em Delphi – Parte
2
Nesta segunda parte da série veremos como trabalhar com as
APIs do Windows para manipulação de eventos do mouse,
impressoras e execução de áudio.
Fique por dentro
Neste artigo continuaremos explorando os recursos da API do Windows para executar algumas tarefas
comuns no dia a dia do programador, como a manipulação de impressoras, mouse e sons diretamente.
O conhecimento dessa API é bastante útil por permitir ao desenvolvedor agregar às suas aplicações
algumas funcionalidades que estão disponíveis diretamente no sistema operacional, sem precisar
reescrevê-las ou utilizar componentes de terceiros.
Nesta segunda parte do artigo iremos explorar mais algumas categorias de recursos da API do Windows
que em algum momento a maior parte dos desenvolvedores precisa utilizar, como:
· Mouse;
· Impressoras;
· Desenhando Formas;
· Manipulando Sons;
· Usando ShellExecute;
· Manipulando a Ajuda do Windows.
Mouse
Nesta categoria temos funções que nos permitem obter informações sobre o cursor do mouse, bem como
defini-las, simulando movimentos e cliques dos botões através de parâmetros que indicam o tipo de ação
executada, as coordenadas e quais botões foram utilizados. Entre essas funções, temos algumas que
merecem maior destaque por serem mais utilizadas.
Função mouse_event
A função mouse_event está declarada na DLL "user32.dll" e é utilizada para sintetizar o movimento do
mouse e o clique de seus botões, enviando essas informações para a entrada do sistema, de forma a
simular uma ação real sobre o mouse. Sua assinatura é a que vemos na Listagem 1.
Listagem 1. Assinatura da função mouse_event
01 (ByVal dwFlags As Long,
02 ByVal dx As Long,
03 ByVal dy As Long,
04 ByVal dwData As Long,
05 ByVal dwExtraInfo As Long)
Os argumentos dessa função são descritos a seguir.
· dwFlags: Esse parâmetro representa uma combinação de valores (flags) que representam as
informações de movimento e clique do mouse que devem ser enviados para o sistema, simulando uma
ação real. Essas flags são representadas pelas seguintes constantes:
o MOUSEEVENTF_ABSOLUTE: Essa flag indica que os parâmetros DX e DY contêm as
coordenadas absolutas do mouse. No sistema de coordenadas utilizado pela função, o canto da tela
superior esquerdo da tela tem as coordenadas (0,0) e o canto inferior direito tem coordenadas
(65535,65535), independentemente do tamanho da tela real.
Se este sinalizador não for definido, DX e DY contêm coordenadas relativas, cuja quantidade de
movimento real depende das configurações de velocidade e aceleração atuais do mouse;
o MOUSEEVENTF_LEFTDOWN: Indica que o botão esquerdo do mouse foi pressionado;
o MOUSEEVENTF_LEFTUP: Indica que o botão esquerdo foi liberado;
o MOUSEEVENTF_MIDDLEDOWN: O botão do meio foi pressionado;
o MOUSEEVENTF_MIDDLEUP: O botão do meio foi liberado.
o MOUSEEVENTF_MOVE: Indica que o mouse se moveu. Neste caso, os parâmetros DX e DY
especificam a quantidade, ou a localização do movimento;
o MOUSEEVENTF_RIGHTDOWN: O botão direito foi pressionado;
o MOUSEEVENTF_RIGHTUP: O botão direito foi liberado;
o MOUSEEVENTF_WHEEL: Indica que a “roda” foi movida e o parâmetro dwData especifica a
quantidade de movimento;
o MOUSEEVENTF_XDOWN: Um botão X foi pressionado. O parâmetro dwData identifica quais
botões X;
o MOUSEEVENTF_XUP: Um botão X foi liberado. O parâmetro dwData identifica quais botões X.
· dX: Especifica a coordenada X absoluta do mouse ou a quantidade de movimento relativo ao eixo X.
Para o movimento relativo, valores positivos movem para a direita e os valores negativos movem para a
esquerda;
· dY: Especifica a coordenada Y absoluta do mouse ou a quantidade de movimento relativo ao eixo Y.
Valores positivos movem para baixo e os valores negativos movem para cima;
· dwData: Se dwFlags contém MOUSEEVENTF_WHEEL, esse parâmetro especifica a quantidade de
movimento da roda, em múltiplos inteiros de WHEEL_DATA.
Valores positivos significam rotação para a frente e valores negativos significam rotação para trás. Se
dwFlags contém qualquer MOUSEEVENTF_XDOWN ou MOUSEEVENTF_XUP, esse parâmetro
indica qual botão X foi pressionado ou solto, podendo receber um dos seguintes valores:
o Xbutton1: O primeiro botão X foi pressionado ou solto;
o Xbutton2: O segundo botão X foi pressionado ou solto.
· DwExtraInfo: Um valor de 32 bits adicional associado com o evento mouse, que pode ser acessado
através da função GetMessageExtraInfo quando for necessário.
É importante destacar que o argumento dwFlags pode receber um ou mais desses valores que foram
apresentados, uma vez que representam flags. Quando for necessário passar mais de um valor, eles
devem ser separados pelo operador OR, assim serão interpretados como um conjunto. Por exemplo, se
utilizássemos a seguinte sintaxe, faríamos com que o cursor do mouse fosse movido para a posição
(0,0):
mouse_event(MOUSEEVENTF_MOVE Or MOUSEEVENTF_ABSOLUTE, 0, 0, 0, 0);
Função GetDoubleClickTime
A função GetDoubleClickTime também está declarada em "user32.dll" e sua assinatura é a seguinte:
() As Long
Ou seja, é uma função que não recebe parâmetros e retorna um valor do tipo Long indicando o tempo
máximo (em milissegundos) permitido entre cliques sucessivos do mouse para que o Windows
interprete como um duplo clique.
Criando uma aplicação para manipular o mouse
Para praticar esses conceitos, vamos criar uma aplicação do tipo VCL Forms, cuja interface do form
principal será com posta por cinco TLabels, cinco TButons, e um Tmemo, como vemos na Figura 1.
Cada um desses botões irá realizar uma chamada à função mouse_event, passando em cada caso
argumentos diferentes. Na Listagem 2 temos o código do evento OnClick dos botões.
Figura 1. Tela do aplicativo para movimentação do mouse.
Listagem 2. Implementando os códigos dos botões.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
procedure TFrm_Principal.Btn_DpClickClick(Sender: TObject);
begin
ShowMessage('O Tempo Máximo em MileSegundos para duplo clique é '
+ IntToStr(GetDoubleClickTime));
end;
procedure TFrm_Principal.Btn_ClickDirClick(Sender: TObject);
var
Pt: TPoint;
begin
Pt.x := 0;
Pt.y := 30;
mouse_event(MOUSEEVENTF_MOVE, Pt.x, Pt.y, 0, 0);
mouse_event(MOUSEEVENTF_RIGHTDOWN,Pt.x, Pt.y, 0, 0);
mouse_event(MOUSEEVENTF_RIGHTUP,Pt.x, Pt.y, 0, 0);
end;
procedure TFrm_Principal.Btn_ClickEsqClick(Sender: TObject);
var
Pt: TPoint;
begin
mouse_event(MOUSEEVENTF_MOVE Or MOUSEEVENTF_ABSOLUTE, 400, 65000, 0, 0);
mouse_event(MOUSEEVENTF_LEFTDOWN, Pt.x, Pt.y, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, Pt.x, Pt.y, 0, 0);
end;
procedure TFrm_Principal.Btn_MovSupClick(Sender: TObject);
begin
mouse_event(MOUSEEVENTF_MOVE Or MOUSEEVENTF_ABSOLUTE, 0, 0, 0, 0);
32
33
34
35
36
37
end;
procedure TFrm_Principal.Btn_MovInfClick(Sender: TObject);
begin
mouse_event(MOUSEEVENTF_MOVE Or MOUSEEVENTF_ABSOLUTE, 65000, 65000, 0, 0);
end;
Perceba que em alguns casos informamos mais de um valor para o parâmetro dwFlag, dessa forma
obtemos o movimento desejado.
Impressora
Trabalhar com a impressora é uma tarefa comum em grande parte das aplicações comerciais
desenvolvidas em Delphi. Na API do Windows temos uma função que facilita a interação com as
impressoras que estão instaladas no sistema.
Função EnumPrinters
A função EnumPrinters está declarada em "winspool.drv" e a sua assinatura pode ser vista na Listagem
3.
Listagem 3. Assinatura da função EnumPrinters
01 EnumPrinters (
02
ByVal Flags As Long,
03
ByVal Name As String,
04
ByVal Level As Long,
05
ByVal pPrinterEnum As Long,
06
ByVal ByVal cdBuf As Long,
07
ByVal pcbNeeded As Long,
08
ByVal pcReturned As Long
09 ) As Long
Essa função encontra e retorna informações sobre uma ou mais impressoras a que o computador tem
acesso, incluindo tanto as impressoras locais (fisicamente conectadas à máquina), quanto as impressoras
de rede. Os argumentos dessa função são descritos a seguir.
· Flags: De forma semelhante à função mouse_event, o argumento Flags recebe um ou mais dos
seguintes valores, que indicam que tipo de impressora se deseja listar:
o PRINTER_ENUM_CONNECTIONS: Obtém informações sobre as impressoras de rede com as
quais o computador faz conexões;
o PRINTER_ENUM_DEFAULT: Obter informações sobre a impressora padrão do computador;
o PRINTER_ENUM_LOCAL: Obter informações sobre as impressoras locais (aqueles diretamente
ligados ao sistema);
o PRINTER_ENUM_NAME: Lista informações sobre todas as impressoras sob o domínio de rede
especificado pelo nome;
o PRINTER_ENUM_NETWORK: Obtém informações sobre todas as impressoras sob o domínio do
computador na rede. Isso só funciona se a estrutura PRINTER_INFO_1 for passada no argumento
Level;
o PRINTER_ENUM_REMOTE: Possui o mesmo funcionamento que
PRINTER_ENUM_NETWORK;
o PRINTER_ENUM_SHARED: Obter informações sobre todas as impressoras com o atributo
compartilhado.
· Name: Este argumento indica o nome de domínio de rede utilizado para pesquisar as impressoras, se
for o caso, dependendo do valor passado no parâmetro Flags. Se esse parâmetro não for utilizado, devese passar uma string vazia;
· Level: Especifica qual estrutura deve ser usada para obter as informações das impressoras. Esse valor
pode ser PRINTER_INFO_1, PRINTER_INFO_2 ou PRINTER_INFO_4;
· pPrinterEnum: Uma matriz que recebe toda a informação encontrada pela função. Após invocar a
função, o valor desse argumento precisa ser copiado manualmente para uma estrutura PRINTER_INFO_
*;
· cdBuf: O tamanho em bytes da matriz passada como pPrinterEnum;
· pcbNeeded: Se a chamada à função for bem sucedida, esse argumento recebe o número de bytes de
informação retornado pela função. Se a função não tiver êxito, esse argumento recebe o número de bytes
que pPrinterEnum deve ter a fim de receber todas as informações;
· pcReturned: Recebe o número de impressoras encontradas pela função.
Uma situação comum em que se pode utilizar essa função é na listagem de impressoras para o usuário
escolher em qual deve imprimir um relatório, por exemplo.
Criando uma aplicação para listar as impressoras
Criaremos agora uma aplicação VCL Forms cuja interface é composta apenas por um TListBox, e um
TBitBtn, conforme vemos na Figura 2. Nesta aplicação listaremos as impressoras no listbox ao clicar no
botão, de acordo com o código da Listagem 4.
Figura 2. Tela do aplicativo para listagem de impressoras.
Listagem 4. Listando impressoras na aplicação
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function TFrm_Principal.ImpreRede(var Str: PChar): PChar;
var
P: PChar;
begin
Result := Str;
if Str = nil then Exit;
P := Str;
while P^ = ' ' do Inc(P);
Result := P;
while (P^ <> #0) and (P^ <> ',') do Inc(P);
if P^ = ',' then
begin
P^ := #0;
Inc(P);
end;
Str := P;
end;
function TFrm_Principal.PegaImpressora: TStrings;
var
LinAtu, Porta: PChar;
Buffer, PrinterInfo: PChar;
Flags, Count, NumInfo: DWORD;
I: Integer;
Level: Byte;
begin
if ListImpre = nil then
begin
ListImpre := TStringList.Create;
Result
:= ListImpre;
31
Try
32
if Win32Platform = VER_PLATFORM_WIN32_NT then
33
begin
34
Flags := PRINTER_ENUM_CONNECTIONS or PRINTER_ENUM_LOCAL;
35
Level := 4;
36
end
37
else
38
begin
39
Flags := PRINTER_ENUM_LOCAL;
40
Level := 5;
41
end;
42
Count := 0;
43
EnumPrinters(Flags, nil, Level, nil, 0, Count, NumInfo);
44
if Count = 0 then Exit;
45
GetMem(Buffer, Count);
46
Try
47
if not EnumPrinters(Flags, nil, Level, PByte(Buffer), Count, Count,
NumInfo) then
48
Exit;
49
PrinterInfo := Buffer;
50
for I := 0 to NumInfo - 1 do
51
begin
52
if Level = 4 then
53
with PPrinterInfo4(PrinterInfo)^ do
54
begin
55
ListImpre.AddObject(pPrinterName, Lbx_Impressora);
56
Inc(PrinterInfo, sizeof(TPrinterInfo4));
57
end
58
else
59
with PPrinterInfo5(PrinterInfo)^ do
60
begin
61
LinAtu := pPortName;
62
Porta := ImpreRede(LinAtu);
63
while Porta^ <> #0 do
64
begin
65
ListImpre.AddObject(pPrinterName +' '+ Porta, Lbx_Impressora);
66
Porta := ImpreRede(LinAtu);
67
end;
68
Inc(PrinterInfo, sizeof(TPrinterInfo5));
69
end;
70
end;
71
Finally
72
FreeMem(Buffer, Count);
73
end;
74
Except
75
ListImpre.Free;
76
ListImpre := nil;
77
Raise;
78
end;
79
end;
80
81
Result := ListImpre;
82 end;
Na seção uses é necessário incluir as units WinSpool e Printers. Em seguida, devemos declarar na seção
private uma variável do tipo TStrings e duas funções que farão a listagem dos dados, da seguinte forma:
ListImpre: TStrings;
function PegaImpressora: TStrings;
function ImpreRede(var Str: PChar): PChar;
A função ImpreRede, será utilizada para obter as impressoras que estão na rede, já a função
PegaImpressora irá listar as impressoras tanto locais como as da rede.
Após a declaração e implementação das funções, basta chamarmos no click do nosso botão, adicionando
o resultado ao listbox, da seguinte forma:
Lbx_Impressora.Items.AddStrings(PegaImpressora);
Reproduzindo Sons
Reproduzir sons em uma aplicação pode ter diversas utilidades, tais como notificar o usuário sobre um
evento ou tocar uma música característica da aplicação enquanto um processo é executado, como um
arquivo de instruções em áudio.
Função PlaySound
A função PlaySound, declarada na DLL "winmm.dll", permite executar um som no formato Wave, que
pode ser inclusive um arquivo embutido na aplicação como recurso. A assinatura dessa função é a que
vemos na Listagem 5.
Listagem 5. Assinatura da função PlaySound
01 (
02
ByVal lpszName as string,
03
ByVal hModule as Long,
04
ByVal dwFlags as Long
05 ) as Long
O retorno dessa função é 0 se um erro ocorreu, ou um valor diferente de zero se o processo foi bemsucedido (tocou o som). Os argumentos são os seguintes:
· lpszName: O nome ou algum outro identificador do som. O formato exato desse parâmetro vai
depender dos valores passados em dwFlags;
· hModule: Um identificador para a aplicação onde está o som a ser tocado (quando for um recurso). Se
a função não necessitar desta informação, passe 0 para este parâmetro;
· dwFlags: Este argumento recebe zero ou um dos seguintes valores, especificando como lpszName irá
tocar o som:
o SND_ALIAS: lpszName é uma string identificando o nome do evento do sistema a tocar;
o SND_ALIAS_ID: lpszName é uma string com o nome do identificador predefinido para tocar;
o SND_APPLICATION: lpszName é uma string identificando a aplicação específica associada ao som
a ser tocado;
o SND_ASYNC: Essa flag faz com que seja executado um som em modo assíncrono, com o controle
retornando para a função assim que o som começa a ser tocado. O som fica então sendo executado em
segundo plano;
o SND_FILENAME: lpszName é uma string identificando o nome do arquivo .wav para tocar;
o SND_LOOP: Esse valor faz tocar o som em um loop até que essa função seja chamada outra vez.
Neste caso, SND_ASYNC também deve ser especificado;
o SND_MEMORY: Quando esse valor é informado, lpszName representa um ponteiro numérico que
aponta para a posição da memória onde está armazenado o som a ser tocado;
o SND_NODEFAULT: Se o som especificado não pode ser encontrado, a função é terminada com
falha. Se este parâmetro não for especificado, o som SystemDefault é usado se o som especificado não
for localizado e a função voltará com sucesso;
o SND_NOSTOP: Se um som está tocando, faz o som parar de tocar, ou retorna uma falha se não
conseguir parar;
o SND_NOWAIT: Se um som já está tocando, não espera o som para de tocar e devolve uma
mensagem de erro;
o SND_PURGE: Pare a repetição de qualquer som no formato wave. Neste caso lpszName deve ser
uma string vazia;
o SND_RESOURCE: lpszName é o identificador numérico do som armazenado em um recurso;
o SND_SYNC: Executa o som em modo síncrono e não retorna o controle do fluxo para a função até o
som ter terminado.
Função Beep
A função Beep está declarada em "kernel32.dll" e sua assinatura pode ser vista na Listagem 6.
Listagem 6. Assinatura da função Beep
01 (
02
ByVal dwFreq As Long,
03
ByVal dwDuration As Long
04 ) As Long
Essa função reproduz um som padrão do sistema operacional, o qual varia de acordo com a versão do
Windows. Com sua chamada sempre é executado o som do sistema SystemDefault, independentemente
dos valores passados.
Se ocorrer um erro, a função retorna 0 (e neste caso utiliza-se a função GetLastError para obter o código
de erro), já se for bem-sucedida, a função retorna um valor diferente de zero. Os argumentos dessa
função são bem simples e descritos a seguir.
· dwFreq: A frequência, em hertz (Hz), do tom a ser tocado;
· dwDuration: A duração, em milissegundos, para tocar o som.
Criando uma aplicação para reprodução de sons
Criaremos agora uma aplicação de exemplo onde veremos como utilizar as funções PlaySound e Beep.
Inicie uma nova aplicação do tipo VCL Forms e monte sua interface de acordo com a Figura 3.
Figura 3. Tela do aplicativo para reprodução de sons.
Com a interface pronta, inclua na seção uses a unit MMSystem e altere o código do evento OnClick dos
botões de acordo com a Listagem 7.
Listagem 7. Código dos botões que reproduzem os sons
01
02
03
04
05
06
07
08
09
procedure TForm1.Btn_MusicaClick(Sender: TObject);
begin
PlaySound('C:\WINDOWS\MEDIA\ringin.wav', 0, SND_ASYNC);
end;
procedure TForm1.Btn_BeepClick(Sender: TObject);
begin
Beep;
end;
Desenhando Formas
Utilizando a API do Windows também podemos desenhar formas geométricas em nossas aplicações, o
que pode ser útil para interfaces não convencionais, que precisem de recursos visuais diferenciados, ou
mesmo para desenhar elementos ligados à identidade visual da aplicação.
Função Ellipse
A função Ellipse está declarada em "gdi32.dll" e serve para desenhar uma elipse. Sua assinatura pode ser
vista na Listagem 8.
Listagem 8. Assinatura da função Ellipse
01 (
02
ByVal hdc As Long,
03
ByVal X1 As Long,
04
ByVal Y1 As Long,
05
ByVal X2 As Long,
06
ByVal Y2 As Long
07 ) As Long
Os dois pares de coordenadas passados para a função, não são diretamente parte da própria elipse, mas
definem os limites de um retângulo no qual a elipse será incluída. A elipse é desenhada com a cor
corrente do dispositivo e é preenchida com a cor de preenchimento atual, se houver. A função retorna 0
se falhar, ou 1 se for bem-sucedida. Os argumentos são:
· hdc: O contexto do objeto dispositivo para desenhar;
· X1: A coordenada X do canto superior esquerdo do retângulo delimitador;
· Y1: A coordenada Y do canto superior esquerdo do retângulo delimitador;
· X2: A coordenada x do canto inferior direito do retângulo delimitador;
· Y2: A coordenada y do canto inferior direito do retângulo delimitador.
Função Rectangle
Também declarada na DLL “gdi32.dll”, a função Rectangle desenha um retângulo na tela e sua
assinatura e retorno são idênticos aos da função Ellipse.
Função LineTo
A função LineTo também está declarada em "gdi32.dll" e sua assinatura é vista na Listagem 9.
Listagem 9. Assinatura da função LineTo
01 (
02
ByVal hdc As Long,
03
ByVal x As Long,
04
ByVal y As Long
05 ) As Long
Essa função desenha uma linha a partir do ponto atual até o ponto especificado. A linha é desenhada na
cor especificada pela propriedade ForeColor desse objeto. Depois que o caminho é traçado, o ponto final
é o novo ponto inicial. A função retorna 0 se teve erro, ou um em caso de sucesso.
ShellExecute
Declarada na DLL "shell32.dll", a função ShellExecute tem sua declaração como vemos na Listagem
10.
Listagem 10. Assinatura da função ShellExecute
01 ShellExecute (
02
ByVal hwnd As Long,
03
ByVal lpOperation As String,
04
ByVal lpFile As String,
05
06
07
08 )
ByVal lpParameters As String,
ByVal lpDirectory As String,
ByVal nShowCmd As Long
As Long
Essa função usa o shell para abrir ou imprimir um arquivo ou ainda executar um programa. Se um
programa executável é especificado, o Windows irá executar esse programa.
Se um arquivo de documento é especificado, o Windows irá abrir ou imprimi-lo usando o programa
associado. Se for bem-sucedida, a função retorna um identificador para a instância do programa aberto
ou, no caso de impressão, um identificador para o aplicativo de servidor DDE invocado.
Se não tiver êxito, a função retorna 0 (ou seja, falta de memória ou recursos) ou um dos seguintes
parâmetros de código de erro:
· ERROR_FILE_NOT_FOUND: O arquivo especificado não pôde ser encontrado;
· ERROR_PATH_NOT_FOUND: O diretório especificado não pôde ser encontrado;
· ERROR_BAD_FORMAT: O arquivo executável especificado (.EXE) foi de alguma forma inválido;
· SE_ERR_ACCESSDENIED: Windows negou o acesso ao arquivo especificado;
· SE_ERR_ASSOCINCOMPLETE: A associação filename está incompleta ou inválido;
· SE_ERR_DDEBUSY: A ação DDE não pôde ocorrer porque outras ações DDE estão em processo;
· SE_ERR_DDEFAIL: A transação DDE falhou;
· SE_ERR_DDETIMEOUT: A transação DDE não foi concluída porque a solicitação expirou;
· SE_ERR_DLLNOTFOUND: O arquivo DLL especificado não foi encontrado;
· SE_ERR_FNF: O mesmo que ERROR_FILE_NOT_FOUND;
· SE_ERR_NOASSOC: Não há nenhum programa associado com o tipo específico de arquivo;
· SE_ERR_OOM: O Windows não tem memória suficiente para executar a operação;
· SE_ERR_PNF: O mesmo que ERROR_PATH_NOT_FOUND;
· SE_ERR_SHARE: Ocorreu violação no compartilhamento do recurso acessado.
Os parâmetros recebidos pela função são descritos a seguir.
· hwnd: O identificador da janela que irá chamar a função;
· lpOperation: A operação para executar em lpFile. "Open" significa abrir o arquivo ou executar o
programa. "Print" significa imprimir o documento;
· lpFile: O arquivo para executar a operação;
· lpParameters: Todos os parâmetros de linha de comando para passar para um aplicativo aberto;
· lpDirectory: O diretório de trabalho para a operação;
· nshowCmd: Este argumento recebe um dos seguintes valores, especificando como exibir qualquer
janela que se abre na função:
o SW_HIDE: Esconder a janela aberta;
o SW_MAXIMIZE: Maximizar a janela aberta;
o SW_MINIMIZE: Minimizar a janela aberta;
o SW_RESTORE: Restaurar a janela aberta (não maximizada nem minimizada);
o SW_SHOW: Mostrar a janela aberta;
o SW_SHOWMAXIMIZED: Mostrar a janela aberta maximizada;
o SW_SHOWMINIMIZED: Mostrar a janela aberta minimizada;
o SW_SHOWMINNOACTIVE: Mostrar a janela aberta minimizada, mas não ativa;
o SW_SHOWNA: Mostrar a janela aberta em seu estado atual, mas não ativá-la;
o SW_SHOWNOACTIVATE: Mostrar a janela aberta em seu tamanho e posição mais recente, mas
não ativá-la;
o SW_SHOWNORMAL: Mostrar a janela aberta e ativá-la (como de costume).
Essa função pode ter diversos usos nos mais variados tipos de aplicações, tais como na abertura de
arquivos externos (PDFs, por exemplo) ou execução de aplicações auxiliares para executar tarefas que
não podem ser executadas na própria aplicação.
Chamando a ajuda do Windows
A Ajuda do Windows pode ser útil para auxiliar o usuário na execução de alguma tarefa. Esse tipo de
ajuda está presente em muitas aplicações e geralmente é acessado a partir da tecla F1, não havendo,
porém, regra fixa para definição desse atalho.
Função WinHelp
A função WinHelp está declarada em "user32.dll" e sua função é a que vemos na Listagem 11.
Listagem 11. Assinatura da função WinHelp
01 WinHelp (
02
03
04
05
06 )
ByVal hwndMain As Long,
ByVal lpHelpFile As String,
ByVal uCommand As Long,
dwData As Any
As Long
WinHelp abre um arquivo de Ajuda do Windows, ou manipula o arquivo de ajuda aberto. Se ocorrer um
erro, a função retorna 0 (e usa-se GetLastError para obter o código de erro) e se for bem-sucedida a
função retorna um valor diferente de zero. Os argumentos que devem ser passados para essa função são
os seguintes:
· hwndMain: Na maioria dos casos, este argumento representa um identificador para a janela a ser
aberta. Se uCommand for passado como HELP_CONTEXTMENU ou HELP_WM_HELP, este é um
identificador para um controle específico, para abrir uma ajuda referente ao contexto;
· lpszHelp: O nome do arquivo de ajuda para exibir. O nome do arquivo pode ser seguido pelo caractere
> e o nome de uma janela de ajuda secundária (definindo o nome do arquivo de ajuda) para abrir, em
vez de abrir o primeiro;
· uCommand: Esse parâmetro recebe um dos seguintes valores, especificando qual ação a função deverá
assumir com o arquivo de ajuda:
o HELP_COMMAND: Indica que o argumento dwData possui o identificador de uma macro a ser
executada;
o HELP_CONTENTS: Exibe o tópico conteúdo do arquivo de ajuda, neste caso dwData deve ser 0.
Este parâmetro está obsoleto e deve-se usar o sinalizador HELP_FINDER.
o HELP_CONTEXT: Exibir o tópico identificado pelo valor passado como dwData.
o HELP_CONTEXTMENU: Exibir o tópico da ajuda associado com o controle selecionado da janela,
em uma janela pop-up. Neste caso dwData é uma matriz de pares de Longs (DWords).
A primeira parte é um identificador do controle e a segunda é o identificador do contexto do tópico de
ajuda associado. Os últimos dados da matriz devem ser dois zeros;
o HELP_CONTEXTPOPUP: Exibir o tópico identificado pelo valor passado como dwData em uma
janela pop-up;
o HELP_FINDER: Exibir a caixa de diálogo de tópicos de ajuda. O argumento dwData deve receber 0
neste caso;
o HELP_FORCEFILE: Certifica de que a ajuda do Windows está exibindo o arquivo de ajuda correta;
se não for, então irá exibir o correto. O valor de dwData deve ser 0;
o HELP_HELPONHELP: Mostrar a ajuda sobre como usar arquivo de ajuda do Windows, que faz
parte do Windows. O valor de dwData deve ser 0;
o HELP_INDEX: O mesmo que HELP_CONTENTS;
o HELP_KEY: Exibir o tópico de ajuda correspondente ao valor passado no argumento dwData. Várias
palavras-chave podem ser passadas, separadas por vírgula;
o HELP_MULTIKEY: Exibir o tópico especificado por uma palavra-chave em uma tabela de palavraschaves alternativa. Neste caso dwData deve ser uma estrutura MULTIKEYHELP que especifica um
identificador de palavra-chave;
o HELP_PARTIALKEY: O mesmo que HELP_KEY, exceto que para exibir o índice sem passar uma
palavra-chave, deve-se passar uma string vazia no dwData;
o HELP_QUIT: Fechar a ajuda do Windows, a menos que outros programas estejam usando;
o HELP_SETCONTENTS: Definir qual tópico de ajuda, é considerado o tema do Conteúdo. DwData é
o identificador do contexto do tema, para definir qual será o conteúdo;
o HELP_SETINDEX: O mesmo que HELP_SETCONTENTS;
o HELP_SETPOPUP_POS: Define a posição de uma janela pop-up subsequente. O parâmetro dwData
deve ser uma estrutura POINT_TYPE que identifica as coordenadas do canto superior esquerdo da
janela pop-up subsequente;
o HELP_SETWINPOS: Exibir a janela de ajuda se estiver minimizada ou oculta, e definir o seu
tamanho e posição. O dwData deve ser uma estrutura HELPWININFO, que define o tamanho e a
posição da janela de ajuda desejado;
o HELP_TCARD: Indica que o tema é para mostrar em um cartão. Este valor deve ser combinado com
outro parâmetro;
o HELP_WM_HELP: Exibir o tópico para o controle identificado por hwndMain. O dwData deve ser
uma matriz de pares de Longs (DWords). A primeira parte será um identificador do controle e a segunda
o identificador do contexto do tópico de ajuda associado. Os últimos dados da matriz devem ser dois
zeros.
· dwData: Esse argumento tem valor variável e depende do valor de uCommand.
Criando uma aplicação para chamar a Ajuda
Aqui desenvolveremos uma aplicação em que utilizaremos diversas combinações de valores para os
parâmetros. A interface da aplicação de exemplo pode ser vista na Figura 4, onde cada botão fará uma
chamada diferente à função WinHelp, conforme mostra a Listagem 12.
Figura 4. Tela do aplicativo para chamar a Ajuda do Windows.
Listagem 12. Chamando a Ajuda do Windows
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
procedure TForm1.BtnAjudaClick(Sender: TObject);
var
Ajuda: Hwnd;
begin
Ajuda := FindWindow(Nil, Pchar('windows.hlp'));
WinHelp(Ajuda, 'C:\WINDOWS\Help\windows.hlp', HELP_FINDER, 0);
end;
procedure TForm1.BtnLocalizarClick(Sender: TObject);
var
Ajuda: Hwnd;
begin
Ajuda := FindWindow(Nil, Pchar('windows.hlp'));
WinHelp(Ajuda, 'C:\WINDOWS\Help\windows.hlp', HELP_KEY, 0);
end;
procedure TForm1.BtnDriveClick(Sender: TObject);
var
Ajuda: Hwnd;
begin
Ajuda := FindWindow(Nil, Pchar('windows.hlp'));
WinHelp(Ajuda, 'C:\WINDOWS\Help\windows.hlp', HELP_CONTENTS, $000F);
end;
procedure TForm1.BtnIndiceClick(Sender: TObject);
var
Ajuda: Hwnd;
begin
Ajuda := FindWindow(Nil, Pchar('windows.hlp'));
WinHelp(Ajuda, 'C:\WINDOWS\Help\windows.hlp',HELP_PARTIALKEY ,00);
end;
32
33
34
35
36
37
38
39
procedure TForm1.BtnFecharClick(Sender: TObject);
var
Ajuda: Hwnd;
begin
Ajuda := FindWindow(Nil, Pchar('windows.hlp'));
WinHelp(Ajuda, 'C:\WINDOWS\Help\windows.hlp',HELP_QUIT , 128);
end;
O Windows oferece nativamente diversas funções bastante úteis para a implementação de certas
funcionalidades em aplicações, minando a necessidade de desenvolvimento de funções próprias para
executar tarefas comuns, como o desenho de formas geométricas e reprodução de sons.
Com essas funções podemos otimizar o funcionamento das aplicações e garantir compatibilidade com o
sistema operacional, uma vez que estamos utilizando funções nativas contidas em algumas das
principais DLLs do Windows.
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de São Paulo.
Formado em técnico em informática no ano de 2003, com diversos cursos em formação específica, como Oracle,
Delphi e C#.
Cadastros e relatórios dinâmicos em Delphi
Neste artigo é apresentada uma solução que permite aos
programadores deixar o usuário criar sozinho um novo cadastro
no sistema em Delphi, bem como uma listagem associada a esse
cadastro.
Fique por dentro
Empresas que estão ligadas ao desenvolvimento de software preocupam-se cada vez mais em construir
softwares que sejam robustos e atendam a todas as necessidades de seus clientes. Sistemas de gestão
empresarial, ERP (Enterprise Resource Planning), sempre têm a necessidade de novos módulos, ou
adequações para atender um maior público.
Módulos como financeiro, administrativo e tantos outros, geralmente possuem seus cadastros e
movimentos, e normalmente possuem uma grande quantidade de campos. Por esses e tantos outros
motivos, há vários relatórios, com campos diversificados, afim de tornar esses cadastros e movimentos
mais eficientes. Este artigo apresenta uma forma de permitir aos próprios usuários a criação de cadastros
e relatórios, tornando o sistema muito dinâmico e eficiente em diversos aspectos.
Partindo da ideia que um sistema inteligente é aquele que pode ser customizado de acordo com a
necessidade do cliente, hoje em dia não temos muitos sistemas inteligentes. Há diversos sistemas
grandes, que são construídos com base em um ramo de negócio.
Porém, quando é necessário fazer uma simples mudança em um relatório ou em um cadastro, os clientes
recebem a reposta que o sistema está estável e que diversas outras empresas usam o mesmo sem
problemas. Mas a resposta ideal à sua solicitação seria: vamos adequar, vamos fazer tais mudanças e
alterações.
Sendo assim é hora de pensar, será que realmente meu negócio está competitivo? Como deve ser a
estrutura, da modelagem de dados? Meus cadastros, relatórios e gráficos necessitam constantemente de
mudanças?
Então por que não fazer um sistema mais inteligente, ou adicionarmos um módulo ao nosso sistema que
permita ao próprio cliente, customizar e criar seus próprios relatórios e ainda indo um pouco mais longe,
por que não dar a possibilidade de ele mesmo criar cadastros. Veremos uma forma de como um cliente
pode criar cadastros e relatórios personalizados.
Para isso usaremos como banco de dados o Firebird, a ferramenta de relatório será o FastReport, e a
parte de conexão de dados usaremos unidac. Lembrando que qualquer que seja o banco de dados, ou
qualquer que seja a empresa, seu negócio tem que ser inteligente para que tenha uma maior
competitividade.
FastReport
O FastReport era uma suíte unicamente externa para geração de relatórios em Delphi. Essa suíte passou
a ser incorporada como ferramenta oficial de desenvolvimento de relatórios a partir do Delphi XE2,
possuindo uma versão própria para essa finalidade.
O FastReport possui algo muito interessante que é a conversões de relatórios Quick Report, Rave
Reports e Report Builder por meio de units. É considerado por muitos uma ótima ferramenta de geração
de relatórios.
Com ele podemos criar desde relatórios simples até os mais complexos. A suíte disponibiliza também o
FastScript que permite a criação de scripts em várias linguagens de programação, o FastReport Exports
que permite a exportação de relatórios do FastReport para diversos formatos como XLS, HTML, CSV
entre outros. Dentre seus vários recursos, da sua versão comercial, usaremos o cross-tab, para criarmos
esses relatórios personalizados.
Unidac
O UniDAC provê suporte e acesso a diversos servidores de banco de dados como Oracle, Firebird,
InterBase, Microsoft SQL Server, PostgreSQL, MySQL, entre outros. Atende a diversas ferramentas
(Delphi, C++ Builder, Lazarus e Free Pascal) em diferentes plataformas (Windows, Mac OS, iOS, Linux
e FreeBSD).
Pode se dizer que a estrutura do Unidac é composta por dois elementos. O primeiro deles seria uma
engine, ou seja, seu motor que provê ao desenvolvedor uma interface de programação comum e
unificada, receptível aos diversos bancos suportados. Já o segundo elemento é a sua parte fundamental,
que é a sua camada de acesso a dados.
Esse acesso a dados é composto pelos provedores (providers), que irão fazer a interação entre a engine e
o servidor de banco de dados. Cada provider fica então responsável por trabalhar com um servidor de
banco de dados específico. Por exemplo, o TOracleUniProvider para Oracle, TInterBaseUniProvider
para InterBase, TPostgreSQLUniProvider para PostgreSQL.
Funcionamento da aplicação
A aplicação permitirá ao usuário criar novos cadastros simples e relatórios simples no sistema sem
solicitar uma alteração do sistema ao seu programador. Através de um cadastro o usuário poderá criar a
tela, quais campos serão utilizados, etc. Será uma aplicação até relativamente simples. Teremos uma tela
principal e teremos um menu, de título Procedimentos, com os seguintes itens: Criar Cadastros, Excluir
Cadastros, Sair. Na Tabela 1 vemos as funcionalidades dos menus.
Menu
Criar Cadastro
Excluir Cadastro
Sair
Funcionalidade
Irá chamar a tela onde o usuário criará os seus cadastros. Informando o nome da
tabela, os seus campos e quais campos irão aparecer em relatório. Terá imagens
e textos informando ao usuário como fazer todo o procedimento de criação.
Irá chamar a tela onde terá uma lista dos cadastros criados. Para que o usuário
possa selecionar um cadastro a ser excluído.
Fecha a nossa aplicação.
Tabela 1. Funcionalidades dos menus
Ao lado deste menu Procedimento teremos um outro menu, Cadastros, e obviamente os itens deste
menu, serão os cadastros criados. Por exemplo, caso sejam criados dois cadastros: Clientes e Ordem de
Serviço, então esses seriam os itens deste menu.
Ao clicar sobre um menu desses, abrirá a tela do referente cadastro. Nesta tela terá uma grade de dados
utilizando um DbGrid, onde será feita a inclusão, alteração e exclusão de dados. Para facilitar toda
manipulação de dados, será utilizado um DbNavigator. Também haverá o botão imprimir, que chamará
o relatório que foi criado junto com o cadastro, escolhendo quais campos serão impressos.
Enfim, será um cadastro com relatório, com várias consistências, verificações de erros e tudo mais, para
que possa ter uma boa usabilidade.
Criando o banco de dados
Para começar, vamos fazer a criação do banco de dados da nossa aplicação. Como é possível ver na
Figura 1, teremos uma modelagem de dados simples baseada em apenas duas tabelas.
A tabela TABELA_USUPER é onde são cadastradas todas as tabelas que o usuário criou. Já a tabela
TAB_CAMPOS, é onde são cadastradas todas as informações das tabelas.
Informações como nome do campo, apelido do campo, se irá aparecer na grade de dados e no relatório,
qual o tipo do campo (Texto, Número, Moeda, Data, Hora, Data e Hora, Observação), e se o campo
deverá ou não aparecer no relatório. Para essa tabela criamos também um Generator e uma Trigger, que
serão os responsáveis para incrementar o campo código a cada novo registro.
Figura 1. Tabelas do Banco de Dados
Para o nosso banco de dados foi adotada a versão 2.1 do Firebird. O script SQL para a criação do banco
encontra-se exibido na Listagem 1.
Listagem 1. Script de criação do banco de dados
SET SQL DIALECT 3;
SET NAMES WIN1252;
CREATE DATABASE '<DIRETÓRIO DO BANCO DE DADOS>\ARTBI.fdb'
USER 'SYSDBA' PASSWORD 'masterkey'
PAGE_SIZE 16384
DEFAULT CHARACTER SET WIN1252;
CREATE GENERATOR GEN_TAB_CAMPOS;
CREATE TABLE TAB_CAMPOS (
CODIGO
INTEGER NOT NULL,
TABELA
VARCHAR(40) NOT NULL,
CAMPO
VARCHAR(30) NOT NULL,
COLUNA
VARCHAR(30),
TIPO
VARCHAR(15),
RELATORIO CHAR(1)
);
CREATE TABLE TABELA_USUPER (
TABELA
VARCHAR(40) NOT NULL,
APELIDO VARCHAR(40)
);
ALTER TABLE TABELA_USUPER ADD CONSTRAINT PK_TABUSERCODIGO PRIMARY KEY (TABELA);
ALTER TABLE TAB_CAMPOS ADD CONSTRAINT PK_TABCODIGO PRIMARY KEY (CODIGO);
ALTER TABLE TAB_CAMPOS ADD CONSTRAINT FK_CAMCODIGO FOREIGN KEY (TABELA)
REFERENCES TABELA_USUPER (TABELA) ON DELETE CASCADE ON UPDATE CASCADE;
SET TERM ^ ;
CREATE TRIGGER NEW_TAB_CAMPOS FOR TAB_CAMPOS
ACTIVE BEFORE INSERT POSITION 0
AS
begin
IF (NEW.CODIGO IS NULL) THEN
NEW.CODIGO = GEN_ID(GEN_TAB_CAMPOS, 1);
end
^
SET TERM ; ^
Criamos uma estrutura no banco de dados para armazenar de forma adequada os dados de novos
cadastros. Temos a tabela TAB_CAMPOS que armazena os campos de uma determinada tela e suas
características e a tabela TABELA_USUPER que armazena as tabelas que precisarão ser criadas no
banco de dados para armazenar os dados do novo cadastro.
Criando a aplicação
No Delphi criamos um novo projeto Win32 e salvamos a unit principal com o nome de
Unt_Principal.pas e o formulário como Frm_Principal. O projeto salvamos como CriarCadastro ou
conforme o gosto.
Adicionamos a seguir um novo formulário, salvando-o como Unt_CriaCadastro e o nomeando-o como
Frm_CriaCadastro. Agora repetindo o processo, adicionamos mais um formulário e salvamos como
Unt_ExcCadastro e o nomeamos como Frm_ExcCadastro.
Finalizando a criação dos formulários, adicionamos o último formulário e salvamos sua unit como
Unt_Cadastro, e Frm_Cadastro. Na Tabela 2 identificamos qual será a funcionalidade dos formulários.
Formulário
Frm_Principal
Frm_CriaCadastro
Frm_ExcCadastro
Frm_Cadastro
Funcionalidade
Formulário principal da aplicação, onde ficam os menus que chamam
todos os outros formulários (Telas).
Pode ser considerado o formulário mais importante, é onde será criado os
cadastros e relatórios.
Será o formulário que apresentará todos os cadastrados criados, para que
possam ser excluídos.
E por fim o nosso formulário do cadastro, onde o usuário irá cadastrar,
manipular os seus dados e chamar o seu relatório.
Tabela 2. Funcionalidades dos formulários
Programando o formulário principal
Adicionamos então aos formulários os seguintes componentes para conexão: TuniConnection, Provider:
TinterBaseUniProvider, Transação: TuniTransaction, Script: TuniScript, Qry_Tabelas: TuniQuery,
Qry_Codigo: TuniQuery. Adicionamos também outros três componentes: Mnu_Principal: TmainMenu,
Imgl_Menu: TimageList, ApeErro: TapplicationEvents. Com isso o nosso formulário principal fica
pronto para ser programado. Após a adição de todos os componentes, ele deverá ficar com a aparência
da Figura 2.
Figura 2. Formulário principal
Nota: Mais adiante vamos utilizar um TClientDataSet, então é importante adicionarmos MidasLib à
seção uses, após a interface. Com isso não é necessário distribuir o arquivo Midas.dll.
Na seção private do nosso formulário teremos também declarado, três variáveis e uma procedure. Já na
seção public teremos duas procedures e, a seguir, na seção uses do implementation, como vamos usar
todos os outros formulários, então fazemos referência a eles, como mostra a Listagem 2.
Listagem 2. Private, Public e Uses do formulário Principal
private
MenuCad,
MenuTabPer: TMenuItem;
ImgItMenu: Integer;
procedure MenuCadPerClick(Sender: TObject);
{ private declarations }
public
procedure AdicionaMenu(Menu: String);
procedure RemoveMenu(Menu: String);
{ public declarations }
end;
var
Frm_Principal: TFrm_Principal;
implementation
Uses Unt_CriaCadastro, Unt_ExcCadastro, Unt_Cadastro;
Programaremos então os itens do menu principal, presente na Figura 3. O primeiro item do menu, Criar
Cadastro, é onde iremos criar o formulário de criação de cadastros. Vamos chamarmos o cadastro e após
isso o liberamos, como mostra a Listagem 3.
Figura 3. Menu da Aplicação
Listagem 3. Item do menu Criar Cadastro
procedure TFrm_Principal.MnuI_CriaCadClick(Sender: TObject);
begin
try
if not Assigned ( Frm_CriaCadastro ) then
Frm_CriaCadastro := TFrm_CriaCadastro.Create(Self);
Frm_CriaCadastro.ShowModal;
finally
FreeAndNil(Frm_CriaCadastro);
end;
end;
Para o segundo item do menu chamaremos o formulário com os cadastros criados, para que possamos
realizar alguma exclusão, como mostra a Listagem 4.
Listagem 4. Item do menu Excluir Cadastro
procedure TFrm_Principal.MnuI_ExcCadClick(Sender: TObject);
begin
try
if not Assigned ( Frm_ExcCadastro ) then
Frm_ExcCadastro := TFrm_ExcCadastro.Create(Self);
Frm_ExcCadastro.ShowModal;
finally
FreeAndNil(Frm_ExcCadastro);
end;
end;
Por fim, no menu Sair fecharemos nossa aplicação, como mostra a Listagem 5.
Listagem 5. Item do menu Sair
procedure TFrm_Principal.MnuI_SairClick(Sender: TObject);
begin
Application.Terminate;
end;
Na Listagem 6 implementamos a procedure AdicionaMenu, que é a responsável por adicionar no menu
(Cadastros) os cadastros criados pelo usuário. Essa procedure recebe como parâmetro o nome do menu a
ser criado e é utilizada no momento em que são recuperados os cadastros criados. Essa recuperação
ocorre no evento Create do formulário principal.
Listagem 6. Implementação da procedure AdicionaMenu
procedure TFrm_Principal.AdicionaMenu(Menu: String);
var
I: Integer;
begin
if ImgItMenu = 0 then
ImgItMenu := 1
else
ImgItMenu := 0;
for I := 0 to Mnu_Principal.Items.Count - 1 do
begin
if AnsiSameCaption(Mnu_Principal.items[I].Caption, 'C&adastros') then
begin
MenuCad := Mnu_Principal.items[I];
Break;
end;
end;
MenuTabPer
MenuTabPer.Caption
MenuTabPer.ImageIndex
MenuTabPer.OnClick
:=
:=
:=
:=
TMenuItem.Create(MenuCad);
Menu;
ImgItMenu;
MenuCadPerClick;
for I := 0 to MenuCad.count - 1 do
begin
if MenuCad.Items[I].isLine then
begin
MenuCad.Insert(I, MenuTabPer);
Break;
end;
end;
end;
A Listagem 7 apresenta como são trazidos do banco de dados cada cadastro criado. Temos duas
variáveis declaradas (Path, Banco), que serão as responsáveis por indicar o caminho do nosso banco de
dados.
Após fazermos a conexão com o banco de dados, buscamos todos os cadastros criados que se encontram
na tabela TABELA_USUPER. Então, para cada registro encontrado chamamos a procedure
AdicionaMenu, para a criação do menu desse cadastro.
Listagem 7. Procedure Create do Form
procedure TFrm_Principal.formCreate(Sender: TObject);
var
Path,
Banco: String;
begin
try
Path := ExtractFiledir(paramstr(0));
Banco := Path+'\ARTBI.FDB';
Conexao.Database := Banco;
Conexao.Open;
except
on E:exception do
begin
Application.MessageBox(pansichar('Erro de Conexão'+#13+E.Message),
' Atenção', MB_OK + MB_ICONHAND);
Application.Terminate;
end;
end;
ImgItMenu := 0;
with Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('SELECT * FROM TABELA_USUPER');
Open;
end;
while not(Qry_Tabelas.Eof) do
begin
AdicionaMenu(Qry_Tabelas.FieldByName('TABELA').AsString +' - '+
Qry_Tabelas.FieldByName('APELIDO').AsString);
Qry_Tabelas.Next;
end;
end;
Para o procedimento de exclusão, ao selecionar um cadastro no formulário de exclusão de cadastro
(Frm_ExcCadastro), excluímos o mesmo e avisamos ao formulário principal que é necessário remover o
item de menu associado a esse registro.
Isso é feito pela procedure RemoveMenu, presente na Listagem 8, que recebe como parâmetro o nome
do menu a ser removido.
Listagem 8. Procedure RemoveMenu
procedure TFrm_Principal.RemoveMenu(Menu: String);
var
I: Integer;
begin
for I := 0 to Mnu_Principal.Items.Count - 1 do
begin
if AnsiSameCaption(Mnu_Principal.items[I].Caption, 'C&adastros') then
begin
MenuCad := Mnu_Principal.items[I];
Break;
end;
end;
for I := 0 to MenuCad.Count - 1 do
begin
if AnsiSameCaption(MenuCad.Items[I].Caption, Menu) then
begin
MenuCad.Remove(MenuCad.items[I]);
Break;
end;
end;
end;
Na Listagem 7 quando criamos e adicionamos um menu, configuramos que seu evento Click é
implementado pela procedure MenuCadPerClick. Essa procedure cria o formulário do cadastro e passa
para ele qual será o cadastro a ser criado, passando para ele qual tabela a ser carregada. Toda
manipulação do cadastro é apresentada na Listagem 9.
Listagem 9. Click dos Menus dos Cadastrados Criados
procedure TFrm_Principal.MenuCadPerClick(Sender: TObject);
begin
try
if not Assigned ( Frm_Cadastro ) then
Frm_Cadastro := TFrm_Cadastro.Create(Self);
Frm_Cadastro.Tabela := TMenuItem(Sender).Caption;
Frm_Cadastro.ShowModal;
finally
FreeAndNil(Frm_Cadastro);
end;
end;
O tratamento de erros foi todo centralizado através componente TApplicationEvents. Nele colocamos
mensagens personalizadas para erros, como “is not a valid date”, “Input value”, “Insufficient memory
for this operation”, etc. O componente TApplicationEvents pode capturar os eventos da aplicação e um
desses eventos é o evento de exceção, ou seja, sempre que uma exceção for levantada ela passará por
esse evento, que é onde realizamos toda a tratativa. Fazemos isso conforme a Listagem 10.
Listagem 10. Mensagens personalizadas, para erro ou falhas da aplicação
procedure TFrm_Principal.ApeErroexception(Sender: TObject; E: exception);
var
Mensagem: String;
Pos1, Pos2: Integer;
begin
if Pos('is not a valid date', E.Message) > 0 then
Application.MessageBox('Data Invalida !',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('is not a valid time', E.Message) > 0 then
Application.MessageBox('Hora Invalida !',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Cannot perform this operation on an empty dataset', E.Message) > 0 then
Application.MessageBox('Sem Dados para excluir!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Invalid input value', E.Message) > 0 then
Application.MessageBox('Informe o Campo Corretamente!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Input value', E.Message) > 0 then
Application.MessageBox('Informe o Campo Corretamente!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Erro ApplyUpdates', E.Message) > 0 then
Application.MessageBox('Erro ao Gravar no Banco de Dados!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos(UpperCase('is not a valid float'), UpperCase(E.Message)) > 0 then
begin
Pos1
:= Pos('''', E.Message);
Mensagem := E.Message;
Delete(Mensagem, Pos1, 1);
Pos2
:= Pos('''', mensagem);
mensagem := Copy(E.Message, Pos1 + 1, Pos2 - Pos1);
mensagem := 'O valor '+ mensagem + ' não é válido.';
Application.MessageBox(pansichar(mensagem),
'Atenção', MB_OK + MB_ICONWARNING )
end
else
if Pos('Dataset not in edit or insert mode', E.Message) > 0 then
Application.MessageBox('Tabela Não está em modo de edição!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Error creating cursor handle', E.Message) > 0 then
Application.MessageBox('Operação realizada com Sucesso !',
'Parabéns', MB_OK + MB_ICONWARNING )
else
if pos(' No current record', E.message)> 0 then
Application.MessageBox('Nenhum Registro Atual !',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('File Delete operation failed', E.Message) > 0 then
Application.MessageBox('Falha na Operação de Exclusão de Arquivo! !',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Access to table disabled because of previous error', E.Message) > 0
then
Application.MessageBox('Acesso à Tabela desativado por causa de Erro
Anterior!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Insufficient memory for this operation', E.Message) > 0 then
Application.MessageBox('Memória Insuficiente para esta Operação!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Insufficient disk space', E.Message) > 0 then
Application.MessageBox('Espaço em disco Insuficiente!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Invalid table delete request', E.Message) > 0 then
Application.MessageBox('Pedido de Apagar inválido!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('not enough memory', E.Message) > 0 then
Application.MessageBox('Memória Insuficiente!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Table is open', E.Message) > 0 then
Application.MessageBox('Tabela Está Aberta!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Socket Error # 10061 Connection refused.', E.Message) > 0 then
Application.MessageBox('Erro de Conexão!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Socket Error # 10060', E.Message) > 0 then
Application.MessageBox('Erro de Conexão!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if Pos('Socket Error # 11001 Host not found.', E.Message) > 0 then
Application.MessageBox('Erro No Host!',
'Atenção', MB_OK + MB_ICONWARNING )
else
if (Copy(E.Message, 1, 27)= 'Access violation at address') then
begin
Application.MessageBox('Ocorreu erro de Violação de Acesso.',
'Atenção', MB_OK + MB_ICONWARNING )
end
else
begin
Mensagem := 'Ocorreu o seguinte erro: ' + #13 +UpperCase(E.Message);
Application.MessageBox(pansichar(mensagem),
'Atenção', MB_OK + MB_ICONWARNING )
end;
end;
end.
A partir desse momento vamos tratar da criação das tabelas no banco de dados. Os procedimentos a
seguir são essenciais para o projeto e embora possam parecer complexos, com a devida atenção, não o
são.
Na seção private do formulário Frm_CriaCadatro temos oito procedures e quatro functions, como na
Tabela 3, onde é possível identificar o nome e a finalidade de cada uma delas.
Procedimento / Função
procedure Replace_Campos;
Finalidade
O script para a criação da tabela, no banco de dados,
é montado em um memo (Mmo_Script). Essa
procedure troca os textos para o tipo do campo. Ex:
(Data para DATE, Texto para VARCHAR, Moeda
para DOUBLE PRECISION, Hora para TIME, etc.)
procedure Add_Texto;
procedure Add_Camps;
procedure MontaScript;
procedure CriaTabela;
procedure InserirTabela;
Adiciona para o Mmo_Script um campo do tipo
texto junto com o seu tamanho informado.
Ex: CIDADE VARCHAR (80)
Adiciona para o Mmo_Script, os demais tipos de
campos, que não precisam de nenhuma outra
informação, como o caso do texto, que tem que se
informar um tamanho, para o mesmo.
Ex: TIME, DATE, TIMESTAMP
Monta o script para ser executado depois. Cria uma
sentença CREATE TABLE + o nome da tabela
informada. Logo em seguida cria a chave primária
da tabela (‘CODIGO CHAR(6) not NULL). Então
percorre todos os campos que o usuário informou.
Esses campos que estão num ClientDataSet (Dados:
TclientDataSet). Os campos são adicionados ao
Mmo_Script. Se for campo do tipo texto, é utilizado
Add_Texto, se não o Add_Camps. Por último
determina que o campo (CODIGO) vai ser a chave
primária e usa o Replace_Campos, para terminar a
finalização do Script.
Aqui então é a criação da tabela, o conteúdo do
Mmo_Script é transferido para o componente de
script do formulário principal (Script: TuniScript).
Então executará o mesmo para que seja criada essa
tabela.
Neste procedimento é onde criamos o cadastro em
si. Aqui inserimos o nome do nosso cadastro, junto
com o seu apelido na tabela (TABELA_USUPER).
Com o nome do nosso cadastro já incluído junto
com o seu apelido (Titulo), cadastramos todos os
campos referente a esse cadastro. Para isso,
percorremos todos os campos cadastrados no nosso
TclientDataSet (Dados) e inserimos esses campos
na nossa tabela TAB_CAMPOS. Os campos dessa
nossa tabela são:
· CODIGO: Campo do tipo inteiro, é a chave
primária da nossa tabela, informamos o mesmo com
a função RetornaCodigo;
· TABELA: Aqui é o nome da nossa tabela e não o
seu apelido (Titulo);
· CAMPO: O nome do campo (interno), e não o
título que será mostrado na grade e no relatório;
· COLUNA: É o título do campo, aquele que será
mostrado na grade e no relatório;
· TIPO: Qual é o tipo do campo (Texto, Numero,
Data, Hora, Data e Hora, Observação, Moeda);
procedure ArrumarNomes;
procedure LimpaGrade;
function VerConsistencias: Boolean;
function RetornaCodigo(Generator: string):
Integer;
function RemoveAcentos(Texto: String):String;
function RemoveEspaco(Texto: String ):String;
· RELATORIO: Só aceita dois valores (S / N) para
controlar se o campo deverá ou não aparecer no
relatório.
Este procedimento remove os espaços em branco do
nome da tabela e dos campos. E também remove os
caracteres acentuados.
Aqui apenas fazemos algumas limpezas.
Apagamos todos os dados no TclientDataSet
(Dados). Limpamos o conteúdo do nosso script de
criação (Mmo_Script). E apagamos o nome da
tabela e o seu apelido.
Esta função verifica algumas coisas para que não
haja erro na hora de criarmos nossa tabela. A
verificação começa ao chamarmos o procedimento
(ArrumarNomes), para que o script fique adequado.
Em seguida é verificado se já não existe um
cadastro com o mesmo nome, se foi informado os
tipos dos campos, se foi informado o tamanho do
campo no caso se o tipo for texto, etc.
Esta função retorna um inteiro que selecionamos do
nosso banco de dados, para que nossa chave
primária seja um valor único.
Uma função que irá remover todos os acentos dos
caracteres, retornando o texto sem os caracteres
acentuados.
Já esta função irá tirar os espaços em brancos,
contidos nos textos.
Tabela 3. Entendendo um pouco a finalidade das funções e procedimentos
Montando o formuláro de criação de cadastros
Agora que já temos conhecimento das funções e procedimentos do formulário de criação de cadastro,
iremos montar o formulário, onde temos um TpageControl com duas abas. A primeira aba é utilizada
para a criação do cadastro em si. Já a segunda, para ensinar o usuário a fazer os seus cadastros.
Nesta segunda aba temos três imagens. A primeira é um exemplo de criação de um cadastro, a segunda,
é a imagem desse cadastro em execução, ou seja, o resultado como ficaria o cadastro criado a partir da
primeira imagem, já a terceira imagem seria o relatório desse cadastro. Veremos então esse formulário
com foco na primeira aba, a aba de criação, como mostra a Figura 4.
abrir imagem em nova janela
Figura 4. Form de Criação de Cadastros
Vamos agora aos nossos componentes. Temos um TPanel com alinhamento allbottom. Neste nosso
panel temos dois TEdits (Edt_Tabela, Edt_Apelido), junto com dois TLabel que são os títulos dos dois
edits. Temos também um TDBNavigator (Nvg_Setas) e temos dois TBitBtn (Btn_Criar, Btn_Sair).
Temos a nossa grade de criação, onde irão ser informados os campos do cadastro. A nossa grade
TDBGrid (Grd_Cadastro) está alinhada em toda a área da tela (allclient).
E por fim os três últimos componentes, um TMemo (Mmo_Script) que é onde será montado o script
para a criação da tabela no banco de dados, esse nosso memo está invisível, ou seja, visible = false.
Um TDataSource (Ds_Dados), e um TClientDataSet (Dados), que é onde terão os campos a serem
informados para a criação do cadastro. Serão um total de cinco campos, (NOME, COLUNA,
TIPOCAMPO, TAMANHO, RELATORIO), todos do tipo TStringField.
Na segunda aba temos três imagens, mostrando um exemplo para a criação de um cadastro, como vemos
nas Figuras 5 a 7.
abrir imagem em nova janela
Figura 5. Exemplo para a criação de um cadastro de Consulta Veterinária
abrir imaghem em nova janela
Figura 6. Resultado do cadastro de Consulta Veterinária
Figura 7. O Relatório da Consulta Veterinária, conforme campos escolhidos a serem mostrados
Nesta segunda aba ainda temos um TBitBtn, localizado abaixo dessas três imagens e irá mostrar um
texto que explica um pouco mais como montar esse cadastro. Esse texto está contido em uma imagem
(Figura 8) que é exibida ao clicar no TBitBtn.
abrir imagem em nova janela
Figura 8. Imagem que contem mais informações de como criar o cadastro
Configurando o TClientDataSet e a grade
Vamos agora inserir os campos para a criação no nosso TClientDataSet e na nossa grade. Para isso,
basta dar um duplo clique no TClientDataSet (Dados) e clicar com o botão direito, escolher a opção
(New Field), como na Figura 9.
Ao fazer isto será exibida uma tela para colocarmos as informações do campo, como vemos na Figura
10. Esse processo deve ser repetido cinco vezes, para os nossos campos (NOME, COLUNA,
TIPOCAMPO, TAMANHO, RELATORIO). Na tela de informações do campo basta colocar apenas
duas informações, o Name que será o nome do campo e o Type que será o tipo do campo, todos os
cincos serão do tipo String. Ao finalizar é necessário ativar o nosso TClientDataSet Dados, então
clicamos com o botão direito sobre ele e escolhemos a opção Create DataSet,
Figura 9. Criando os campos no TClientDataSet (Dados), escolhendo New Field
Figura 10. Tela para colocarmos as informações dos campos
Como já ativamos o nosso dataset, agora vamos fazer a ligação do nosso TDataSource a esse TDataSet.
Então no nosso Ds_Dados indicamos na propriedade DataSet o TClientDataSet Dados. Agora ligamos o
nosso Grid (Grd_Cadastro) e o nosso Navegador (Nvg_Setas) a esse TDataSource. Para isso basta
selecionarmos os dois componentes e colocar na propriedade DataSource, o nosso Ds_Dados.
Perceba que automaticamente ao ligarmos a grid ao datasource, todos os cincos campos que criamos já
aparecem no grid. Agora vamos configurar essa nossa grid. Então selecionamos a mesma e clicamos na
propriedade Columns. Será apresentada uma janela que é a janela das colunas, então na parte superior
desta clicamos no segundo botão (Add All Fields). Isso faz que os campos sejam listados nesta janela.
Selecionamos então o campo TIPOCAMPO e clicamos na propriedade PickList do mesmo. Feito isto
será apresentada a janela para informamos a lista que esse campo deve conter.
No nosso caso serão os tipos dos campos, então basta informar os tipos Texto, Numero, Data, Hora,
Data e Hora, Observação, Moeda. Lembrando que é uma listagem, então informe um em cada linha,
totalizando então sete linhas.
Programando a criação dos cadastros
É preciso garantir uma boa usabilidade e segurança para o usuário no momento da criação de seu
cadastro, assim controlamos algumas situações por código. Por exemplo, a Listagem 11 mostra como
impedir a exclusão ou inclusão de registros na grade de campos através de teclas especiais.
Listagem 11. Evento KeyDown da Grade
procedure TFrm_CriaCadastro.Grd_CadastroKeydown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
if (Shift =[ssctrl]) and (key = vk_delete) then abort;
if (key = vk_Up)then abort;
if (key = vk_down)then abort;
if (key = vk_Cancel)then abort;
if (key = vk_Escape)then abort;
if (key = vk_Insert)then abort;
end;
A Listagem 12 verifica se na coluna 2 foi selecionado um tipo de campo da lista. Se não foi é o tipo do
campo como Texto. Já para coluna 4 é verificado se informou ou não se o campo deverá aparecer no
relatório. Se não informou, o campo será mostrado no relatório.
Para isso é de extrema importância que os campos sejam criados como mencionado anteriormente, nesta
ordem: NOME, COLUNA, TIPOCAMPO, TAMANHO, RELATORIO.
Listagem 12. Evento ColExit da Grade
procedure TFrm_CriaCadastro.Grd_CadastroColExit(Sender: TObject);
var
Texto: String;
begin
if not (Ds_Dados.DataSet.State in [DsEdit, DsInsert]) then
Exit;
if (Grd_Cadastro.SelectedIndex = 2) then
begin
Texto := Grd_Cadastro.Columns[2].Field.Text;
if ((Texto <> 'Texto')
and (Texto <> 'Numero')
and (Texto <> 'Moeda')
and (Texto <> 'Data')
and (Texto <> 'Hora')
and (Texto <> 'Data e Hora')
and (Texto <> 'Observação')) then
Grd_Cadastro.Columns[2].Field.Text := 'Texto';
end
else
if (Grd_Cadastro.SelectedIndex = 4) then
begin
Texto := Grd_Cadastro.Columns[4].Field.Text;
if ((Texto <> 'S') and (Texto <> 'N')) then
Grd_Cadastro.Columns[4].Field.Text := 'S';
end;
end;
A Listagem 13 mostra a implementação do evento NewRecord do TClientDataSet, assim, a cada
registro novo o campo RELATORIO é configurado com o valor padrão ‘S’, ou seja, para que ele seja
exibido no relatório, da mesma forma o campo TIPOCAMPO tem seu valor padrão como ‘Texto’ e o
tamanho, campo TAMANHO, definido como ‘40’.
Listagem 13. Evento NewRecord do TClientDataSet
procedure TFrm_CriaCadastro.DadosNewRecord(DataSet: TDataSet);
begin
Dados.FieldByName('RELATORIO').AsString := 'S';
Dados.FieldByName('TIPOCAMPO').AsString := 'Texto';
Dados.FieldByName('TAMANHO').AsString
:= '40';
end;
Também é preciso validar o nome da tabela a ser criada, vamos permitir que só contenha letras de A à Z
em minúsculo, como mostra a Listagem 14, onde verificamos se a tecla pressionada não é “a”, “z”, ou
BackSpace, então soamos o beep e ignoramos a tecla.
Listagem 14. Evento KeyPress do Edt_Tabela
procedure TFrm_CriaCadastro.Edt_TabelaKeyPress(Sender: TObject;
var Key: Char);
begin
if not(key in['a'..'z', #8] ) then
begin
beep;
key:=#0;
end;
end;
A Listagem 15 mostra a implementação do botão de criação (Btn_Criar), apesar de parecer um pouco
mais complicada, com um pouco de atenção fica fácil o seu entendimento.
Listagem 15. Botão de Criação do Cadastro
procedure TFrm_CriaCadastro.Btn_CriarClick(Sender: TObject);
begin
try
Screen.Cursor := crSQLWait;
if (Trim(Edt_Tabela.Text) ='') then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Informe o Nome do Cadastro!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Exit;
end;
if (Dados.IsEmpty) then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Insira os Campos do Cadastro na Grade!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Exit;
end;
if not (VerConsistencias) then
begin
Screen.Cursor := crDefault;
Exit;
end;
MontaScript;
CriaTabela;
InserirTabela;
LimpaGrade;
Screen.Cursor := crDefault;
Application.MessageBox('Cadastro e Relatório Personalizado Criado com
Sucesso!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
except
on E:exception do
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Erro Ao Criar Tabela:' +#13+ E.message),
'Business Inteligence', MB_OK +
MB_ICONERROR);
Exit;
end;
end;
end;
Usamos um bloco try/except para capturar qualquer exceção, para não deixar que uma apareça na tela do
usuário. No try mudamos o cursor do mouse, caso o processo demore muito. Verificamos se foi
informado o nome da tabela, caso não, retornamos ao cursor normal, e informamos ao usuário.
A mesma coisa já acontece logo a seguir, verificamos se foram informados os campos do cadastro, caso
não, agimos da mesma forma acima, voltamos ao cursor normal e também informamos o usuário.
Na sequência usamos a função de verificar consistências para ver se podemos prosseguir com o processo
de criação. Se estiver tudo certo, aí usaremos quatro funções MontaScript, CriaTabela, InserirTabela,
LimpaGrade, para finalizarmos a criação.
E então informamos o usuário que o cadastro e o relatório foram criados com sucesso. Caso haja algum
erro no meio deste processo todo, entramos no Except.
O Except apenas volta o cursor do mouse ao normal e informa ao usuário que houve um erro na criação,
informando a mensagem do erro.
Procedimentos e funções da seção Private
Declaramos na seção Private os procedimentos e funções vistas na Listagem 16. Elas realizam
operações de apoio, como já foi explicado na Tabela 3. Uma vez declaradas, para implementá-las basta
pressionar Shift+Ctrl+C.
Nota: No código disponível para download é possível verificar a implementação de todas, aqui no artigo
destacamos as mais importantes.
Listagem 16. Seção Private do formulário
private
procedure Replace_Campos;
procedure Add_Texto;
procedure Add_Camps;
procedure MontaScript;
procedure CriaTabela;
procedure InserirTabela;
procedure ArrumarNomes;
procedure LimpaGrade;
function VerConsistencias: Boolean;
function RetornaCodigo(Generator: string): Integer;
function RemoveAcentos(Texto: String):String;
function RemoveEspaco(Texto: String ):String;
{ private declarations }
Como o script para a criação da tabela no banco de dados é montado em um memo (Mmo_Script) a
procedure Replace_Campos (Listagem 17) tem a responsabilidade de trocar os textos que representam
tipos em tipos reais. Por exemplo, Data para DATE, Texto para VARCHAR, Moeda para DOUBLE
PRECISION, Hora para TIME, Etc.). Repare que ele troca Data por DATE, e Hora por TIME. Em
seguida ele verifica se for DATE e TIM) para então trocar por TIMESTAMP.
Listagem 17. Procedure Replace_Campos
procedure TFrm_CriaCadastro.Replace_Campos;
begin
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
'VARCHAR', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
'BLOB SUB_TYPE 1 SEGMENT SIZE 30', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
[rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
[rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
'TIMESTAMP', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
PRECISION', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
'INTEGER', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text,
[rfReplaceAll]);
'Texto',
'Observação',
'Data', 'DATE',
'Hora', 'TIME',
'DATE e TIME',
'Moeda', 'DOUBLE
'Numero',
'Sim', 'S',
Mmo_Script.Lines.Text :=
[rfReplaceAll]);
end;
StringReplace(Mmo_Script.Lines.Text, 'Não', 'N',
A Listagem 18 apresenta o procedimento MontaScript. Ele é responsável por montar o script que é
executado. Através de variáveis do tipo string todo o texto é montado. Essa montagem é iniciada com a
sentença CREATE TABLE + o nome da tabela informada. Logo em seguida a chave primária da tabela,
‘CODIGO CHAR(6) not NULL.
Então todos os campos que o usuário informou são percorridos. Esses campos estão no ClientDataSet
Dados.
Então um a um é adicionado no nosso Mmo_Script, verificando se for campo do tipo texto, utiliza-se o
procedimento Add_Texto, se não for do tipo texto, utiliza-se o procedimento Add_Camps.
Para finalizar, é removida a última vírgula que foi adicionada após o último campo, fechando a sentença
então com os caracteres “');” e o AlteraTabela. Então para que o script fique totalmente correto e possa
ser executado no banco de dados, é chamado o procedimento Replace_Campos.
Listagem 18. Procedure MontaScript
procedure TFrm_CriaCadastro.MontaScript;
var
CriaTabela,
AlteraTabela,
NomeTabela,
UltLinha,
Codigo: String;
I, Virgula: Integer;
begin
try
NomeTabela
:= Edt_Tabela.Text;
CriaTabela
:='CREATE TABLE ' + NomeTabela + ' (';
Codigo := 'CODIGO CHAR(6) not NULL,';
AlteraTabela :='ALTER TABLE '+ NomeTabela +
' ADD CONSTRAINT PK_' + NomeTabela + ' PRIMARY KEY (CODIGO);';
Dados.First;
Mmo_Script.Lines.Add(CriaTabela);
Mmo_Script.Lines.Add(Codigo);
while not Dados.Eof do
begin
if (Dados.FieldByName('TIPOCAMPO').AsString ='Texto')then
Add_Texto
else
Add_Camps;
Dados.Next;
end;
I
:= Mmo_Script.Lines.Count - 1;
UltLinha := Mmo_Script.Lines[I];
Virgula := Pos(',', UltLinha);
if Virgula > 0 then
begin
Delete(UltLinha, Virgula, Length(UltLinha));
Insert(');', UltLinha, Virgula);
Mmo_Script.Lines[I]:= UltLinha;
end;
Mmo_Script.Lines.Add(AlteraTabela);
Replace_Campos;
except
on E:exception do
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Erro Ao Montar Script:' +#13+ E.message),
'Business Inteligence', MB_OK +
MB_ICONERROR);
Exit;
end;
end;
end;
Bom agora que estamos com o nosso script correto, então é necessário criar a tabela. A Listagem 19
mostra o método CriaTabela, ele irá jogar o conteúdo do Mmo_Script no componente de script do
formulário principal (Script: TuniScript) e então executará o mesmo, obviamente avisando se houve
alguma falha.
Listagem 19. Procedure CriaTabela
procedure TFrm_CriaCadastro.CriaTabela;
begin
try
Frm_Principal.Script.SQL.Text := Mmo_Script.Lines.Text;
Frm_Principal.Script.Execute;
except
on E:exception do
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Erro Ao Criar Tabela:' +#13+ E.message),
'Business Inteligence', MB_OK +
MB_ICONERROR);
Exit;
end;
end;
end;
O procedimento InserirTabela mostrado na Listagem 20 é responsável por criar o cadastro em si. Aqui
inserimos o nome do cadastro junto com o seu apelido na tabela (TABELA_USUPER). Por exemplo,
Nome (CONSVET), Apelido (Consulta Veterinária). Posteriormente são cadastrados todos os campos
referente a esse cadastro, percorrendo o conteúdo do TClientDataSet Dados. A cada registro encontrado
é realizada uma inserção na tabela TAB_CAMPOS. Os campos dessa nossa tabela são:
· CODIGO: campo do tipo inteiro, é a chave primária da tabela, informamos o mesmo com a função
RetornaCodigo);
· TABELA: é o nome da tabela e não o seu apelido (Titulo);
· CAMPO: o nome do campo (interno) e não o título que será mostrado na grade e no relatório;
· COLUNA: aqui sim é o título do campo, aquele que será mostrado na grade e no relatório;
· TIPO: indica o tipo do Campo, Texto, Numero, Data, Hora, Data e Hora, Observação, Moeda;
· RELATORIO: Só aceita dois valores (S/N) para controlar se o campo deverá ou não aparecer no
relatório.
Feito isto, o nosso Cadastro e Relatório Personalizado foram criados com sucesso. No menu do
formulário principal esse cadastro já pode ser acessado como um outro qualquer.
Listagem 20. Procedure InserirTabela
procedure TFrm_CriaCadastro.InserirTabela;
var
Tabela,
Apelido,
TpCampo,
Relatorio: String;
begin
try
Tabela := Trim(Edt_Tabela.Text);
Apelido := Edt_Apelido.Text;
if (Apelido = '')then
Apelido := Tabela;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO TABELA_USUPER (TABELA, APELIDO) VALUES (:TABELA,
:APELIDO)');
Params.ParamByName('TABELA').AsString := Tabela;
Params.ParamByName('APELIDO').AsString := Apelido;
Execute;
end;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO TAB_CAMPOS (CODIGO, TABELA, CAMPO, COLUNA, TIPO,
RELATORIO) VALUES ');
SQL.Add('(:CODIGO, :TABELA, :CAMPO, :COLUNA, :TIPO, :RELATORIO)');
Params.ParamByName('CODIGO').AsInteger
Params.ParamByName('TABELA').AsString
Params.ParamByName('CAMPO').AsString
Params.ParamByName('COLUNA').AsString
:=
:=
:=
:=
RetornaCodigo('GEN_TAB_CAMPOS');
Tabela;
'CODIGO';
'Código';
Params.ParamByName('TIPO').AsString
:= 'STRING';
Params.ParamByName('RELATORIO').AsString := 'S';
Execute;
end;
Dados.First;
while not Dados.Eof do
begin
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'STRING'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'INTEGER'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'DATE'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'TIME'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'TIMESTAMP'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'MEMO'
else
if (Dados.FieldByName('TIPOCAMPO').AsString
TpCampo := 'DOUBLE';
='Texto')then
='Numero')then
='Data')then
='Hora')then
='Data e Hora')then
='Observação')then
='Moeda')then
if (Dados.FieldByName('RELATORIO').AsString ='S')then
Relatorio := 'S'
else
if (Dados.FieldByName('RELATORIO').AsString ='N')then
Relatorio := 'N';
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO TAB_CAMPOS (CODIGO, TABELA, CAMPO, COLUNA, TIPO,
RELATORIO) VALUES ');
SQL.Add('(:CODIGO, :TABELA, :CAMPO, :COLUNA, :TIPO, :RELATORIO)');
Params.ParamByName('CODIGO').AsInteger :=
RetornaCodigo('GEN_TAB_CAMPOS');
Params.ParamByName('TABELA').AsString := Tabela;
Params.ParamByName('CAMPO').AsString
:=
Dados.FieldByName('NOME').AsString;
Params.ParamByName('COLUNA').AsString :=
Dados.FieldByName('COLUNA').AsString;
Params.ParamByName('TIPO').AsString
:= TpCampo;
Params.ParamByName('RELATORIO').AsString := Relatorio;
Execute;
end;
Dados.Next;
end;
Frm_Principal.AdicionaMenu(Tabela +' - '+ Apelido);
except
on E:exception do
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Erro Ao Inserir Tabela:' +#13+
E.message),
'Business Inteligence', MB_OK +
MB_ICONERROR);
Exit;
end;
end;
end;
Claro que para permitir a criação de um cadastro e o mesmo possa ser inserido no banco de dados, temos
que realizar algumas consistências. A função VerConsistencias da Listagem 21 mostra isso.
O procedimento ArrumarNomes (ver arquivos do download) é executado para que o script fique
adequado. Esse procedimento remove caracteres indesejados dos nomes informados.
Em seguida outras verificações necessárias são realizadas. Por exemplo, se já não existe um cadastro
com o mesmo nome, se foi informado o tipo dos campos, se foi informado o tamanho dos campos, no
caso se o tipo for texto se foi informado se o campo irá ou não aparecer no relatório.
Listagem 21. Function VerConsistencia
function TFrm_CriaCadastro.VerConsistencias: Boolean;
begin
Result := True;
ArrumarNomes;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('SELECT TABELA FROM TABELA_USUPER ');
SQL.Add('WHERE TABELA =:TABELA');
Params.ParamByName('TABELA').AsString := Edt_Tabela.Text;
Open;
end;
if not(Frm_Principal.Qry_Tabelas.IsEmpty) then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Já Existe um Cadastro Personalizado com este
Nome!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
Dados.First;
while not Dados.Eof do
begin
if (Dados.FieldByName('NOME').AsString ='Texto') or
(Dados.FieldByName('NOME').AsString ='Numero') or
(Dados.FieldByName('NOME').AsString ='Data') or
(Dados.FieldByName('NOME').AsString ='Hora') or
(Dados.FieldByName('NOME').AsString ='Data e Hora') or
(Dados.FieldByName('NOME').AsString ='Observação') or
(Dados.FieldByName('NOME').AsString ='Moeda') or
(Dados.FieldByName('COLUNA').AsString ='Texto') or
(Dados.FieldByName('COLUNA').AsString ='Numero') or
(Dados.FieldByName('COLUNA').AsString ='Data') or
(Dados.FieldByName('COLUNA').AsString ='Hora') or
(Dados.FieldByName('COLUNA').AsString ='Data e Hora') or
(Dados.FieldByName('COLUNA').AsString ='Observação') or
(Dados.FieldByName('COLUNA').AsString ='Moeda') then
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Atenção Não Pode Haver:' +#13+
'Texto, Numero, Data, Hora, Data e Hora,
Observação, Moeda.'+#13+
'No ( Nome do Campo ) e em ( Titulo da
Coluna ).'),
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
if (Dados.FieldByName('TIPOCAMPO').AsString ='')then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Informe o Tipo de Campo para todos os
Campos(Linhas)!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
if (Dados.FieldByName('TIPOCAMPO').AsString ='Texto') and
(Dados.FieldByName('TAMANHO').AsString ='')then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Informe o Tamanho para o Tipo de Campo Texto!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
if (Dados.FieldByName('RELATORIO').AsString ='')then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Informe se os Campos irão aparecer no Relatório ou
não!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
if Pos(' ', Dados.FieldByName('NOME').AsString) > 0 then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Não Pode Ter Espaço em Branco no Campo Nome!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
if Pos(' ', Edt_Tabela.Text) > 0 then
begin
Screen.Cursor := crDefault;
Application.MessageBox('Não Pode Ter Espaço em Branco no Nome do
Cadastro!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Result := False;
Exit;
end;
Dados.Next;
end;
end;
Montando o formulário dos cadastros
O formulário de cadastro possui sete componentes referente a parte de relatórios. São eles:
· FrxPDF (TfrxPDFExport), usado para exportar o relatório para o formato PDF;
· FrxHTML (TfrxHTMLExport), usado para exportar o relatório para o formato HTML;
· FrxJPEG (TfrxJPEGExport), usado para exportar o relatório para o formato JPEG;
· FrxEXCEL (TfrxXLSExport), usado para exportar o relatório para o formato XLS;
· FrxCSV (TfrxCSVExport), usado para exportar o relatório para o formato CSV;
· FrxCross (TfrxCrossObject), componente usado para a montagem e exibição dos dados;
· FrxRelPerso (TfrxReport), componente do relatório.
São necessários mais dois componentes não visuais: Qry_Cadastro (TuniQuery) e Ds_Cadastro
(TDataSource), que serão os responsáveis para a parte dos dados do cadastro. Temos também dois
TPanel. O primeiro painel Pnl_Tabela é alinhado ao topo do formulário, nele conterá um TLabel,
Lbl_Status. Esse label servirá, para informar o status do cadastro se está consultando, editando, ou
inserindo dados.
O segundo panel (Pnl_Botao) é alinhado na parte de baixo do formulário. Temos nesse painel quatro
componentes, dois TDBNavigator (Nvg_Setas e Nvg_Dados). O primeiro exibe os botões de navegação
de registros, já o segundo os botões para manipulação de dados.
O primeiro navegador só fica habilitado se não estiver inserindo ou editando o cadastro. Os outros dois
componentes são TBitBtn (Btn_Imprimir e Btn_Sair).
O botão de imprimir será responsável por gerar o relatório. Por fim, temos o último componente, o
Grd_Cadastro, um TDBGrid. A grade do cadastro que fica alinhada em todo o restante da tela, entre os
dois painéis.
Montamos esse formulário para que ele fique parecido com a Figura 11.
Figura 11. Montagem do Form de Cadastros
Criando o Relatório
Para criar o relatório basta dar um clique no componente de relatório FastReport, FrxRelPerso.
Mudamos as subpropriedades da propriedade PreviewOptions, nela temos Buttons onde devemos deixar
que só esses fiquem habilitados (true) os seguintes botões: pbPrint, pbExport, pbZoom, pbTools,
pbNavigator, pbExportQuick, pbNoFullScreen.
Feita esta pequena mudança agora é só dar um duplo clique no componente do relatório que será exibido
o seu designer para fazermos sua montagem. No centro fica a página do relatório e a esquerda uma
coleção de botões, os quais usaremos alguns para montar o relatório. Nesse nosso relatório vamos ter
três bandas, uma para o título, uma para os dados e a outra do rodapé que informará o número da página.
A imagem desta montagem pode ser vista na Figura 12.
Banda do Título
Aqui é uma simples banda que exibirá o título do relatório, esse título será: Relatório de Apelido do
cadastro. Por exemplo: Relatório de Consulta Veterinária. Então clicamos no botão Inserir Banda e
escolhemos a banda Título do Relatório, a nomeamos como BdTitulo. Agora nesta banda inserimos um
objeto Memo e o nomeamos para MmoTitulo, mudando sua propriedade Align para baWidth e a
propriedade HAlign para haCenter.
Banda dos Dados
Clicamos no botão de Inserir banda e escolhemos a banda Dado Mestre. Aparecerá uma tela que informa
que a banda não está relacionada com DataSet algum, basta clicar no OK. Nomeie esta banda para
BdDados e alteramos a propriedade Height para 2,80.
Inserimos nesta banda um objeto CrossTab. Na tela que aparecerá, na parte direita no canto superior, a
propriedade Colum, nela escolhemos sem ordenação. Logo a baixo, na propriedade Cell, escolher
“Nenhum”.
Mais um pouco em baixo, nas caixas de seleção, deixar marcado somente as opções “Exibe Canto,
Cabeçalho de Coluna, Tamanho Automático, Arredonda Bordas das Células, Reimprime cabeçalho em
nova página”. Selecione o estilo Gray, ou conforme o gosto. Clique no ok para fechar a tela e vamos
fazer as outras mudanças.
Nomeie o objeto CrossTab para Cross, mude a propriedade Top para 0, a propriedade Width para 2,62 e
a propriedade Left para 0,05 e a propriedade Height para 2,76. Agora clicamos na primeira coluna, onde
está escrito Columm. Mudamos a propriedade HAlign para hacenter e a propriedade VAlign para
vacenter. Colocamos sua fonte com o estilo Negrito e mudamos a sua cor para cl3DLight, ou outra
desejada.
Agora clicamos na parte de baixo, onde está o 0 (zero). Mudamos a propriedade color para clWhite, a
sua propriedade HAlign para haLeft, na propriedade Frame no BottonLine mude a propriedade color
para clMenuText.
Banda do Rodapé
Aqui também é uma simples banda, ela informará o número da página e o total de páginas. Clicamos
novamente no botão de inserir banda, escolhemos a banda Rodapé de Página, a nomeamos para
BdPgFoote). Agora inserimos um objeto Texto, nomeamos para MmoLinhaFooter.
Na propriedade Frame mudamos o Width para 2, e seu Top para 0. Também alteramos a propriedade
Width para 2, em TopLine, RightLine, LeftLine, BottonLine mudamos a propriedade Type para ftTop
como true.
Agora inserimos um objeto (Texto do Sistema), na sua tela que aparecerá marcamos a opção de Texto,
que está na parte de baixo da tela. Nela escrevemos o seguinte texto, logo em baixo na sua caixa de
texto: (Página [PAGE#] de [TOTALPAGES#]). Agora basta dar ok e nomear o mesmo para
SmmoPagina, mudamos também a sua propriedade Top para 0,10 e o Left para 0. Ajustamos o seu
tamanho para que caiba todo o texto e aí finalizamos a montagem.
Figura 12. Montagem do Relatório
Procedimentos e funções do fomulário dos cadastros
Na seção Uses declaramos o formulário principal e em seguida declaramos uma constante, que será
usada para o status do cadastro, conforme mostra a Listagem 22.
Listagem 22. Adicionando Units ao Uses, e Declarando uma Constante
unit Unt_Cadastro;
interface
Uses
Unt_Principal, DBAccess, Uni //etc
Const
dsEditModesStr: array [1..3] of String = ('Consultando', 'Editando',
'Inserindo');
Na seção private e public temos algumas variáveis, procedimentos e funções. São elas que irão montar o
nosso cadastro e auxiliar em diversas outras rotinas. Olhamos com atenção a seção public, nela está
declarada uma variável Tabela. É o formulário principal que irá passar para essa variável qual é o
cadastro escolhido e a ser montado. Vejamos como ficam essas seções na Listagem 23 e sua
implementação está disponível no código fonte do artigo.
Listagem 23. Seção Private e Public do Formulário
private
Apelido,
Nome,
Sql: String;
Frm: Tform;
MmoGrade: TMemo;
ListApelidos: TStringList;
OldStateCad: TDataSetState;
procedure MontaCadastro;
procedure DataHoraText(Sender: TField; const Text: String);
procedure MemoText(Sender: TField; var Text: String; DisplayText:
Boolean);
procedure FecharClick(Sender: TObject);
procedure ConfirmarClick(Sender: TObject);
function fZerosLeft(Str: String; Tam: Word): String;
function fCodDefault(Qry: TUniQuery; Chave, Tab: String; nInc: Integer;
lZerosLeft: Boolean; Condicao: String = ''; Tabela: TDataSet = nil;
Edit: TCustomEdit = nil): String;
{ private declarations }
public
Tabela: String;
{ public declarations }
end;
Programando o formulário de excluir cadastros
O formulário Frm_ExcCadastro é o mais simples todos os outros. São apenas quatro componentes e
quatro procedures. Na sua montagem foi utilizado um TPanel (Pnl_Botao) alinhado em baixo da tela
AllBottom. Dentro deste Pnl_Botao foram colocados dois TBitBtn (Btn_Excluir, Btn_Sair).
Obviamente que o primeiro é para excluir um cadastro e o segundo é para fechar o formulário. E por
último um TListBox (Lst_Cadastros) que por sua vez é alinhado em todo o restante da tela (AllClient),
nele é onde serão listados os cadastros existentes. Após a sua montagem, ele deverá ficar com a
aparência da Figura 13.
Figura 13. Montagem do Form de Excluir Cadastros
No evento Create do formulário selecionamos todos os campos da tabela TABELA_USUPER, que é
onde ficam as informações dos cadastros criados. Percorremos o resultado da consulta e adicionamos no
TListBox o Cadastro (TABELA) e o seu apelido (APELIDO), como mostra Listagem 24.
Listagem 24. Evento Create do Formulário
procedure TFrm_ExcCadastro.formCreate(Sender: TObject);
begin
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('SELECT * FROM TABELA_USUPER');
Open;
end;
while not(Frm_Principal.Qry_Tabelas.Eof) do
begin
Lst_Cadastros.Items.Add(Frm_Principal.Qry_Tabelas.FieldByName('TABELA').AsString +'
- '+
Frm_Principal.Qry_Tabelas.FieldByName('APELIDO').AsString);
Frm_Principal.Qry_Tabelas.Next;
end;
if not (Frm_Principal.Qry_Tabelas.IsEmpty) then
Lst_Cadastros.Selected[0] := True;
end;
A Listagem 25 mostra a exclusão disparada pelo botão Btn_Excluir e que usa a procedure ExcTabela.
Essa procedure deve ser declarada na seção private também. Verificamos se existe algum cadastro, caso
não, informamos o usuário que não há cadastro para excluir.
Se existir o cadastro, é perguntado ao usuário se ele realmente deseja excluir: se sim é chamada a
procedure ExcTabela, que pode ser vista na Listagem 26.
Listagem 25. Botão Excluir Cadastro
procedure TFrm_ExcCadastro.Btn_ExcluirClick(Sender: TObject);
begin
if (Lst_Cadastros.ItemIndex < 0) then
begin
Application.MessageBox('Sem Cadastro/Selecionado para Apagar!!!',
' Atenção', MB_OK + MB_ICONINFORMATION);
Exit;
end;
if Application.MessageBox('Deseja Realmente Apagar o Cadastro Selecionado??',
' Apagar o Cadastro', MB_ICONQUESTION + MB_YESNO) <>
idYes then Exit;
ExcTabela;
end;
Listagem 26. Procedimento ExcTabela
procedure TFrm_ExcCadastro.ExcTabela;
var
Tabela,
TApelido: String;
begin
try
TApelido := Lst_Cadastros.Items.Strings[Lst_Cadastros.ItemIndex];
Tabela
:= Copy(TApelido, 0, Pos('-', TApelido )-1);
Screen.Cursor := crSQLWait;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('DELETE FROM TAB_CAMPOS TC ');
SQL.Add('WHERE (TC.TABELA = :PTABELA)');
Params.ParamByName('PTABELA').AsString := Tabela;
Execute;
end;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('DELETE FROM TABELA_USUPER TU ');
SQL.Add('WHERE (TU.TABELA = :PTABELA)');
Params.ParamByName('PTABELA').AsString := Tabela;
Execute;
end;
with Frm_Principal.Qry_Tabelas do
begin
Close;
SQL.Clear;
SQL.Add('DROP TABLE '+ Tabela);
Execute;
end;
Frm_Principal.RemoveMenu(TApelido);
Lst_Cadastros.Items.Delete(Lst_Cadastros.ItemIndex);
Screen.Cursor := crDefault;
except
on E:exception do
begin
Screen.Cursor := crDefault;
Application.MessageBox(PAnsiChar('Erro Ao Excluir Tabela:' +#13+
E.message),
'Business Inteligence', MB_OK +
MB_ICONERROR);
Exit;
end;
end;
end;
Nela, repare que temos duas variáveis (Tabela, TApelido), que servem para pegar o nome e o apelido da
tabela a ser excluída. Em seguida apagamos os campos pertencentes a essa tabela, que estão na tabela
TAB_CAMPOS. Logo após apagamos o cadastro desta tabela, que se encontra na tabela
TABELA_USUPER.
Feito isso apagamos essa tabela com o comando Drop Table Agora sim no nosso banco de dados não há
mais nada referente a esse cadastro.
Na sequência chamamos a procedure RemoveMenu, passando como parâmetro a variável apelido, e
tiramos o cadastro desta lista e do menu.
Permitir que um usuário possa criar cadastros simples e seus respectivos relatórios é uma funcionalidade
que garante flexibilidade e pode até mesmo ser um diferencial comercial para qualquer produto.
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de
São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.
Explorando APIs do Windows em Delphi – Parte
1
Neste artigo veremos algumas APIS do Windows que podem ser
acessadas pelo Delphi, dentre as quais destacaremos as da
categoria Arquivos, Cursores, Registros e Informações.
Fique por dentro
Recursos como o de criar pastas, achar determinado arquivo, apagar temporários após sua utilização,
entre outros, estão disponíveis pela API do Windows, que é um conjunto de DLLs que fazem parte do
sistema, expondo as funções do mesmo. Nesse artigo vamos explorar as APIs da categoria Arquivos,
Cursores, Registros e Informações sobre o sistema e Windows.
As APIs do Windows são expostas através de DLLs que podem ser utilizadas no Delphi e quando as
utilizamos estamos lidamos diretamente com o sistema operacional. Dentre as categorias existentes nas
APIs, pode-se dizer que as principais são:
· Windows;
· Arquivos;
· Informações sobre o sistema;
· Cursores;
· Mensagens;
· Mouse;
· Teclado;
· Impressoras;
· Ícones;
· Arquivos INI;
· Registro;
· Dispositivos;
· Acessibilidade.
A Embarcadero disponibiliza no Delphi o acesso a essas APIs através da unit Windows, que realiza uma
ponte entre o código Delphi e as várias DLLs disponibilizadas pelo sistema. As principais DLLs são:
· User32.dll;
· kernel32.dll;
· Comdlg32.dll;
· gdi32.dll;
· shell32.dll;
· Advapi32.dll;
· winmm.dll.
Uma DLL (Dynamic-link library ou biblioteca de vínculo dinâmico), é um arquivo com extensão que
consiste numa coleção de funções e procedures que podem ser chamadas por outras aplicações e outras
DLLs, que por sua vez, é ligada em tempo de execução ao programa que as usa.
Informações sobre o Sistema
É possível obter informações sobre o Sistema através de algumas funções expostas:
· GetComputerName: está declarada em kernel32.dll e irá ler o nome do computador, que será
devolvido em uma variável do tipo string. Esta deve ser passada como parâmetro na função. Sua
declaração é feita da seguinte forma:
GetComputerNameA (ByVal lpBuffer As String, nSize As Long) As Long
O parâmetro lpBuffer é uma sequência de caracteres que deve ser grande o suficiente para manter o
nome do computador. Já nSize é o comprimento em caracteres de lpBuffer, geralmente usado com o
valor 255.
· GetUserName: está declarada em advapi32.dll e recupera o nome do usuário que está logado no
Windows. Este também é retornado em uma string que devemos passar como parâmetro. Sua declaração
é a seguinte:
GetUserNameA (ByVal lpBuffer As String, nSize As Long) As Long
O lpBuffer é uma sequência de caracteres que deve ser grande o suficiente para manter o nome do
usuário. O nSize é o comprimento em caracteres de lpBuffer, geralmente com o valor 144.
· GetSystemDirectory: retorna o caminho do diretório de sistema do Windows. É importante observar é
que nunca devemos assumir que o diretório é “C:\Windows\System”, porque o diretório não
necessariamente precisa ser chamado Windows. Sua declaração é parecida com as outras duas que
vimos anteriormente e até mesmo os mesmos parâmetros são parecidos. Ela está declarada em
kernel32.dll e sua declaração é:
GetSystemDirectoryA (ByVal lpBuffer As String, ByVal nSize As Long) As Long
· GetWindowsDirectory: está declarada em kernel32.dll e retorna o caminho do diretório do Windows.
É onde o próprio Windows está instalado, contudo, isso não significa que seja sempre “C:\Windows”.
Sua declaração é:
GetWindowsDirectoryA (ByVal lpBuffer As String, ByVal nSize As Long) As Long
· GetTempPath: retorna o diretório Temp do Windows, onde ficam os arquivos temporários. A função
está declarada em kernel32.dll e, ao contrário das quatro funções vistas anteriormente, aqui os
parâmetros se invertem. Primeiro é passado o tamanho a ser usado para receber a string, e depois o
parâmetro da mesma, como na declaração a seguir:
GetTempPathA (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
· GetVersionEx: declarada em kernel32.dll, esta função retorna as informações sobre a versão do
Windows em execução. Essas informações incluem o número da versão, o build e a versão do sistema
instalado. Essas informações são transferidas para uma variável do tipo OSVersionInfo, que é do tipo
record, conforme a sua declaração:
GetVersionExA (lpVersionInformation As OSVERSIONINFO) As Long
Aplicação sobre Informações do Sistema
Para mostrar como utilizar essas APIs vamos desenvolver uma aplicação. Nela teremos apenas uma tela
com seis TEdits, seis TLabels e um TButton, como vemos na Figura 1.
Figura 1. Tela do aplicativo
A propriedade Name do Form1 é modificada para Frm_Principal, e os seis TEdits para Edt_CompNome,
Edt_UsurNome, Edt_PastaSys, Edt_WinDiretorio, Edt_PastaTemp e Edt_WinVersao. O botão recebe o
nome de Btn_Informacoes e os seis TLabels têm sua propriedade name modificada para
Lbl_CompNome, Lbl_UsurNome, Lbl_PastaSys, Lbl_WinDiretorio, Lbl_PastaTemp, e Lbl_WinVersao.
Ao salvar a aplicação ajustamos a unit para o nome de Unt_Principal e o projeto para InfoSys. Já a
propriedade Caption dos Tlabels deve ficar como visto na Figura 1.
Procedimentos e Funções da Seção Private
Na seção private são declaradas seis funções, como mostra a Listagem 1. Elas são responsáveis por
acessar as APIs. Uma vez declaradas pressionamos a combinação Shift + Ctrl + C e com isso o Delphi
inicia a implementação dessas funções, que podemos ver no código da Listagem 2.
Listagem 1. Seção Private do Frm_Principal
private
function
function
function
function
function
function
fGetComputerName: String;
fGetUserName: String;
fGetSystemDirectory: String;
fWindowsDirectory: String;
fGetTempPath: String;
fGetVersionEx: string;
Listagem 2. Implementação
procedure TFrm_Principal.Btn_InformacoesClick(Sender: TObject);
begin
Edt_CompNome.Text
:= fGetComputerName;
Edt_UsurNome.Text
Edt_PastaSys.Text
Edt_WinDiretorio.Text
Edt_PastaTemp.Text
Edt_WinVersao.Text
end;
:=
:=
:=
:=
:=
fGetUserName;
fGetSystemDirectory;
fWindowsDirectory;
fGetTempPath;
fGetVersionEx;
function TFrm_Principal.fGetComputerName: String;
var
Buffer: Array[0..255] of Char;
I: DWord;
begin
I := SizeOf(Buffer);
GetComputerName(Buffer, I);
Result := StrPas(Buffer);
end;
function TFrm_Principal.fGetSystemDirectory: String;
var
Buffer: Array[0..255] of Char;
begin
GetSystemDirectory(Buffer, 255);
Result := StrPas(Buffer);
end;
function TFrm_Principal.fGetTempPath: String;
var
Buffer: Array[0..255] of Char;
begin
GetTempPath(255, Buffer);
Result := StrPas(Buffer);
end;
function TFrm_Principal.fGetUserName: String;
var
Buffer: Array[0..255] of Char;
I: DWord;
begin
I := SizeOf(Buffer);
GetUserName(Buffer, I);
Result := StrPas(Buffer);
end;
function TFrm_Principal.fWindowsDirectory: String;
var
Buffer: Array[0..255] of Char;
begin
GetWindowsDirectory(Buffer, 255);
Result := StrPas(Buffer);
end;
function TFrm_Principal.fGetVersionEx: string;
var
VersionInfo: TOSVersionInfo;
begin
VersionInfo.dwOSVersionInfoSize := SizeOf(VersionInfo);
GetVersionEx(VersionInfo);
with VersionInfo do
begin
case dwPlatformid of
0: begin
Result := 'Windows 3.11';
end;
1: begin
case dwMinorVersion of
0: Result := 'Windows 95';
10: begin
if (szCSDVersion[ 1 ] = 'A' ) then
Result :='Windows 98 SE'
else
Result := 'Windows 98';
end;
90: Result := 'Windows Millenium';
else
Result := 'Não achei a Versão';
end;
end;
2: begin
case dwMajorVersion of
3: Result := 'Windows NT ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
4: Result := 'Windows NT ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
5: begin
case dwMinorVersion of
0: Result := 'Windows 2000';
1: Result := 'Windows XP';
end;
end;
6: Result := 'Windows 7 ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
7: Result := 'Windows 8 ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
8: Result := 'Windows Vista ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
else
Result := 'Não achei a Versão';
end;
if szCSDVersion <> '' then
Result := Result + ' ' + szCSDVersion;
end;
else
Result := 'Não achei a Platforma';
end;
Result := Result + ', Build: ' + IntToStr(Loword(dwBuildNumber)) ;
end;
end;
end.
As funções a seguir foram criadas para obter informações e repassá-las aos controles TEdit:
· StrPas – É a função declarada na Unit SysUtils que converte uma cadeia de strings, terminado em nulo,
para uma cadeia de string longa (AnsiString).
· SizeOf – É a função declarada na Unit System, que retorna o tamanho em bytes de uma variável ou
tipo.
· TOSVersionInfo – É um record declarado em SysUtils, que contém informações do sistema
operacional, plataforma (Windows, Mac Os X), versão, tipo de arquitetura (Intel x86 ou Intel x64) e
Service Pack. Esse record contém dois tipos públicos:
o TArchitecture (arIntelX86, arIntelX64, arARM32);
o TPlatform (pfWindows, pfMacOS, pfiOS, pfAndroid, pfWinRT, pfLinux).
Arquivos
O Windows oferece uma grande variedade de funções para tratamento de arquivos como vemos a
seguir:
· CopyFile: está declarada em kernel32.dll e copia um arquivo de um local para outro, assim como a
cópia de um arquivo no Windows Explorer. Em sua declaração temos três parâmetros:
o LpExistingFileName - O arquivo de origem, ou seja, o arquivo a ser copiado;
o LpNewFileName - O arquivo de destino, ou seja, o novo arquivo para criar;
o BFailIfExists - Se 0, a função irá substituir LpNewFileName caso ele já existe, caso contrário, a
função irá falhar.
· MoveFile: move ou renomeia um arquivo ou pasta. Se um diretório é movido/renomeado, todos os
subdiretórios e arquivos contidos nele serão afetados. A função retorna 1 se for bem-sucedida ou zero se
ocorrer um erro. Espera-se dois parâmetros:
o LpExistingFileName - O arquivo de origem ou diretório, ou seja, o arquivo ou diretório para renomear
(mover);
o LpNewFileName - O arquivo de destino ou diretório, ou seja, o novo nome do arquivo ou diretório,
que se dá ao arquivo de origem para que seja movido.
· CreateFile: cria ou abre um arquivo em disco para acesso posterior. É necessário ter os direitos de
acesso permitidos para o arquivo a ser utilizado. Essa função tem inúmeros parâmetros para especificar
os níveis e tipos de acesso, e irá retornar o identificador para o arquivo criado/aberto se for bemsucedido, ou -1 se um algum erro ocorreu. Seus parâmetros são:
o LpFileName - O nome do arquivo a ser criado ou aberto;
o DwDesiredAccess - Zero ou um dos seguintes parâmetros, especificando as quantidades de acesso, de
leitura e gravação para o arquivo:
§ GENERIC_READ - Permitir que o programa leia os dados do arquivo;
§ GENERIC_WRITE - Permitir que o programa grave dados no arquivo.
o DwShareMode - Zero ou um dos seguintes parâmetros, especificando as quantidades de acesso, de
leitura e gravação concedidas a outros programas enquanto o programa ainda está com ele aberto:
§ FILE_SHARE_READ - Permitir que outros programas possam ler os dados do arquivo;
§ FILE_SHARE_WRITE - Permitir que outros programas possam gravar dados no arquivo.
o LpSecurityAppributes - Os atributos de segurança dados ao arquivo criado ou aberto;
o DwCreationDisposition - Exatamente um dos seguintes parâmetros, especificando como e quando
criar ou abrir o arquivo, dependendo se ele já existe ou não:
§ CREATE_ALWAYS - Cria um novo arquivo, substituindo o mesmo caso esse já exista;
§ CREATE_NEW - Cria um novo arquivo, mas falha se ele já existe;
§ OPEN_ALWAYS - Abre um arquivo existente e, se o arquivo não existir, ele será criado;
§ OPEN_EXISTING - Abre um arquivo existente, mas falha se o arquivo não existe.
§ TRUNCATE_EXISTING - Abre um arquivo existente e apaga o seu conteúdo. A função falhará se o
arquivo não existe.
o DwFlagsAndAttributes - Uma combinação dos seguintes parâmetros, especificando os atributos do
arquivo para um recém-criado, para cria-lo ou abri-lo. Deve ser incluso um handle para o arquivo, para
especificar os seus atributos:
§ FILE_ATTRIBUTE_ARCHIVE - Um arquivo normal;
§ FILE_ATTRIBUTE_HIDDEN - Um arquivo oculto, que normalmente não é visível para o usuário,
dependendo da configuração;
§ FILE_ATTRIBUTE_NORMAL - Um arquivo sem atributos (esse não pode ser usado com atributos);
§ FILE_ATTRIBUTE_READONLY - Um arquivo de somente leitura;
§ FILE_ATTRIBUTE_SYSTEM - Um arquivo de sistema, utilizado exclusivamente pelo sistema
operacional;
§ FILE_FLAG_DELETE_ON_CLOSE - Exclui o arquivo, uma vez que o mesmo está fechado;
§ FILE_FLAG_OVERLAPPED - Permiti que o arquivo seja lido e gravado ao mesmo tempo. Se for
utilizado, as funções que leem e escrevem no arquivo devem especificar essa estrutura (OVERLAPPED)
para identificar o ponteiro do arquivo;
§ FILE_FLAG_POSIX_SEMANTICS - Permiti que o nome do arquivo seja maiúsculo ou minúsculo;
§ FILE_FLAG_RANDOM_ACCESS - Otimiza o cache de arquivos para acesso aleatório (poder pular
por várias partes do arquivo);
§ FILE_FLAG_SEQUENTIAL_SCAN - Otimiza o cache de arquivos para acesso sequencial crescente);
§ FILE_FLAG_WRITE_THROUGH – Lê e escreve diretamente no arquivo, ignorando qualquer cache
de disco;
o HTemplateFile – Identifica 1 arquivo aberto e copia os atributos, ou zero para não copiar os mesmos.
· DeleteFile: exclui um arquivo completamente, sem enviá-lo para a lixeira. Ele também não solicita a
confirmação da exclusão, então deve ser utilizado com toda atenção. A função retorna 1 se for bemsucedida, ou zero caso tenha ocorrido algum erro como, por exemplo, quando o arquivo a ser excluído
não existe. A função espera por um único parâmetro LpFileName, que representa o nome do arquivo a
ser excluído;
· FindClose: termina a pesquisa de um arquivo iniciado por FindFirstFile. Esta função fecha o
identificador da pesquisa de arquivos;
· FindFirstFile: começa uma pesquisa de arquivo e fornece informações sobre o primeiro arquivo
correspondente. As pesquisas de arquivos têm base em apenas um nome de arquivo com sua extensão ou
não. A pesquisa só olha em um único diretório, mas identifica quaisquer nomes no mesmo que
corresponde à sequência da pesquisa A função retorna um identificador de pesquisa que pode ser usado
para procurar por arquivos correspondentes adicionais, usando FindNextFile. Pode ser retornado -1
também caso não haja arquivos coincidentes com a pesquisa. Seus parâmetros são:
o LpFileName – É a sequência de pesquisa de arquivos para procurar, incluindo o caminho completo.
Pode conter os curingas como * ou ?;
o LpFindFileData - Recebe informações de identificação sobre o primeiro arquivo encontrado.
· FindNextFile: continua uma pesquisa de arquivo que começou com FindFirstFile. Encontra e fornece
informações de identificação sobre o próximo arquivo que corresponde à sequência da pesquisa. A
função retorna 1 se foi encontrado um outro arquivo, ou zero se não existem mais arquivos
correspondentes (ou se ocorreu um erro). Seus parâmetros são:
o HFindFile - identificador do arquivo para a pesquisa começado com FindFirstFile;
o LpFindFileData - recebe informações de identificação sobre o próximo arquivo correspondente que foi
encontrado.
· CreateDirectory: cria um novo diretório em disco e define os atributos de segurança do mesmo. A
função retorna 1 se for bem-sucedida, ou zero se ocorrer algum erro. Seus parâmetros são:
o LpPathName - nome do novo diretório a ser criado;
o LpSecurityAttributes - atributos de segurança para dar ao novo diretório.
· FileExists: retorna um valor boolean se o arquivo especificado como parâmetro existe ou não.
· DirectoryExists: retorna um valor boolean se o diretório especificado existir ou não. Seu único
parâmetro é o Directory, que representa o nome do diretório para verificar sua existência. A função
poderá falhar se o usuário não tiver permissão para o caminho informado do diretório.
· CloseHandle: fecha um identificador e o objeto associado a ele. Depois de ter sido fechado, o
identificador não será mais válido. A função retorna 1 se for bem-sucedida, ou zero se ocorreu algum
erro. Seu parâmetro HObject representa o identificador do objeto a fechar.
Aplicação sobre Arquivos
Ao criar um novo aplicativo do tipo Win32, adicionamos ao formulário principal seis componentes
TLabel, seis TButton e um TListBox, como mostra a Figura 2.
Figura 2. Tela do Aplicativo
A propriedade Name do Form1 é modificada para Frm_Principal e os seis TButons para Btn_Copiar,
Btn_Mover, Btn_Criar, Btn_Apagar, Btn_CriaDir e Btn_BuscArqui. O TListBox é chamado de
Lst_BuscArqui, e os seis TLabel devem ter a propriedade Name modificada para Lbl_Copiar,
Lbl_Mover, Lbl_Apagar, Lbl_Criar, Lbl_CriaDir e Lst_BuscArqui. Salvamos a unit com o nome de
Unt_Principal e o projeto como Arquivos. Mude também a propriedade Caption dos TLabels e TButtons
conforme a Figura 2.
Incluímos na seção uses a unit ShellApi, pois precisaremos dela para criar, copiar, mover, apagar os
arquivos. Usaremos arquivos de textos normais (*.txt).
A Listagem 3 mostra a implementação do botão Buscar Arquivos.
Listagem 3. Usando as funções e implementando o código dos botões
procedure TFrm_Principal.Btn_BuscArquiClick(Sender: TObject);
var
SR: TSearchRec;
Pasta: String;
begin
Pasta := 'C:\Apagar Arquivos Txt';
if not(DirectoryExists(Pasta)) then
begin
Application.MessageBox('Não Existe a Pasta "C:\Apagar Arquivos Txt" ',
' Atenção',MB_ICONINFORMATION + MB_OK);
Exit;
end;
If FindFirst(Pasta +'\*.txt', faAnyFile, SR) =0 then
begin
Repeat
if (SR.Attr and faDirectory) <> faDirectory then
Lst_BuscArqui.Items.Add(Sr.Name);
Until FindNext(SR) <> 0;
FindClose(SR);
end;
end;
TSearchRec é um record que está declarado em SysUtils e define uma estrutura de dados, que veremos
na Listagem 4. Ela será utilizada para armazenar informações de pesquisa de arquivos pelas rotinas
FindFirst e FindNext.
Listagem 4. Estrutura TSearchRec
TSearchRec = record
Time: Integer;
Size: Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
FindHandle: THandle;
FindData: TWin32FindData;
end;
Na estrutura record temos:
· Time: Data do arquivo modificado e o tempo;
· Size: Tamanho do arquivo em bytes;
· Attr: Atributos do arquivo:
o faAnyFile: Qualquer arquivo;
o faReadOnly: Arquivos somente leitura;
o faHidden: Arquivos ocultos;
o faSysFile: Os arquivos de sistema;
o faVolumeID: Volume: arquivos ID;
o faDirectory: Arquivos Diretório;
o faArchive: Arquivos;
· Name: Nome do arquivo.
Windows
A categoria Windows permite interação com suas janelas, habilitar e desabilitar diversas configurações e
muito mais. Essa API conta com 31 funções, mas para esse artigo apresentaremos apenas as principais
que serão utilizadas na aplicação de exemplo para entendermos mais sobre.
ShowWindow
A função pode minimizar, maximizar ou restaurar uma determinada janela. Retorna zero se a janela
estiver invisível antes da chamada, ou um valor diferente se estiver visível. Recebe os seguintes
parâmetros de entrada:
· Hwnd - O identificador da janela, para alterar o status de como é mostrado;
· NcmdShow - Exatamente um dos seguintes parâmetros, especificando como mostrar a janela:
o SW_HIDE - Esconde a janela;
o SW_MAXIMIZE - Maximiza a janela;
o SW_MINIMIZE - Minimiza a janela;
o SW_RESTORE - Restaura a janela (não maximizada e nem minimizada);
o SW_SHOW - Mostra a janela;
o SW_SHOWMAXIMIZED - Mostra a janela maximizada;
o SW_SHOWMINIMIZED - Mostra a janela minimizada;
o SW_SHOWMINNOACTIVE - Mostra a janela minimizada, mas não a ativa;
o SW_SHOWNA - Mostra a janela em seu estado atual, mas não a ativa;
o SW_SHOWNOACTIVATE -Mostra a janela em seu tamanho e a posição mais recente, mas não ativa;
o SW_SHOWNORMAL - Mostra a janela e a ativa (geralmente o normal).
FindWindow
Esta função procura por todas as janelas abertas que correspondam ao nome da classe da janela
informado e/ou nome da janela. Essa busca não é sensível a maiúsculas e seus parâmetros são
relacionados a seguir:
· LpClassName - O nome da classe da janela para se encontrar. Passe zero para permitir que a janela seja
de qualquer classe;
· LpWindowName - O texto da barra de título da janela para se encontrar. Passe zero para permitir que a
janela tenha qualquer nome.
Se ocorrer algum erro, ou uma janela correspondente não puder ser encontrada, a função retorna zero.
Caso contrário, a função retornará um identificador para a janela encontrada.
GetForegroundWindow
Esta função acha a janela que está atualmente em primeiro plano. A janela em primeiro plano é a janela
geralmente na qual o usuário está atualmente trabalhando, ou seja, a janela com o foco. A função retorna
zero se um erro ocorrer, ou o identificador da janela se bem-sucedido.
GetWindowText
Retorna o texto da barra de título de uma janela. Esta função funciona com qualquer janela, não apenas
aquelas em sua aplicação. O texto é devolvido em uma variável do tipo String, passada como parâmetro.
A função também retorna o comprimento do texto, se bem-sucedida, ou zero se ocorreu algum erro.
Seus parâmetros são:
· Hwnd - A janela para ler o título;
· LpString - Variável que recebe o texto da barra de título da janela;
· CCH - O comprimento em caracteres de LpString, ou seja, a quantidade de caracteres do título da
janela.
GetWindowTextLength
Retorna o comprimento em caracteres do texto da barra de título de uma janela, ou retorna zero se
ocorrer erro. Seu único parâmetro é Hwnd, que deve receber o identificador da janela a ser lida.
EnableWindow
Essa função ativa ou desativa uma janela. Se estiver desativada, ela não pode receber o foco e irá ignorar
qualquer tentativa de entrada. Alguns tipos de controles, como botões, aparecerão desativados, embora
qualquer janela possa ser ativada ou desativada. A função retorna zero se a janela está ativada, ou um
valor diferente de zero se a janela está desativada. Recebe dois parâmetros:
· Hwnd -Um identificador para a janela a ser ativada ou desativada;
· FEnable - Se zero, a janela será desativada, caso contrário, a janela será ativada.
SetWindowPos
A função move uma janela para um novo local na tela. Suas coordenadas físicas, dimensões e posição,
bem como o Z-order, que determina se a janela está em cima das outras, podem ser definidos. A função
retorna zero caso ocorra um erro. A relação de seus parâmetros pode ser vista a seguir.
· Hwnd – Move a janela;
· HwndInsertAfter – É o identificador da janela para posicionar esta janela para trás. Um dos seguintes
parâmetros pode ser passado, indicando onde Z-ordem deve colocar a janela:
o HWND_BOTTOM - Coloca a janela na parte inferior;
o HWND_NOTOPMOST - Coloca a janela abaixo de todas as janelas de nível superior, e acima de
todas as janelas não-superiores;
o HWND_TOP - Coloca a janela na parte superior;
o HWND_TOPMOST - Coloca a janela no topo, ou seja, acima de todas as outras janelas, de forma
permanente;
· X - A coordenada x do canto superior esquerdo da janela;
· Y - A coordenada y do canto superior esquerdo da janela;
· CX - A coordenada x do canto inferior direito da janela;
· CY - A coordenada y do canto inferior direito da janela
· Wflags - Zero ou um dos seguintes parâmetros, afirmando como mover a janela:
o SWP_DRAWFRAME - O mesmo que SWP_FRAMECHANGED;
o SWP_FRAMECHANGED - Redesenha totalmente a janela em sua nova posição;
o SWP_HIDEWINDOW - Ocultar a janela da tela;
o SWP_NOACTIVATE – Não ativa a janela após movê-la, a menos que a mesma já esteja ativa;
o SWP_NOCOPYBITS - Não redesenha nada na janela depois que for movida;
o SWP_NOMOVE - Não move a janela;
o SWP_NOSIZE - Não redimensiona a janela;
o SWP_NOREDRAW - Não remove a imagem da janela em sua antiga posição, efetivamente deixando
uma imagem fantasma da tela;
o SWP_NOZORDER - Não muda a posição da janela no Z-ordem;
o SWP_SHOWWINDOW - Mostra a janela, caso esteja oculta.
IsIconic
Esta função verifica se uma determinada janela está minimizada ou não. A função retorna zero se a
janela estiver minimizada ou um valor diferente se a janela não estiver minimizada. Seu único parâmetro
de entrada é Hwnd, onde deve ser passado o identificar da janela a ser verificada.
IsZoomed
Verifica se uma determinada janela está maximizada ou não. A função retorna zero se a janela estiver
maximizada ou retornará um valor diferente se a janela não estiver maximizada. Também tem como
único parâmetro de entrada o Hwnd.
Aplicação usando a categoria Windows
Nesse exemplo criamos uma nova aplicação do tipo Win32 e em seu formulário principal adicionamos
cinco controles TLabel e cinco TButton, como mostra a Figura 3. Para demonstrar as funções, vamos
interagir com a janela dos aplicativos calculadora e bloco de notas do próprio Windows.
Figura 3. Tela do Aplicativo
Modificamos a propriedade Name do Form1 para Frm_FWindows e os cinco controles TButton para
Btn_ShowWin, Btn_Janela, Btn_EstJanela, Btn_MovJanela, e Btn_HabDesab. Cada botão possui um
componente TLabel associado, que deve mudar a propriedade name desses controles para bl_ShowWin,
Lbl_Janela, Lbl_EstJanela, Lbl_MovJanela e Lbl_HabDesab. Podemos então salvar a aplicação e, a unit
com o nome de Unt_Principal e o projeto como PWindows. A Listagem 5 mostra como podemos
utilizar cada uma das APIs da categoria Windows detalhadas no artigo.
Listagem 5. Implementando os Códigos dos Botões
procedure TFrm_FWindows.Btn_ShowWinClick(Sender: TObject);
var
Janela: HWND;
begin
janela := FindWindow(Nil, Pchar('Calculadora'));
ShowWindow(Janela, SW_SHOWNormal);
end;
procedure TFrm_FWindows.Btn_JanelaClick(Sender: TObject);
var
Tela: Integer;
Tamanho: Integer;
TextoWindows: Array [0..255] of Char;
Retorno: Integer;
begin
TextoWindows := '';
Tela
:= GetForegroundWindow();
Tamanho
:= GetWindowTextLength(Tela) + 1;
Retorno
:= GetWindowText(Tela, TextoWindows, Tamanho);
If Retorno > 0 then
ShowMessage(TextoWindows);
end;
procedure TFrm_FWindows.Btn_EstJanelaClick(Sender: TObject);
var
Minimizado: Boolean;
Maximizado: Boolean;
WindowNotp: hWnd;
begin
WindowNotp := FindWindow(Nil, 'Sem título - Bloco de notas'); //Bloco de notas
Minimizado := IsIconic(WindowNotp);
Maximizado := IsZoomed(WindowNotp);
If Minimizado Then
ShowMessage('Bloco de Notas Minimizado')
Else
if Maximizado Then
ShowMessage('Bloco de Notas Maximizado')
Else
ShowMessage('Bloco de Notas esta Restaurado');
end;
procedure TFrm_FWindows.Btn_MovJanelaClick(Sender: TObject);
var
Calculadora: Hwnd;
begin
Calculadora := FindWindow(Nil, 'Calculadora');
if SetWindowPos(Calculadora, HWND_TOPMOST, 0, 0, 300, 0, 0) then
Exit;
end;
procedure TFrm_FWindows.Btn_HabDesabClick(Sender: TObject);
var
WindowEn: hWnd;
begin
WindowEn := FindWindow(Nil, 'Sem título - Bloco de notas'); //Bloco de notas
EnableWindow(WindowEn, False); // True Deixa a Janela Ativa(Normal)
end;
Para testar o código é preciso antes abrir o bloco de notas e a calculadora do Windows. O botão
Btn_ShowWinClick faz uso da API FindWindow. Observe que passamos o nome da janela para a
função. Se seu Windows estiver com o idioma inglês definido, por exemplo, e seus aplicativos também
estiverem nessa língua, você deve passar o que aparece na barra de título. Ao ser encontrada, é retornado
então seu identificador (handle), que é então passado para a função ShowWindow, que irá enviar o
comando de exibição.
Na procedure Btn_JanelaClick utilizamos várias funções para obter informações da janela ativa. A
primeira delas é a GetForegroundWindow, que retorna o identificador da janela em foco. Esse é o ponto
de partida, porque uma vez com este em mãos, podemos acessar a janela.
Na sequência usamos GetWindwTextLength para contar a quantidade de caracteres do título da janela
ativa e depois, através de GetWindowText, obtemos qual é o título. É importante notar que o retorno de
GetWindowText é armazenado e verificado. Se esse for maior que zero é porque o título foi recuperado
com sucesso.
Na próxima procedure utilizamos IsIconic e IsZoomed para identificar se a janela está respectivamente
minimizada ou maximizada. Já na procedure Btn_MovJanelaClick conseguimos mover a janela da
calculadora de lugar utilizando a função SetWindowPos, passando o novo posicionamento.
E para finalizar, na procedure HabDesabClick desativamos a janela do bloco de notas. O importante
nesse pequeno exemplo é que estamos manipulando uma janela que não pertence ao nosso sistema, ou
seja, teoricamente estaria fora de nosso controle.
Contudo, através das APIs da categoria Windows podemos acessá-la. Imagine a situação onde um
usuário está utilizando seu sistema e ao mesmo tempo está com várias outras janelas abertas: para
chamar sua atenção é possível minimizar essas outras e deixar apenas seu sistema em foco.
Cursores
Os Cursores fazem parte de uma categoria que pode facilmente ser confundida com Mouse, que é outra
categoria. Por exemplo, quando o cursor exibir uma ampulheta, não é o mouse que a detém, e sim o
cursor. Nessa categoria temos as seguintes funções relacionadas a seguir:
· ShowCursor: mostra ou esconde o cursor do mouse. Na verdade, é um contador e, se for 1 então o
cursor é visível, se esse contador for negativo, então o cursor não será visível. A função retorna o valor
deste contador e possui o parâmetro Bshow, que se for zero, diminui o contador em 1, caso contrário,
incrementa em 1.
· GetCursor: encontra o identificador para o cursor do mouse em uso atualmente. Esse é o cursor que
está sendo usado para representar o ponteiro do mouse na tela. A função retorna um identificador para a
imagem se bem-sucedido, ou retornará zero se algum erro ocorrer.
· GetCursorPos: lê a posição atual do cursor do mouse, ou seja, as coordenadas X e Y do cursor em
relação à tela. Essas informações são transferidas para o parâmetro LpPoint. A função retorna zero se
ocorreu um erro ou 1 se for bem-sucedida.
· LoadCursor: Carrega um cursor a partir de um arquivo de recurso do programa ou de um arquivo de
recurso de cursor do próprio Windows, que pode ser referenciado pelo seu nome ou pelo seu número de
ID. Se tudo estiver certo, a função retorna um identificador para o cursor carregado, caso contrário, a
função retorna zero. Seus parâmetros são:
o HInstance – Carrega o cursor a partir de um arquivo de recurso do programa. Pode definir zero caso
queira carregar de um arquivo de recurso do Windows.
o LpCursorName - Informe o nome ou o número do cursor, para que seja carregado através, de um
arquivo de recurso do Windows. Para cursores do Windows, use uma das opções para carregar o cursor
desejado:
§ IDC_APPSTARTING - O cursor inicial (seta e ampulheta).
§ IDC_ARROW - O cursor ponteiro de seta regular.
§ IDC_CROSS - O cursor transversal.
§ IDC_IBEAM - O cursor em forma de I (cursor de edição de texto).
§ IDC_NO - O cursor com círculo com uma barra.
§ IDC_SIZEALL - O cursor de quatro pontas.
§ IDC_SIZENESW - O cursor de duas pontas, apontando para o canto superior direito, e inferior
esquerdo.
§ IDC_SIZENS - O cursor de duas pontas, apontando para cima e para baixo.
§ IDC_SIZENWSE - O cursor de duas pontas, apontando para o canto inferior direito, e superior
esquerdo.
§ IDC_SIZEWE - O cursor de duas pontas, apontando para a esquerda e para a direita.
§ IDC_UPARROW - O cursor de seta para cima.
§ IDC_WAIT - O cursor de espera (ampulheta).
· SetCursor: define a imagem usada para representar o cursor do mouse. O novo cursor pode ser
qualquer um válido que tiver sido criado ou carregado. Se for bem-sucedida, a função retorna um
identificador para a imagem do cursor, caso contrário, a função retorna zero. Seu único parâmetro é o
HCursor, que representa um identificador válido de cursor.
· SetCursorPos: define a posição do cursor do mouse. Se você tentar definir as coordenadas fora da área
da tela, por exemplo, se você definir a posição para 700,40 em uma tela de 640x480, o cursor irá até a
borda da tela ou retângulo. Os parâmetros dessa função são justamente essas coordenadas X e Y.
Aplicação de Cursores
Em uma nova aplicação do tipo Win32 adicionamos cinco controles TLabel e cinco TButton, como
mostra a Figura 4. Com a tela já montada, modificamos as propriedades Name do Form1 para
Frm_Cursor e os cinco TButon para Btn_ShowCursor, Btn_TrocaCur, Btn_PosMouse, Btn_BuscCursor,
e Btn_PosCursor. Ao salvar a aplicação definimos a unit com o nome de Unt_Principal e o projeto como
Cursores. A Listagem 6 mostra a utilização da API.
Figura 4. Tela do Aplicativo
Listagem 6. Implementando os Códigos dos Botões
procedure TFrm_Cursor.Btn_ShowCursorClick(Sender: TObject);
begin
ShowCursor(False);
Sleep(7000);
ShowCursor(True);
end;
procedure TFrm_Cursor.Btn_TrocaCurClick(Sender: TObject);
var
CursorAnt: Integer;
CursorNov: Integer;
begin
CursorAnt := GetCursor();
CursorNov := LoadCursor(0, IDC_SIZEALL);
SetCursor(CursorNov);
Sleep (5000);
SetCursor(CursorAnt);
end;
procedure TFrm_Cursor.Btn_PosMouseClick(Sender: TObject);
var
Cord: TPoint;
begin
GetCursorPos(Cord);
Showmessage('O Mouse esta na Posição X ' + IntTostr(Cord.X) +' Posição Y ' +
IntTostr(Cord.Y));
end;
procedure TFrm_Cursor.Btn_BuscCursorClick(Sender: TObject);
var
BusCursor: Integer;
NovCursor: Integer;
begin
BusCursor := LoadCursor(0, IDC_NO);
NovCursor := SetCursor(BusCursor);
Sleep (5000);
SetCursor(NovCursor);
end;
procedure TFrm_Cursor.Btn_PosCursorClick(Sender: TObject);
begin
SetCursorPos(30, 30);
end;
O procedimento Btn_ShowCursorClick simplesmente esconde e exibe o cursor do mouse. Entre uma
operação e outra foi utilizado o procedimento sleep, que pausa o processamento do aplicativo em
execução por uma quantidade de segundos, expressos em milissegundos. Já o procedimento
Btn_TrocaCurClick realiza a troca do cursor. Observe que o cursor em uso é armazenado em uma
variável para que possa posteriormente ser recuperado.
Com o procedimento Btn_PosMouseClick recuperamos a posição atual do cursor e a exibimos em tela.
No procedimento Btn_BuscCursorClick recuperamos um cursor específico e o definimos como cursor
em uso, depois esperamos 5 segundos e desfazemos a operação. Por fim, em Btn_PosCursorClick
modificamos a posição do cursor para 30, 30 pixels.
Registro
Podemos dizer que várias configurações do Sistema Operacional Windows se encontram gravadas no
“Registro do Windows”. O registro do Windows mantém essas configurações em uma espécie de
dicionário, onde temo o par chave e valor. A configuração em si é a chave, e seu conteúdo é o valor.
Qualquer mudança realizada em registros existentes deve ser feita com todo o cuidado, porque chave
com valor incorreto pode desestabilizar o sistema operacional.
Um exemplo de configuração do Windows armazenada no registro são os dados retornados pela função
GetVersionEx, nos traz a versão do Windows instalado no computador. O mesmo poderia ser feito lendo
a seguinte chave do registro:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\ProductName
Então podemos imaginar que, caso modifiquemos essa chave por código, a função GetVersionEx
poderia retornar um valor incorreto. A seguir temos funções da categoria Registro.
RegOpenKeyEx
Abre uma chave no registro do Windows. Esta função não irá criar a chave, se ela não existir. A função
retorna zero se consegui abrir a chave, ou um código de erro diferente de zero caso não consiga abrir,
seus parâmetros são:
· HKey - A chave do registro aberto, ou um dos seguintes valores, sobre o qual a chave está sob:
· HKEY_CURRENT_USER - Armazena informações sobre os programas do usuário atual.
· HKEY_LOCAL_MACHINE - Armazena informações sobre os programas para todos os usuários.
· HKEY_USERS - Contém informações de qualquer usuário, e não apenas o fornecido pela
HKEY_CURRENT_USER.
· HKEY_CURRENT_CONFIG - Armazena informações de configuração do computador.
· HKEY_DYN_DATA - Armazena dados dinâmicos.
· LpSubKey - O nome da chave para abrir.
· UlOptions - Reservado. Defina como zero.
· SamDesired - Um ou mais dos seguintes valores, especificando o acesso de leitura / gravação desejado:
· KEY_ALL_ACCESS - Permissão para todos os tipos de acesso.
· KEY_CREATE_LINK - A permissão para criar links simbólicos.
· KEY_CREATE_SUB_KEY - Permissão para criar subchaves.
· KEY_ENUMERATE_SUB_KEYS - Permissão para enumerar subchaves.
· KEY_EXECUTE - O mesmo que KEY_READ.
· KEY_NOTIFY - Autorização para prestar notificação de alteração.
· KEY_QUERY_VALUE - Permissão para consultar dados subchave.
· KEY_READ - Permissão para o acesso de leitura em geral.
· KEY_SET_VALUE - Permissão para definir dados subchave.
· KEY_WRITE - Permissão para o acesso geral de gravação.
· PhkResult - Recebe a informação da chave do registro.
RegCloseKey
RegCloseKey fecha uma chave de registro. Isto deve ser feito depois que terminar de ler ou escrever no
registro. Fechando a chave de registro são liberados alguns recursos. Obviamente, você não pode mais
usar a chave depois de fechá-la, será preciso abri-la novamente. A função retorna zero se for bemsucedida, ou um código de erro diferente de zero. Seu único parâmetro é HKey, a chave do registro para
fechar.
RegDeleteKey
RegDeleteKey apaga uma chave do registro. A chave a ser apagada não pode ter quaisquer subchaves
dentro dela ou então a operação de exclusão falhará. A função retorna zero se for bem-sucedida, ou um
código de erro diferente de zero. Seus parâmetros são:
· HKey - Um identificador para uma chave do registro, que é a chave a ser excluída. Ou um dos
seguintes valores, especificando a chave do registro:
· HKEY_CLASSES_ROOT - A chave base de HKEY_CLASSES_ROOT.
· HKEY_CURRENT_CONFIG - A chave base de HKEY_CURRENT_CONFIG.
· HKEY_CURRENT_USER - A chave base de HKEY_CURRENT_USER.
· HKEY_DYN_DATA - A chave base de HKEY_DYN_DATA.
· HKEY_LOCAL_MACHINE - A chave base de HKEY_LOCAL_MACHINE
· HKEY_PERFORMANCE_DATA - A chave base de HKEY_PERFORMANCE_DATA.
· HKEY_USERS - A chave base de HKEY_USERS.
· LpSubKey - O nome da subchave dentro da chave HKey a excluir.
RegDeleteValue
Exclui um valor armazenado em uma chave especificada no registro. Esta função só funciona com
valores armazenados, não podendo excluir subchaves. O valor pode ser de qualquer tipo de dado do
registo. A função retorna zero se for bem-sucedida, ou um código de erro diferente de zero. Parâmetros:
· HKey - Um identificador para a chave do registro aberto, que contém o valor a ser excluído.
· LpValueName - O nome do valor a ser excluído.
WriteString
WriteString é uma procedure do Delphi e está declarada na unit Registry. O procedimento irá escrever
um valor, de qualquer tipo de dado, na chave especificada.
ReadString
Já ReadString é uma função, que também está declarada na unit Registry. Ela vai ler o valor de uma
chave, passada como parâmetro, e irá devolver uma string com o valor da chave.
GetValueNames
GetValueNames é uma procedure do Delphi e também está declarada na unit Registry. O procedimento
retorna uma lista de strings (Tstrings) de uma chave específica passada como parâmetro.
Aplicação de Registro
Criamos uma nova aplicação Win32 e seu formulário principal adicionamos seis controles TLabel,
quatro TButton, dois TMemo e um TImage, como vemos na Figura 5. Nosso aplicativa vai configurar a
calcular para ser iniciada junto ou não com a inicialização do Windows e irá mostrar os papéis de parede
registrados.
abrir imagem em nova janela
Figura 5. Tela do Aplicativo
Com a tela já montada modificarmos algumas propriedades. A propriedade Name do Form1 para
Frm_Registro e os seis TLabel para Lbl_Iniciar, Lbl_NaoIniciar, Lbl_ImgPapel, Lbl_ItensChave,
Lbl_ValorChaves e Lbl_NomeChaves. Os quatro TButton para Btn_Iniciar, Btn_NaoIniciar,
Btn_ImgPapel, e Btn_Itens. Vamos agora nomear os nossos dois Memos para Mmo_ValorChaves e
Mmo_NomeChaves. Por último vamos nomear o TImage, para Img_PapParede. Ao salvar o projeto,
salve a unit com o nome de Unt_Principal e o projeto como (Registro). Agora vamos declarar, duas
procedures e uma functions que serão as responsáveis para pegar o caminho da calculadora e por iniciar
a mesma junto com o Window ou não. A Listagem 7 mostra a seção private do formulário.
Listagem 7. Declaração das funções
private
procedure SetAutorum;
procedure NaoSetAutorum;
function DiretorioCalc: String;
{ Private declarations }
Depois de fazer estas declarações, basta pressionar simultaneamente as teclas Shift + Ctrl + C e seu
código inicial será construído. A Listagem 8 apresenta a implementação. Não podemos esquecer de
adicionar a unit Registry ao uses, pois usaremos seus métodos.
Listagem 8. Implementando os Códigos do Aplicativo
function TFrm_Registro.DiretorioCalc: String;
var
Buffer: Array[0..255] of Char;
begin
GetSystemDirectory(Buffer, 255);
Result := StrPas(Buffer) + '\calc.exe';
end;
procedure TFrm_Registro.NaoSetAutorum;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey('\Software\Microsoft\Windows\CurrentVersion\Run', True) then
begin
Reg.DeleteValue('Calculadora');
Reg.DeleteKey('Calculadora');
Reg.CloseKey;
Application.MessageBox('Calculadora Não Será Iniciado Com o Windows !',
' Atenção', MB_ICONINFORMATION + MB_OK);
end;
Finally
Reg.Free;
Inherited;
end;
end;
procedure TFrm_Registro.SetAutorum;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey('\Software\Microsoft\Windows\CurrentVersion\Run', True) then
begin
Reg.WriteString('Calculadora', DiretorioCalc);
Reg.CloseKey;
Application.MessageBox('Calculadora Será Iniciado Junto Com o Windows !',
' Atenção', MB_ICONINFORMATION + MB_OK);
end;
Finally
Reg.Free;
Inherited;
end;
end;
procedure TFrm_Registro.Btn_IniciarClick(Sender: TObject);
begin
SetAutorum;
end;
procedure TFrm_Registro.Btn_NaoIniciarClick(Sender: TObject);
begin
NaoSetAutorum;
end;
procedure TFrm_Registro.Btn_ImgPapelClick(Sender: TObject);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey('\Control Panel\Desktop', True) then
begin
Img_PapParede.Picture.LoadFromFile(Reg.ReadString('Wallpaper'));
Reg.CloseKey;
end;
Finally
Reg.Free;
Inherited;
end;
end;
procedure TFrm_Registro.Btn_ItensClick(Sender: TObject);
var
Reg: TRegistry;
Lista: TStrings;
I: integer;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell
Folders', True) then
begin
Lista := TStringList.Create;
Reg.GetValueNames(Lista);
Mmo_NomeChaves.Lines.Add(Lista.Text);
For I := 0 to Lista.Count -1 do
Mmo_ValorChaves.Lines.Add(Reg.ReadString(Lista.Strings[I]));
Lista.Free;
end;
Finally
Reg.CloseKey;
Reg.Free;
end;
end;
A função DiretorioCalc retorna o diretório de sistema, é nele que se encontra o aplicativo Calculadora.
Na sequência temos o procedimento NaoSetAutorum que simplesmente apaga do registro do Windows
os dados da calculadora que foram armazenados na seção do registro que inicializa aplicativos. Já o
procedimento SetAutorum é responsável por incluir os dados da calculadora nessa seção.
Em ImgPapelClick a imagem selecionada é exibida, a lista de imagens foi obtida por
Btn_ItensClick(Sender: TObject).
Como podemos ver, API do Windows pode e deve na auxiliar em diversas tarefas de desenvolvendo de
softwares e nos dar uma enorme ajuda em diversos aspectos. Vimos aqui um pouco do seu poder e um
pouco de como utilizar todo esse poder.
Existe várias situações em que fazer o uso de API é de uma enorme vantagem. Às vezes, não precisamos
fazer enormes rotinas para uma determinada situação, sendo que na API, existe uma função, que faz a
mesma situação (objetivo). Então porque não chamar esta função?
Obviamente devemos ter um certo cuidado ao chamar funções que se tratam, diretamente do sistema,
arquivos, ou do Registro, enfim categorias destes tipos. Mas aqui, além de aprendermos mais sobre essas
categorias, tanto na teoria, como na prática.
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de
São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.
Como incorporar arquivos de recursos em
executáveis no Delphi
Facilitar e agilizar a distribuição e implantação de sistemas é
essencial para qualquer desenvolvedor. Veja neste artigo como
embutir dentro do arquivo executável de seus aplicativos,
arquivos secundários necessários, como modelos de relatórios.
Fique por dentro
Este artigo é útil em diversos tipos de aplicações que necessitam periodicamente de mudanças, seja para
correção de erros, melhorias, ou adequações.
Para estes casos, utiliza-se alguns recursos, entre eles o de distribuir com a aplicação outro executável.
Isto permite verificar se há mudanças a realizar, validar a versão do software entre outras.
Essas aplicações e diversas outras, utilizam essa técnica de incluir outros arquivos, músicas, imagens,
cursores personalizados, arquivos de texto e outros executáveis “embutidos” no executável principal.
Em Delphi podemos fazer isso utilizando recursos.
Durante o desenvolvimento de um software podemos fazer uso de DLLs, criar modelos de documentos e
tudo isso precisa ser instalado junto com nosso software.
Para facilitar uma implantação ou atualização, podemos incluir esses arquivos, ou como são chamados,
recursos, dentro de nosso executável e no momento de sua execução, extraí-los.
Vídeo, som, imagem ou qualquer arquivo binário, por exemplo, pode ser facilmente incorporado a um
executável feito em Delphi através do uso da diretiva de recursos (RES - Resource).
É muito importante olhar com cuidado o uso dessa técnica, porque esses arquivos de recurso podem até
dobrar o tamanho do executável. Também podemos fazer uso de outras possibilidades, como uso de
pacotes BPL ou bibliotecas DLL dependendo da necessidade do projeto.
Estes recursos são parte do arquivo executável e são carregados ao mesmo tempo em que a aplicação é
executada. O seu carregamento e descarregamento são realizados pela memória livre. Então não há um
limite de números de recursos a serem carregados ou descarregados.
Arquivos de script de recurso (.rc)
Um arquivo de script de recursos nada mais é que um arquivo de texto simples com a extensão .rc. Este
arquivo é compilado por uma ferramenta, para então gerar o arquivo res.
Para isso, geralmente utilizamos a Borland Resource Compiler com a finalidade de criá-lo (res). A
estrutura de um arquivo rc para incorporar arquivos é composta basicamente por três características,
sendo elas:
· Nome do recurso - Uma identificação para o recurso a ser usado pelo executável. Para criar esse
nome, pode-se adotar qualquer caractere, inclusive números de 0 a 9. Só não são permitidos caracteres
especiais (^, %, #, @, !, ~, *, &) e nem qualquer tipo de acentuação.
· Tipo do recurso - Para identificar o tipo do recurso, ou seja, o tipo do arquivo que está sendo
incorporado. Já existem alguns tipos de recursos predefinidos como o Bitmap, Icon, Cursor e etc. Para
outros tipos de arquivos como executáveis, pode-se definir o tipo File.
· Caminho para o recurso – Local onde se encontra o arquivo que se deseja utilizar como recurso. O
diretório deve estar entre os caracteres de aspa dupla, ou será emitido um erro na hora de compilar o
arquivo.
Internacionalizando a aplicação com o script de recurso
Além do que já foi citado até aqui, é possível também internacionalizar a aplicação com um arquivo de
recurso. Para isso deve-se declarar no arquivo de recurso uma tabela de Strings (String Table).
Nessa tabela de Strings inserimos todos os textos para fazer a internacionalização. Existem outras
maneiras de se internacionalizar um projeto desenvolvido em Delphi.
Além deste, os mais comuns são: o uso de DLLs, ResourceString e usando banco de dados para
armazenamento dos textos. No entanto, se o projeto não for muito grande e não conter muitas telas e
objetos, torna-se vantajoso fazer o uso do Resource para essa internacionalização, visto que um arquivo
de Resource, com poucas Strings, não pesaria em nada a aplicação, sendo o tamanho insignificante.
Uma forma simples de fazer uma tabela de Strings para esse propósito seria colocar os textos dos
objetos com uma quebra de linha entre as línguas.
A estrutura de uma String Table é relativamente simples, composta por um identificador e a String em
si. Veja a seguir na Listagem 1 um exemplo de sua estrutura.
Listagem 1. String Table
STRINGTABLE
{
1000, "Português"
1001, "Sim"
1002, "Não"
2000, "Inglês"
2001, "Yes"
2002, "No"
}
Observe que utilizamos a palavra STRINGTABLE para representar a tabela de strings, seu início e fim
são delimitados pelas chaves e cada palavra a ser internacionalizada recebe um número de identificação.
Veja que foi inserida uma quebra (linha em branco) entre os idiomas português e inglês para facilitar a
visualização. O identificador numérico é o que será usado para recuperarmos essa String. Com uma
chamada à função LoadString (BOX 1) passamos esse identificador para que a string seja retornada.
BOX 1. LoadString
Essa função, que está contida na DLL User32.dll, carrega através do executável um recurso de texto que
se encontra em um Resource a ele associado. Ao usarmos esta chamada à API, passamos o parâmetro
Hinstance que é o identificador para uma instância do nosso recurso.
Além disso, passa-se também o parâmetro ID, que é o identificador da string que queremos carregar. No
caso do exemplo exposto anteriormente, seria um dos valores entre 1000 e 2002. Para finalizar, são
passados mais dois parâmetros (LpBuffer e NBufferMax). LpBuffer é o responsável por receber a
String. Essa String terá um caractere nulo para indicar o seu término.
Já NbufferMax é o parâmetro da memória em caracteres. O retorno dessa função é a quantidade de
caracteres copiados para LpBuffer, não incluindo o caractere nulo de terminação ou zero, caso não
exista a String no recurso.
Compilando o arquivo de script de recurso
Para fazer uso das funcionalidades que foram definidas no arquivo de script de recurso da Listagem 1
temos primeiramente que compilá-lo como já mencionado, para que então seja gerado o arquivo RES.
Usaremos o compilador de recursos da Borland (brcc32.exe). O mesmo se encontra no diretório bin do
Delphi. Ao compilar, o arquivo de script de recurso será gerado no mesmo local onde se encontra o
arquivo RES com base nos conteúdos definidos no arquivo RC. O arquivo RES será gerado com o
mesmo nome do arquivo RC.
Para facilitar a criação desse arquivo, é mais fácil colocá-lo no diretório bin do Delphi. Como um
projeto Delphi também gera um arquivo RES com o mesmo nome, temos que nomear este RC com um
nome diferente do projeto para que quando compilarmos arquivo RC, seja gerado um RES com outro
nome, caso contrário, teremos dois arquivos RES com o mesmo nome.
Para efetuar esta compilação, basta abrir um prompt de comando no diretório bin do Delphi e usar o
brcc32 com o nome do arquivo RC. Supondo que o arquivo RC tenha o nome de ArtResource.rc, e já se
encontre no diretório bin, bastaria digitar o comando brcc32 ArtResource.rc.
Assim sendo, será gerado um arquivo com o mesmo nome, porém, com a extensão .RES. Com isso,
teríamos o arquivo RES para fazermos uso em nossas aplicações. Um exemplo da compilação de um
arquivo RC pode ser visto na Figura 1.
Figura 1. Compilação de um arquivo Rc
Nota: O compilador brcc32 talvez seja o mais utilizado por desenvolvedores Delphi, no entanto, há
outros editores e compiladores de recursos como o Windres, Borland Resource WorkShop, Microsoft
Windows Resource Compiler (RC), entre outros.
Usando o arquivo de resource
Para usar esses recursos que foram definidos no arquivo de script de recurso e que agora estão
embutidos nesse arquivo RES basta adicionarmos à nossa aplicação Delphi uma diretiva do compilador
no código-fonte do projeto Delphi. Para visualizar o código-fonte do .dpr usamos o menu View> Source
do Delphi.
No código fonte adicione a diretiva logo abaixo da diretiva de compilação {$R *.res}. Utilizando como
exemplo, um recurso chamado (ArtResource), ficaria da seguinte forma:
{$R *.res}
{$R ArtResource}
Ao compilar o projeto, esse resource é automaticamente incorporado à aplicação juntamente com todos
os recursos contidos nele.
Criando um arquivo res para o exemplo
Para ficar um pouco mais claro o entendimento dessa diretiva e do arquivo de Script de recursos, iremos
criar esse arquivo e compilá-lo com a finalidade de gerar esse resource e usá-lo em uma aplicação.
Como já visto anteriormente, é simplesmente um arquivo de texto com a extensão .rc. Então se pode
facilmente escrevê-lo em um bloco de notas, a exemplo do arquivo rc da Listagem 2.
Listagem 2. Arquivo texto (script) para criação do resource
IMG1
BITMAP
"C:\Arquivos de programas\Arquivos comuns\CodeGear
Shared\Images\Splash\16Color\ATHENA.bmp"
IMG2
BITMAP
"C:\Arquivos de programas\Arquivos comuns\CodeGear
Shared\Images\Splash\16Color\SKYLINE.bmp"
IMG3
JPEG
"C:\Documents and Settings\Administrador\Desktop\Busca.jpg"
ICONE ICON
"C:\Arquivos de programas\Arquivos comuns\CodeGear
Shared\Images\Icons\FACTORY.ico"
CURSR CURSOR "C:\Arquivos de programas\Arquivos comuns\CodeGear
Shared\Images\Cursors\HANDPNT.cur"
DOCTXT
FILE
MUSICA
WAVE
"C:\Documents and Settings\Administrador\Desktop\Doctxt.txt"
"C:\WINDOWS\Media\ringout.wav"
PROGRAMA
FILE
"C:\Documents and
Settings\Administrador\Desktop\PrgSecundario.exe"
STRINGTABLE
{
1000, "Português"
1001, "Imagem Nº1"
1002, "Imagem Nº2"
1003, "Icone"
1004, "Cursor"
1005, "Documento Txt"
1006, "Musica Wave"
1007, "Programa Secundário"
1008, "Programa Uso de Resource"
1009, "Imagem Nº3"
2000, "Inglês"
2001, "Picture Nº1"
2002, "Picture Nº2"
2003, "Icon"
2004, "Cursor"
2005, "Txt Document"
2006, "Music Wave"
2007, "Secondary Program"
2008, "Use Program Resource"
2009, "Picture Nº3"
3000, "Francês"
3001, "Photo Nº1"
3002, "Photo Nº2"
3003, "Icône"
3004, "Curseur"
3005, "Document Txt"
3006, "Musique Wave"
3007, "Programme Secondaire"
3008, "L'Utilisation des Ressources du Programme"
3009, "Photo Nº3"
}
Após ter escrito esse arquivo em bloco de notas, basta salvá-lo com a extensão rc. O nome dado ao
exemplo foi ArtResource.rc.
O conteúdo definido no resource apresentado na Listagem 2 é composto por três imagens: duas do tipo
Bitmap (Img1, Img2) e uma do tipo Jpeg (Img3). Algo muito importante a ser lembrado é que o
caminho desse arquivo no disco ou na rede deve estar entre aspas.
Seguindo ainda a explanação do arquivo, definimos um Icone, um Cursor, um arquivo de texto simples
Txt (DocTxt), um arquivo de música do tipo Wave (Musica) e, para finalizar, foi adicionado outro
executável (PrgSecundario.exe).
Esses foram os arquivos inclusos no resource. Como já mencionado, conforme de arquivos adicionados
como recurso dentro da aplicação, o tamanho cresce consideravelmente.
Após a definição dos arquivos foi criada uma tabela de Strings. Essa tabela será a responsável por
traduzir os botões de um formulário juntamente ao seu título (Caption).
Além disso, estamos definindo três línguas (Português, Inglês e Francês) e nove informações para cada
língua. Essas informações, são referentes aos oito arquivos definidos (Img1, Img2, Img3, Icone, Cursr,
DocTxt, Musica, Programa) e ao título da aplicação.
Após a explicação do arquivo de script de recurso, basta compilá-lo, para que seja gerado o arquivo
ArtResource.Res. Esse executável adicionado ao arquivo res é um aplicativo em Delphi composto por
uma tela simples que exibe um texto em um Memo, conforme mostra a Figura 2, no entanto, poderia ser
qualquer outro aplicativo.
Nota: Como o projeto é composto apenas por um aplicativo e um Memo. Não abordaremos sua criação,
porém, o código deste aplicativo estará disponível juntamente do código-fonte do artigo.
Figura 2. Executável incorporado ao aplicativo
Iniciaremos criando a aplicação que fará uso deste resource. Para isso, crie um novo projeto nomeando-o
como PrgResource. Modifique o nome do formulário principal para Frm_Principal e sua unit para
Unt_Principal.
Após isso, criaremos uma nova unit chamada de Unt_Traduzir que será a responsável por traduzir os
Captions dos componentes da nossa aplicação, como mostra a Listagem 3.
Listagem 3. Unt_Traduzir buscará as línguas e os textos do Resource
01 unit Unt_Traduzir;
02 interface
03 uses
04
Windows, Classes;
05 type
06
TTraduzir = class
07
class procedure BuscaLinguas(const Strings: TStrings);
08
class function BuscaTexto(const Idx, Position: Integer): String;
09
end;
10 implementation
Nesse código podemos ver que a classe TTraduzir será muito simples, contendo apenas dois métodos
(BuscaLinguas e BuscaTexto), nas linhas 07 e 08 respectivamente. A seguir, nas Listagens 4 e 5 temos
a implementação desses dois métodos. Note que temos as chamadas de class procedure e class function,
que são chamados métodos de classe (BOX 2).
BOX 2. Métodos de classe
Diferentemente das chamadas convencionais, aqui usamos métodos de classe. Os métodos de classe são
comumente utilizados para criações de Units especificas de “funções” de auxílio em aplicações. A
grande vantagem é que neste caso não precisamos instanciar a classe para utilizar seus métodos,
bastando apenas utilizar a sintaxe: TClasse.NomeMétodo.
Listagem 4. Implementação do método BuscaLinguas
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class procedure TTraduzir.BuscaLinguas(const Strings: TStrings);
const
Intervalo = 1000;
var
Buffer: array[0..255] of Char;
Indice, Position: Integer;
begin
Position := Intervalo;
Strings.Clear;
Indice := LoadString(HInstance, Position, Buffer, SizeOf(Buffer));
while (Indice <> 0) do
begin
Strings.AddObject(Buffer, TObject(Position));
Position := Position + Intervalo;
Indice := LoadString(HInstance, Position, Buffer, SizeOf(Buffer));
end;
end;
Nesse código temos a implementação do método de classe BuscaLinguas. Nele, carregaremos as
linguagens disponíveis em um TRadioGroup. Estamos definindo um intervalo que é de 1000 (linhas 02 e
03) ou seja, a cada intervalo deste, teremos uma nova língua, e claro que os valores que estiverem dentro
desses intervalos, serão os textos referentes à língua que usaremos para a tradução.
Sendo assim, para pegarmos essas línguas usamos a função LoadString (linha 10). Passando esse
intervalo de 1000, é realizado um loop (linha 11) e enquanto houver esse intervalo (língua) no Resource,
vamos obtendo o seu conteúdo e incrementando o intervalo para verificar se existe outra língua.
A seguir, na Listagem 5 temos a implementação do segundo método definido na classe TTraduzir.
Listagem 5. Implementação do método BuscaTexto
01 class function TTraduzir.BuscaTexto(const Idx, Position: Integer):
String;
02 var
03
Buffer : array[0..255] of Char;
04
Indice : Integer;
05 begin
06
07
Result := '';
Indice := LoadString(HInstance, Idx + Position, Buffer,
SizeOf(Buffer));
08
if (Indice <> 0) then
09
Result := Buffer;
10 end;
Nessa listagem temos a implementação do método BuscaTexto, responsável por buscar o nosso texto
desejado. Passamos dois parâmetros na chamada do mesmo (Idx e Position – linha 01). O parâmetro Idx
refere-se ao número de identificação da língua. Exemplo: (1000 = Português, 2000 = Inglês). O
parâmetro Position é a identificação da posição de qual texto queremos. Exemplo (1 = IMG1, 2 = IMG2
e etc.).
Ou seja, se quisermos o texto em Francês, que está na posição 6. Chamaremos essa função, passando os
parâmetros (3000, 6). No caso do nosso resource feito anteriormente, o resultado seria (Musique Wave).
Voltando ao aplicativo principal
Em nosso formulário principal iremos adicionar os componentes necessários para a criação do exemplo.
Sendo assim, adicionamos ao formulário oito Buttons, um Image, um RadioGroup e um Memo. O
layout proposto para a aplicação é o da Figura 3.
Figura 3. Tela do aplicativo
Primeiramente, definiremos o método WinExecAndWait na sessão private do formulário. O cabeçalho
do método é o seguinte:
function WinExecAndWait(const Path: PChar; const Visibility: Word; const
Wait: Boolean) : Boolean;
A implementação do método encontra-se na Listagem 6.
Listagem 6. Método WinExecAndWait
01 function TFrm_Principal.WinExecAndWait(const Path: PChar;
const Visibility: Word; const Wait: Boolean): Boolean;
02 var
03
ProcessInformation: TProcessInformation;
04
StartupInfo: TStartupInfo;
05 begin
06
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
07
with StartupInfo do
08
begin
09
cb
:= SizeOf(TStartupInfo);
10
lpReserved := NIL;
11
lpDesktop
:= NIL;
12
lpTitle
:= NIL;
13
dwFlags
:= STARTF_USESHOWWINDOW;
14
wShowWindow := Visibility;
15
cbReserved2 := 0;
16
lpReserved2 := NIL
17
end;
18
Result := CreateProcess(NIL, Path, NIL, NIL, FALSE,
NORMAL_PRIORITY_CLASS,
NIL, NIL, StartupInfo, ProcessInformation);
19
if Result then
20
begin
21
with ProcessInformation do
22
begin
23
if Wait then
24
WaitForSingleObject(hProcess, 100);
25
CloseHandle(hThread);
26
CloseHandle(hProcess);
27
end;
28
end;
29 end;
Entre as linhas 03 e 04 temos declaradas duas variáveis. A variável ProcessInformation é responsável
por receber a estrutura de um processo que criaremos com a finalidade de executar esse programa
secundário.
Note que entre as linhas 07 e 17 que há as configurações referentes à variável StartupInfo, que trata-se
da estrutura da janela do aplicativo que passamos no momento da criação do processo. Na linha 18 o
processo é criado usando o CreateProcess, que por sua vez também cria a thread principal e executa o
aplicativo informado no Resource, que no nosso caso é o programa secundário.
Para finalizarmos, é chamado o método CloseHandle para liberar esses processos (linhas 25 e 26).
Continuando a codificação da aplicação principal, faremos a implementação do evento OnCreate que
simplesmente carrega as línguas disponíveis dentro do RadioGroup e, caso haja itens, automaticamente
é setada a primeira posição do mesmo, conforme mostra a Listagem 7.
Listagem 7. Evento FormCreate
01 procedure TFrm_Principal.FormCreate(Sender: TObject);
02 begin
03
TTraduzir.BuscaLinguas(Rgp_Linguas.Items);
04
if (Rgp_Linguas.Items.Count >= 0) then
05
Rgp_Linguas.ItemIndex := 0;
06 end;
Seguindo a sequência dos botões definidos, começaremos pelos eventos OnClick dos botões Btn_Img1 e
Btn_Img2 respectivamente. O código de ambos é mostrado na Listagem 8.
Listagem 8. Evento OnClick que carrega as imagens
01 procedure TFrm_Principal.Btn_Img1Click(Sender: TObject);
02 begin
03
Img_Exibir.Picture.Bitmap.Handle := LoadBitmap(HInstance, 'IMG1');
04 end;
05 procedure TFrm_Principal.Btn_Img2Click(Sender: TObject);
06 begin
07
Img_Exibir.Picture.Bitmap.Handle := LoadBitmap(HInstance, 'IMG2');
08 end;
Observe que a implementação é a mesma dos dois botões, a única diferença é a tag ‘IMG1’ e ‘IMG2’,
que refere-se ao Resource. O método LoadBitmap utiliza o Handle para o carregamento da imagem.
Repare que o resultado é automaticamente atribuído ao objeto Img_Exibir em ambos os casos.
Para o botão 3 (Btn_Img3) faremos uma listagem a parte, afinal, diferentemente das situações anteriores,
aqui carregamos um JPEG, sendo assim, implemente o evento OnClick do mesmo com o código da
Listagem 9.
Listagem 9. Evento OnClick do Btn_Img3 que carrega um JPEG
01 procedure TFrm_Principal.Btn_Img3Click(Sender: TObject);
02 var
03
Res: TResourceStream;
04
MemStream: TMemoryStream;
05
hFind, hRes: THandle;
06 begin
07
hFind := FindResource(HInstance, 'IMG3', 'JPEG');
08
if (hFind <> 0) then
09
begin
10
hRes := LoadResource(HInstance, hFind);
11
if (hRes <> 0) then
12
begin
13
MemStream := TMemoryStream.Create;
14
Res
:= TResourceStream.Create(HInstance, 'IMG3', 'JPEG');
15
try
16
Res.SaveToStream(MemStream);
17
MemStream.SaveToFile(ExtractFileName(ParamStr(0)+'\Img3.jpg'));
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Img_Exibir.Picture.LoadFromFile(
ExtractFileName(ParamStr(0)+'\Img3.jpg'));
finally
Res.Free;
MemStream.Free;
FreeResource(hFind);
FreeResource(hres);
end;
end
else
Application.MessageBox('Não Consegui Carregar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
end
else
Application.MessageBox('Não Consegui Encontrar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
end;
Entre as linhas 03 e 05 temos a definição das variáveis. Na linha 07 usamos o método FindResource que
verifica se existe o Resource e o conteúdo, caso haja, o retorno é atribuído a hFind. Na linha 10 o
Resource é carregado e atribuído a hRes.
Em seguida, é criado o MemoryStream e um ResourceStream (linha 14) que serão utilizados para a
exibição da imagem JPEG. Na linha 16 o Resource salva o conteúdo dentro do MemoryStream, a seguir,
na linha 17 é exportada a imagem, para ser carregada em seguida (linha 18). Entre as linhas 20 a 23 as
variáveis são destruídas e liberadas da memória.
Entre as linhas 26 a 30 temos as mensagens de erro em situações que o conteúdo não é carregado
corretamente.
Continuando, implementaremos o evento OnClick do Btn_Icone que é o próximo da sequência da
Interface da aplicação. No próximo evento carregaremos um Cursor do mouse, para isso,
implementamos o método da Listagem 10.
Listagem 10. Evento OnClick do botão Btn_Cursor
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
procedure TFrm_Principal.Btn_CursorClick(Sender: TObject);
const
CursorRes = 1;
begin
Screen.Cursors[CursorRes] := LoadCursor(HInstance, 'CURSR');
if (Img_Exibir.Cursor = CursorRes) then
begin
Img_Exibir.Cursor := 0;
Btn_Cursor.Cursor := 0;
end
else
begin
Img_Exibir.Cursor := CursorRes;
Btn_Cursor.Cursor := CursorRes;
end;
end;
Na linha 5 é executado o método LoadCursor, atribuindo à Screen.Cursors, onde teremos a modificação
do cursor do mouse relativo à posição da tela. Caso haja um Cursor válido, ele é zerado e vice-versa.
Continuando, o próximo evento será a implementação do OnClick referente ao botão Btn_DocTxt. Sua
implementação encontra-se na Listagem 11.
Listagem 11. Evento OnClcik do botão Btn_DocTxt
01 procedure TFrm_Principal.Btn_DocTxtClick(Sender: TObject);
02 var
03
Res: TResourceStream;
04
hFind, hRes: THandle;
05
Txt: TStringStream;
06 begin
07
hFind := FindResource(HInstance, 'DOCTXT', 'FILE');
08
if (hFind <> 0) then
09
begin
10
hRes := LoadResource(HInstance, hFind);
11
if (hRes <> 0) then
12
begin
13
Txt := TStringStream.Create;
14
Res := TResourceStream.Create(HInstance, 'DOCTXT', 'FILE');
15
try
16
Res.SaveToStream(Txt);
17
Mmo_txt.Lines.Text := Txt.DataString;
18
finally
19
Res.Free;
20
Txt.Free;
21
FreeResource(hFind);
22
FreeResource(hres);
23
end;
24
end
25
else
26
Application.MessageBox('Não Consegui Carregar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
27
end
28
else
29
Application.MessageBox('Não Consegui Encontrar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
30 end;
Entre as linhas 03 a 05 temos a definição das variáveis. Na linha 07 é executado o método FindResource
buscando o conteúdo de acordo com a Tag específica, atribuindo o resultado à variável hFind. Na linha
10 o recurso é carregado e atribuído à hRes.
A Seguir, na linha 13 é criado um StringStream e um ResourceStream (linha 14). O método
SaveToStream é executado na linha 16, salvando o conteúdo dentro do StringStream criado
anteriormente. O seu conteúdo é então atribuído ao Memo (linha 17), por fim, temos a destruição e
liberação dos recursos e objetos da memória (linhas 19 a 22).
Entre as linhas 26 a 29 temos as validações referentes aos casos em que o resource não é carregado.
A seguir, o próximo botão é o responsável por executar o arquivo Wave, sendo assim, temos a chamada
do método PlaySound dentro do evento OnClick do botão Btn_Musica, conforme Listagem 12.
Listagem 12. Implementação do evento OnClick do botão Btn_Musica
01 procedure TFrm_Principal.Btn_MusicaClick(Sender: TObject);
02 begin
03
PlaySound('MUSICA', HInstance, SND_ASYNC or SND_RESOURCE);
04 end;
Observe no código anterior que é utilizada a TAG do Resource (‘MUSICA’), especificada no primeiro
parâmetro, note como é simples a chamada do botão.
Para finalizar as implementações da aplicação principal, temos o código da Listagem 13 que trata a
execução de outro aplicativo.
Listagem 13. Implementação do evento OnClick do botão Btn_Programa
01 procedure TFrm_Principal.Btn_ProgramaClick(Sender: TObject);
02 var
03
Arquivo: String;
04
Res: TResourceStream;
05
hFind, hRes: THandle;
06 begin
07
Arquivo := ExtractFileName(ParamStr(0)+'\PrgSecundario.exe');
08
if not FileExists(Arquivo) then
09
begin
10
hFind := FindResource(HInstance, 'PROGRAMA', 'FILE');
11
if (hFind <> 0) then
12
begin
13
hRes := LoadResource(HInstance, hFind);
14
if (hRes <> 0) then
15
begin
16
Res := TResourceStream.Create(HInstance, 'PROGRAMA', 'FILE');
17
try
18
Res.SavetoFile(Arquivo);
19
finally
20
Res.Free;
21
FreeResource(hFind);
22
FreeResource(hres);
23
end;
24
end
25
else
26
Application.MessageBox('Não Consegui Carregar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
27
end
28
else
29
Application.MessageBox('Não Consegui Encontrar o Arquivo.',
'Atenção', MB_OK + MB_ICONASTERISK);
30
end;
31
WinExecAndWait(Pchar(Arquivo), SW_SHOWNORMAL, True);
32 end;
Entre as linhas 03 a 05 temos as variáveis utilizadas. Na linha 07 é utilizado o método ExtractFileName
considerando o caminho do executável principal.
O nome do arquivo é atribuído à variável Arquivo. Posteriormente, na linha 08 validamos se o caminho
do arquivo é valido. Nas linhas 10 e 11 verificamos a existência do Resource, carregando-o em seguida
na linha 13. A seguir, na linha 16 o ResourceStream é criado e tem seu conteúdo salvo na variável que
armazena o caminho (arquivo - linha 18).
Entre as linhas 20 a 22 temos a destruição e liberação dos recursos e, nas linhas 26 e 29 as mensagens de
restrição relacionadas aos casos em que o recurso não foi carregado com sucesso. Com isso finalizamos
as implementações dos botões.
Finalizando a aplicação de exemplo
Para finalizarmos a aplicação, a última modificação refere-se à adequação do Resource ao projeto,
bastando simplesmente declarar o Resource na aplicação. Para isso, podemos utilizar a opção
Project>View Source dentro do Delphi.
Veja o código completo na Listagem 14. Repare que a linha 07 que inclui o recurso na aplicação através
da diretiva {$R ArtResource}.
Listagem 14. Opção Project>View da aplicação
01 program PrgResource;
02 uses
03
Forms,
04
Unt_Principal in 'Unt_Principal.pas' {Frm_Principal},
05
Unt_Traduzir in 'Unt_Traduzir.pas';
06 {$R *.res}
07 {$R ArtResource}
08 begin
09
Application.Initialize;
10
Application.MainFormOnTaskbar := True;
11
Application.Title := 'Uso de Resource';
12
Application.CreateForm(TFrm_Principal, Frm_Principal);
13
Application.Run;
14 end.
As Figuras 4 e 5 exibem respectivamente dois exemplos de execução do nosso exemplo, o primeiro é
utilizando o recurso de línguas, já o segundo, mostra o carregamento do ícone.
Figura 4. Utilização do recurso de línguas
Figura 5. Carregamento do ícone
Simplificar e agilizar a distribuição e atualização de um software é essencial, e através do uso de
recursos, arquivos secundários requeridos pela aplicação podem ser incorporados ao aplicado, nada mais
de “esquecer uma DLL”, deixe o aplicativo principal lidar com isso.
Usando Windows e
Acessibilidade
Saiba como integrar ao Delphi recursos de
acessibilidade do windows
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas
de São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.
Hoje em dia com todo o avanço da tecnologia, é comum vermos pessoas com diversos tipos de
deficiência usarem o computador, máquinas, robôs e acessórios de todos os tipos. Desenvolvedores
estão cada vez mais atenciosos, a questão da acessibilidade, permitindo assim que haja uma maior
inclusão digital a todos os públicos. Temos vários tipos de situações como pessoas que não possuem os
braços, e usam o computador, acedem as luzes de casa e diversas outras rotinas com o uso e o auxílio do
comando por voz. Uma outra situação por exemplo, é a de pessoas com deficiência visual limitada, que
usam leitores de telas para fazer uso de seus computadores. Através disso, todo e qualquer software, não
importando o tamanho e nem sua finalidade, deve fazer o uso de acessibilidade para que todos possam
usá-lo.
Pode-se dizer que as vezes é algo muito eficaz saber onde o mouse se encontra. Se está em uma
caixa de diálogo, menu, botão, página da web, diretório, arquivo e etc. Nunca nos atentamos a isto, mas
a obtenção desse texto é a maneira mais utilizada pelos leitores de tela para identificar a posição do
cursor do mouse. Isso é muito utilizado pelo Screen Reading. Já se tratando da parte técnica, a interface
IAccessible nos possibilita isto e muito mais! Pode-se dizer que esta é essencial a Microsoft Active
Accessibility. Olharemos mais adiante seus principais recursos, fazendo o seu uso na prática, para obter
o texto de acordo com a posição do mouse, e, assim, entenderemos um pouco mais sobre os recursos que
a acessibilidade pode proporcionar.
Se partimos do princípio que um software acessível, é aquele que exibe seus dados de forma que
um leitor de tela possa processar, e que os controles usados como menus, caixas de diálogos, botões,
caixas de escolhas, podem ser acessados plenamente usando-se apenas o teclado, hoje em dia não temos
muitos softwares acessíveis. Pensando-se que uma pessoa que possui baixa visão ou algum tipo de
deficiência visual, com algum esforço com o mouse conseguiria utilizar seu software, ele não é tão
acessível assim. Porque no caso de pessoas cegas, o mouse não é utilizado, mas sim o teclado. Por esse
motivo, é extremamente importante que os desenvolvedores façam aplicativos cada vez mais acessíveis
ao público deficiente, colocando nomes, hints e rotulando todos os controles. Todo e qualquer objeto
como caixas de textos, label’s, botões e etc.
OleAcc
Como o Microsoft Windows em sua maior parte é construído na forma de bibliotecas dinâmics
(DLL - "Dynamic Link Libraries"). A DLL OleAcc.dll é onde se encontram os códigos e recursos para
usarmos a Microsoft Active Accessibility. Essa biblioteca de vínculo dinâmico é quem vai gerenciar as
solicitações que fazemos à Microsoft Active Accessibility.
Interface IAccessible
Como dito anteriormente, a interface IAccessible é o coração do Microsoft Active Accessibility.
Todos os aplicativos que fazem uso desta interface implementam o Component Object Model (COM)
para representar os elementos da interface do usuário. Os softwares chamam as propriedades e os
métodos de IAccessible para obter as informações desejadas da interface do usuário e de seus
aplicativos. Veja a seguir o que a Microsoft Active Accessibility nos fornece como principal:
•
•
•
•
•
Qual o tipo do objeto: Se é uma janela, menu, botão, caixa de texto e etc.
Nome do objeto: O nome de um arquivo, caption, caso seja um botão, o título em uma situação
de barra de títulos, o texto para objetos do tipo caixa de texto ou seleção.
Localização do objeto: Onde o objeto se encontra, mesmo que esteja dentro de outros objetos.
Exemplo: um Edit que está dentro de um Panel, contido em um Page Control.
O estado em que se encontra o objeto: Se está checado ou não, caso seja uma seleção
(CheckBox). Se está ou não selecionado, se é um arquivo. Se está habilitado ou não, caso seja
um botão e etc.
Notificação de mudanças ocorridas: Como mudanças ao arrastar uma janela de lugar, mover um
arquivo e tudo mais.
IAccessible Métodos e Propriedades
A lista a seguir mostra as propriedades e métodos que encontram-se na interface IAccessible,
organizados em grupos.
Navegação e Hierarquia
accNavigate
get_accChild
get_accChildCount
get_accParent
Propriedades descritivas e Métodos
accDoDefaultAction
get_accDefaultAction
get_accDescription
get_accHelp
get_accHelpTopic
get_accKeyboardShortcut
get_accName
get_accRole
get_accState
get_accValue
put_accName
put_accValue
Seleção e Focus
accSelect
get_accFocus
get_accSelection
Mapeamento espacial
accLocation
accHitTest
Embora a interface IAcessible tenha vários métodos, abordaremos três mais especificamente, na
qual serão utilizados no decorrer do artigo, são eles: Get_AccName, Get_AccValue, Get_AccRole.
Get_AccName
O método get_AccName traz o nome do objeto especificado. Os componentes como menus, caixas
de seleção (CheckBox), caixas de textos (Edit), botões entre outros, possuem seus rótulos, ou seja,
Captions, textos que são exibidos para o usuário. Sendo assim, qualquer rótulo que é exibido para o
usuário é retornado para a propriedade nome do objeto. Exemplo: se tivermos um botão com a
propriedade Caption = “Cadastrar”, então o retorno da propriedade Nome será (Cadastrar).
Get_AccValue
O método get_AccValue traz o valor do objeto, muitos objetos não possuem um valor. Um objeto
como uma barra de títulos por exemplo, podem trazer como retorno o seu Caption para a propriedade
valor. Objetos como ScrollBar e TrackBar, trazem como retorno a porcentagem, a informação ou a
posição em que o mouse encontra-se. Exemplo: considerando que o componente seja uma barra de
rolagem vertical, se o mouse encontra-se acima do marcador da mesma, provavelmente o retorno será a
porcentagem em que se encontra. Caso o mouse esteja abaixo do marcador, o retorno poderá ser a
informação: “Uma página abaixo”, “Rolar para baixo”, “Rolagem vertical para baixo” dependendo de da
posição de onde o mouse se encontra e do tamanho da barra de rolagem.
Get_AccRole
O método get_AccRole traz o papel do objeto. Há um total de 64 roles (papéis de objetos).
Quando o chamamos, é disparada a função GetRoleText passando para a mesma o parâmetro deste
papel, que por sua vez retornará o texto com sua descrição. Exemplo: Se chamado o método
get_AccRole e o seu retorno for o papel ROLE_SYSTEM_MENUITEM, então a função GetRoleText
trará a descrição "Item de Menu". Na Tabela 1 vemos os objetos e suas respectivas descrições.
Tabela 1. Descrição das Roles
Objeto
ROLE_SYSTEM_TITLEBAR
ROLE_SYSTEM_MENUBAR
ROLE_SYSTEM_SCROLLBAR
ROLE_SYSTEM_GRIP
ROLE_SYSTEM_SOUND
ROLE_SYSTEM_CURSOR
ROLE_SYSTEM_CARET
ROLE_SYSTEM_ALERT
ROLE_SYSTEM_WINDOW
ROLE_SYSTEM_CLIENT
Funcionalidade
Para Barra de títulos.
Para a barra de menu (não para o item, e sim para
a barra).
Para barras de rolagem.
O ponteiro do mouse especial, para quando se
manipula as janelas. Exemplo: quando clica-se
sobre a borda de uma janela e a arrasta para
aumentar seu tamanho.
É um sistema de som, o qual está associado com
vários eventos do sistema.
O ponteiro do mouse.
O cursor do sistema.
É um alerta ou uma condição que o usuário deve
ser notificado.
Representa a moldura da janela, que contém
objetos filho, como uma barra de títulos, cliente e
outros contidos em uma janela.
Representa a área de cliente da janela.
ROLE_SYSTEM_MENUPOPUP
ROLE_SYSTEM_MENUITEM
ROLE_SYSTEM_TOOLTIP
ROLE_SYSTEM_APPLICATION
ROLE_SYSTEM_DOCUMENT
ROLE_SYSTEM_PANE
ROLE_SYSTEM_CHART
ROLE_SYSTEM_DIALOG
ROLE_SYSTEM_BORDER
ROLE_SYSTEM_GROUPING
ROLE_SYSTEM_SEPARATOR
ROLE_SYSTEM_TOOLBAR
ROLE_SYSTEM_STATUSBAR
ROLE_SYSTEM_TABLE
ROLE_SYSTEM_COLUMNHEADER
ROLE_SYSTEM_ROWHEADER
ROLE_SYSTEM_COLUMN
ROLE_SYSTEM_ROW
ROLE_SYSTEM_CELL
ROLE_SYSTEM_LINK
ROLE_SYSTEM_HELPBALLOON
ROLE_SYSTEM_CHARACTER
ROLE_SYSTEM_LIST
ROLE_SYSTEM_LISTITEM
ROLE_SYSTEM_OUTLINE
ROLE_SYSTEM_OUTLINEITEM
ROLE_SYSTEM_PAGETAB
ROLE_SYSTEM_PROPERTYPAGE
ROLE_SYSTEM_INDICATOR
ROLE_SYSTEM_GRAPHIC
ROLE_SYSTEM_STATICTEXT
ROLE_SYSTEM_TEXT
ROLE_SYSTEM_PUSHBUTTON
ROLE_SYSTEM_CHECKBUTTON ROLE
ROLE_SYSTEM_RADIOBUTTON
Para menus do tipo popup.
É o item de um menu.
Para fornecer dicas e truques.
É a janela principal de um aplicativo.
Janela de um documento. Só se aplica a interface
de documentos múltiplos (MDI).
Para painéis dentro de janelas ou quadro de
documentos.
Representa gráficos.
Caixas de diálogo ou caixas de mensagem.
A borda de uma janela.
Para agrupamento de objetos.
Para barras de separação.
Para barra de ferramentas.
Para barra de status.
Para tabelas.
O cabeçalho da coluna de uma tabela.
O cabeçalho da linha de uma tabela.
Coluna de células de uma tabela.
Linha de uma célula de uma tabela.
Célula de uma tabela.
Para links.
Apresenta um tópico de ajuda na forma de uma
dica de ferramenta ou a ajuda de balão.
É um objeto gráfico do tipo “cartoon”, como o
assistente do Office.
Para listas.
É um item de uma lista, como um item de um
ComboBox, Listbox e etc.
Para objetos que possuem estrutura de árvores,
como um TreeView.
É o item de uma estrutura do tipo árvore, como
um item de um TreeView.
Representa a página de uma guia, como um
TabIndex de um Page Control.
É uma folha de propriedades.
É um indicador, como um ponteiro gráfico, que
aponta para um item.
Para imagens.
Para controles que exibem textos somente de
leitura. Esses não podem ser selecionados ou
modificados. Para controles que exibem textos
somente de leitura. Esses não podem ser
selecionados ou modificados.
Para textos.
Controle de botão.
Controles do tipo Check Box.
Para objetos do tipo botão de escolha, como um
ROLE_SYSTEM_COMBOBOX
ROLE_SYSTEM_DROPLIST
ROLE_SYSTEM_PROGRESSBAR
ROLE_SYSTEM_DIAL
ROLE_SYSTEM_HOTKEYFIELD
ROLE_SYSTEM_SLIDER
ROLE_SYSTEM_SPINBUTTON
ROLE_SYSTEM_DIAGRAM
ROLE_SYSTEM_ANIMATION
ROLE_SYSTEM_EQUATION
ROLE_SYSTEM_BUTTONDROPDOWN
ROLE_SYSTEM_BUTTONMENU
ROLE_SYSTEM_BUTTONDROPDOWNGRID
ROLE_SYSTEM_WHITESPACE
ROLE_SYSTEM_PAGETABLIST
ROLE_SYSTEM_CLOCK
ROLE_SYSTEM_SPLITBUTTON
ROLE_SYSTEM_IPADDRESS
ROLE_SYSTEM_OUTLINEBUTTON
Radio Button.
Controles do tipo Combo Box.
Controle de calendário, SysDateTimePick32. É
usada para indicar que foi encontrada uma data ou
um tipo de calendário.
Para barras de progresso.
Representa um mostrador ou maçaneta.
Para atalhos de teclado.
Para controles deslizantes.
Para caixas de rotação, que é um controle que
permite ao usuário, aumentar ou diminuir o valor
exibido em um controle que esteja associado a
essa caixa de rotação.
Gráfico de diagramas.
É uma animação, um exemplo desse tipo é quando
copiamos arquivos muitos grandes, e é exibida a
animação dos arquivos movendo-se de uma pasta
para a outra, ou quando apagamos e etc.
Para equações matemáticas.
É um botão que suspende uma lista de itens.
É um botão que desce um menu.
É um botão que suspende uma grade.
Espaço em branco entre outros objetos.
Para controles do tipo guia, como Page Control.
Para o relógio.
Representa um botão em uma barra de ferramentas
que tem uma lista de ícones suspenso diretamente
adjacente ao botão.
Para uso de endereço de Internet Protocol (IP).
Para navegar entre os itens. Pode se usar as setas
do teclado para navegar, percorrer, expandir e
recolher os itens.
AccessibleObjectFromPoint
A função AccessibleObjectFromPoint, obtém a interface IAccessible referente aoobjeto do ponto
especifico na tela. Caso o ponto especifico na tela não seja um objeto acessível, ou seja, não suporta a
interface IAccessible, então a função retornará o “pai” deste objeto. Cabe citar que além desta função,
podemos obter a interface do objeto através ainda dos métodos AccessibleObjectFromEvent e
AccessibleObjectFromWindows.
WM_GETOBJECT
Quando uma das três funções do AccessibleObjectFrom(X) é chamada, o Active Accessibility
envia a mensagem do Windows WM_GETOBJECT. Quando a mensagem é recebida, retornará um
ponteiro para o objeto que que implementa a interface IAccessible. Este ponteiro é um LResult que é
obtido chamando o método LResultFromObject. A Active Accessibility junto com a biblioteca COM
(Component Object Model), nos passa esse ponteiro da interface IAccessible. Para ficar mais claro o
entendimento, quando chamamos o AccessibleObjectFromPoint, a Active Accessibility vai enviar a
mensagem WM_GETOBJECT, que por sua vez nos retornará a interface IAccessible do objeto, para que
possamos usar suas propriedades e métodos. Para a maioria dos objetos comuns como (caixas de textos,
menus, botões e tantos outros), a interface do IAccessible retornada, irá nos trazer informações
completas do objeto como (nome, valor, estado, localização e todas as outras).
Obtendo o texto ou a informação de onde está o mouse
Em vários fóruns da internet, é possível encontrar tópicos questionando como obter o texto em que
o mouse está em cima, ou sua posição. Com certeza, esse é o fator mais importante para um software
leitor de tela, ou para aplicações que necessitam fazer algum tipo de monitoramento do mouse.
A Microsoft Accessibility nos facilita isso e muito mais, nas telas, janelas e diálogos do Windows,
assim como em diversos outros lugares do mesmo. É possível não só recuperar a informação de onde o
mouse se encontra, como recuperar muitas outras informações e ainda ter uma maior interatividade com
o Sistema Operacional.
Pessoas que possuem algum tipo de deficiência visual, desde que não sejam deficientes visuais
plenas (cegos), geralmente usam ampliadores de telas para operar o computador. Já para o segundo caso,
esses necessitam do uso de softwares leitores de telas.
Pessoas que não possuem visão não usam somente leitores de tela. Geralmente elas usam um
software que seja totalmente operado pelo mouse. Não que não consigam operar o mouse, mas sim
porque é bem mais acessível o uso do teclado, já que com ele, pode-se abrir os menus, acessar os
aplicativos e arquivos, assim como navegar na internet e tudo mais.
E se caso ainda assim precisar usar o mouse, esses softwares geralmente tem a opção de ativar a
setas de direção do teclado, para que se torne possível mover o mouse com elas. Não existem muitos
softwares desse tipo no mercado. Porém o que está aumentando bastante, é o número de empresas que
estão a olhar esse lado da acessibilidade. Empresas como IBM, Apple, Adobe e algumas outras, estão
fazendo seus softwares cada vez mais acessíveis. Já empresas como OG Giken, Aska, Panasonic e
Okada, estão criando equipamentos e acessórios de todos os tipos para pessoas com todo o tipo de
deficiência física.
Então pegaremos o texto de onde o mouse está, mas lembrando que a Microsoft Accessibility, nos
permite muito mais que isso. Pegaremos só o texto porque geralmente é o que os leitores de telam
utilizam. Os leitores obtém esse texto, e usam um sintetizador de voz para pronunciar o texto ao usuário.
Dentro dos diversos recursos que a Microsoft Active Accessibility nos possibilita, destaca-se o de poder
sabermos se em determinado diretório está faltando alguma pasta ou arquivo. E ainda, usando outros
recursos, saber tudo sobre cada arquivo e pasta dentro de um diretório, ou um local especifico, como
uma barra de menu ou de ferramentas por exemplo.
Criando o aplicativo de exemplo
Para dar início ao nosso projeto, iniciaremos criando uma nova aplicação Window Forms
tradicional. Salve a unit principal com o nome de Unt_Principal.pas e o projeto conforme gosto.
Adicione a seguir uma nova Unit e a nomeie como Oleacc.pas. Esta Unit será responsável por
implementar os métodos na qual a Microsoft Active Accessibility disponibiliza. Sendo assim, a principal
implementação será justamente nessa Unit. Como pode-se notar, ela foi dividida em várias listagens
devido sua extensão. Na Listagem 1 temos a definição da primeira parte em que encontram-se as
declarações de algumas constantes. Vale salientar que não utilizaremos todos os recursos da interface
aqui, no entanto, iremos implementá-la por completo, para que seja possível a utilização de recursos que
vão além do que é mostrado.
Nota: Aplicativos quando aplicações fazem chamadas a DLLs do Windows, alguns antivírus emitem
alerta que este pode estar infectado.
Listagem 1. Constantes definidas na unit Oleacc
unit Oleacc;
Interface
uses
Windows, Variants, Classes;
const
ACCDLL = 'OLEACC.DLL';
const
DISPID_ACC_PARENT
DISPID_ACC_CHILDCOUNT
DISPID_ACC_CHILD
=-5000;
=-5001;
=-5002;
DISPID_ACC_NAME
DISPID_ACC_VALUE
DISPID_ACC_DESCRIPTION
DISPID_ACC_ROLE
DISPID_ACC_STATE
DISPID_ACC_HELP
DISPID_ACC_HELPTOPIC
DISPID_ACC_KEYBOARDSHORTCUT
DISPID_ACC_FOCUS
DISPID_ACC_SELECTION
DISPID_ACC_DEFAULTACTION
=-5003;
=-5004;
=-5005;
=-5006;
=-5007;
=-5008;
=-5009;
=-5010;
=-5011;
=-5012;
=-5013;
DISPID_ACC_SELECT
DISPID_ACC_LOCATION
DISPID_ACC_NAVIGATE
DISPID_ACC_HITTEST
DISPID_ACC_DODEFAULTACTION
=-5014;
=-5015;
=-5016;
=-5017;
=-5018;
const
NAVDIR_MIN
NAVDIR_UP
NAVDIR_DOWN
NAVDIR_LEFT
NAVDIR_RIGHT
NAVDIR_NEXT
NAVDIR_PREVIOUS
NAVDIR_FIRSTCHILD
NAVDIR_LASTCHILD
NAVDIR_MAX
=$00000000;
=$00000001;
=$00000002;
=$00000003;
=$00000004;
=$00000005;
=$00000006;
=$00000007;
=$00000008;
=$00000009;
SELFLAG_NONE
SELFLAG_TAKEFOCUS
SELFLAG_TAKESELECTION
SELFLAG_EXTENDSELECTION
SELFLAG_ADDSELECTION
SELFLAG_REMOVESELECTION
SELFLAG_VALID
=$00000000;
=$00000001;
=$00000002;
=$00000004;
=$00000008;
=$00000016;
=$00000032;
STATE_SYSTEM_UNAVAILABLE
STATE_SYSTEM_SELECTED
STATE_SYSTEM_FOCUSED
STATE_SYSTEM_PRESSED
STATE_SYSTEM_CHECKED
STATE_SYSTEM_MIXED
STATE_SYSTEM_INDETERMINATE
STATE_SYSTEM_READONLY
STATE_SYSTEM_HOTTRACKED
STATE_SYSTEM_DEFAULT
STATE_SYSTEM_EXPANDED
=$00000001;
=$00000002;
=$00000004;
=$00000008;
=$00000010;
=$00000020;
=STATE_SYSTEM_MIXED;
=$00000040;
=$00000080;
=$00000100;
=$00000200;
STATE_SYSTEM_COLLAPSED
=$00000400;
STATE_SYSTEM_BUSY
=$00000800;
STATE_SYSTEM_FLOATING
=$00001000;
STATE_SYSTEM_MARQUEED
=$00002000;
STATE_SYSTEM_ANIMATED
=$00004000;
STATE_SYSTEM_INVISIBLE
=$00008000;
STATE_SYSTEM_OFFSCREEN
=$00010000;
STATE_SYSTEM_SIZEABLE
=$00020000;
STATE_SYSTEM_MOVEABLE
=$00040000;
STATE_SYSTEM_SELFVOICING
=$00080000;
STATE_SYSTEM_FOCUSABLE
=$00100000;
STATE_SYSTEM_SELECTABLE
=$00200000;
STATE_SYSTEM_LINKED
=$00400000;
STATE_SYSTEM_TRAVERSED
=$00800000;
STATE_SYSTEM_MULTISELECTABLE=$01000000;
STATE_SYSTEM_EXTSELECTABLE =$02000000;
STATE_SYSTEM_ALERT_LOW
=$04000000;
STATE_SYSTEM_ALERT_MEDIUM
=$08000000;
STATE_SYSTEM_ALERT_HIGH
=$10000000;
STATE_SYSTEM_PROTECTED
=$20000000;
STATE_SYSTEM_HASPOPUP
=$40000000;
STATE_SYSTEM_VALID
=$1FFFFFFF;
ROLE_SYSTEM_TITLEBAR
ROLE_SYSTEM_MENUBAR
ROLE_SYSTEM_SCROLLBAR
ROLE_SYSTEM_GRIP
ROLE_SYSTEM_SOUND
ROLE_SYSTEM_CURSOR
ROLE_SYSTEM_CARET
ROLE_SYSTEM_ALERT
ROLE_SYSTEM_WINDOW
ROLE_SYSTEM_CLIENT
ROLE_SYSTEM_MENUPOPUP
ROLE_SYSTEM_MENUITEM
ROLE_SYSTEM_TOOLTIP
ROLE_SYSTEM_APPLICATION
ROLE_SYSTEM_DOCUMENT
ROLE_SYSTEM_PANE
ROLE_SYSTEM_CHART
ROLE_SYSTEM_DIALOG
ROLE_SYSTEM_BORDER
ROLE_SYSTEM_GROUPING
ROLE_SYSTEM_SEPARATOR
ROLE_SYSTEM_TOOLBAR
ROLE_SYSTEM_STATUSBAR
ROLE_SYSTEM_TABLE
ROLE_SYSTEM_COLUMNHEADER
ROLE_SYSTEM_ROWHEADER
ROLE_SYSTEM_COLUMN
ROLE_SYSTEM_ROW
ROLE_SYSTEM_CELL
ROLE_SYSTEM_LINK
ROLE_SYSTEM_HELPBALLOON
ROLE_SYSTEM_CHARACTER
ROLE_SYSTEM_LIST
ROLE_SYSTEM_LISTITEM
ROLE_SYSTEM_OUTLINE
ROLE_SYSTEM_OUTLINEITEM
ROLE_SYSTEM_PAGETAB
ROLE_SYSTEM_PROPERTYPAGE
ROLE_SYSTEM_INDICATOR
=$00000001;
=$00000002;
=$00000003;
=$00000004;
=$00000005;
=$00000006;
=$00000007;
=$00000008;
=$00000009;
=$00000010;
=$00000011;
=$00000012;
=$00000013;
=$00000014;
=$00000015;
=$00000016;
=$00000017;
=$00000018;
=$00000019;
=$00000020;
=$00000021;
=$00000022;
=$00000023;
=$00000024;
=$00000025;
=$00000026;
=$00000027;
=$00000028;
=$00000029;
=$00000030;
=$00000031;
=$00000032;
=$00000033;
=$00000034;
=$00000035;
=$00000036;
=$00000037;
=$00000038;
=$00000039;
ROLE_SYSTEM_GRAPHIC
=$00000040;
ROLE_SYSTEM_STATICTEXT
=$00000041;
ROLE_SYSTEM_TEXT
=$00000042;
ROLE_SYSTEM_PUSHBUTTON
=$00000043;
ROLE_SYSTEM_CHECKBUTTON
=$00000044;
ROLE_SYSTEM_RADIOBUTTON
=$00000045;
ROLE_SYSTEM_COMBOBOX
=$00000046;
ROLE_SYSTEM_DROPLIST
=$00000047;
ROLE_SYSTEM_PROGRESSBAR
=$00000048;
ROLE_SYSTEM_DIAL
=$00000049;
ROLE_SYSTEM_HOTKEYFIELD
=$00000050;
ROLE_SYSTEM_SLIDER
=$00000051;
ROLE_SYSTEM_SPINBUTTON
=$00000052;
ROLE_SYSTEM_DIAGRAM
=$00000053;
ROLE_SYSTEM_ANIMATION
=$00000054;
ROLE_SYSTEM_EQUATION
=$00000055;
ROLE_SYSTEM_BUTTONDROPDOWN =$00000056;
ROLE_SYSTEM_BUTTONMENU
=$00000057;
ROLE_SYSTEM_BUTTONDROPDOWNGRID =$00000058;
ROLE_SYSTEM_WHITESPACE
=$00000059;
ROLE_SYSTEM_PAGETABLIST
=$00000060;
ROLE_SYSTEM_CLOCK
=$00000061;
ROLE_SYSTEM_SPLITBUTTON
=$00000062;
ROLE_SYSTEM_IPADDRESS
=$00000063;
ROLE_SYSTEM_OUTLINEBUTTON
=$00000064;
O código da Listagem 1 trata-se somente de constantes como pode ser notado. Aqui temos na
seguinte ordem a referência a dll (OLEACC.DLL), que será utilizada para a chamada dos métodos de
IAccessible. As constantes referente a hierarquia de objetos, constantes relacionadas a propriedades
descritivas dos objetos, constantes dos métodos, constantes de entrada para as constantes
disp_acc_navigate, disp_acc_Select, disp_acc_state e disp_acc_role.
Continuando, definiremos as demais constantes e as chamadas para a DLL conforme a Listagem
2. Note que não há uma implementação de fato, mas sim, apenas as definições as chamadas dos métodos
disponíveis para acesso.
Listagem 2. Continuação da Unit Oleacc.pas
const
LIBID_Accessibility:
IID_IAccessible:
IID_IDispatch:
IID_IEnumVARIANT:
TGUID
TGUID
TGUID
TGUID
=
=
=
=
'{1ea4dbf0-3c3b-11cf-810c-00aa00389b71}';
'{618736e0-3c3d-11cf-810c-00aa00389b71}';
'{a6ef9860-c720-11d0-9337-00a0c90dcaa9}';
'{00020404-0000-0000-c000-000000000046}';
{$EXTERNALSYM IEnumVariant}
type
IEnumVariant = interface(IUnknown)
['{00020404-0000-0000-C000-000000000046}']
function Next(celt: LongWord; var rgvar: OleVariant; out pceltFetched:
LongWord): HResult; stdcall;
function Skip(celt: LongWord): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out Enum: IEnumVariant): HResult; stdcall;
end;
type
IAccessible = interface;
LPACCESSIBLE = ^IAccessible;
LPDispatch = ^IDispatch;
IAccessible = interface(IDispatch)
['{618736e0-3c3d-11cf-810c-00aa00389b71}']
function Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
function Get_accChildCount(out pcountChildren: Integer): HResult;
stdcall;
function Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch):
HResult; stdcall;
function Get_accName(varChild: OleVariant; out pszName: WideString):
HResult; stdcall;
function Get_accValue(varChild: OleVariant; out pszValue: WideString):
HResult; stdcall;
function Get_accDescription(varChild: OleVariant; out pszDescription:
WideString): HResult; stdcall;
function Get_accRole(varChild: OleVariant; out pvarRole: OleVariant):
HResult; stdcall;
function Get_accState(varChild: OleVariant; out pvarState: OleVariant):
HResult; stdcall;
function Get_accHelp(varChild: OleVariant; out pszHelp: WideString):
HResult; stdcall;
function Get_accHelpTopic(out pszHelpFile: WideString; varChild:
OleVariant; out pidTopic: Integer): HResult; stdcall;
function Get_accKeyboardShortcut(varChild: OleVariant; out
pszKeyboardShortcut: WideString): HResult; stdcall;
function Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
function Get_accSelection(out pvarChildren: OleVariant): HResult;
stdcall;
function Get_accDefaultAction(varChild: OleVariant; out
pszDefaultAction: WideString): HResult; stdcall;
function accSelect(flagsSelect: Integer; varChild: OleVariant): HResult;
stdcall;
function accLocation(out pxLeft: Integer; out pyTop: Integer; out
pcxWidth: Integer; out pcyHeight: Integer; varChild: OleVariant): HResult;
stdcall;
function accNavigate(navDir: Integer; varStart: OleVariant; out
pvarEndUpAt: OleVariant): HResult; stdcall;
function accHitTest(xLeft: Integer; yTop: Integer; out pvarChild:
OleVariant): HResult; stdcall;
function accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
function Set_accName(varChild: OleVariant; const pszName: WideString):
HResult; stdcall;
function Set_accValue(varChild: OleVariant; const pszValue: WideString):
HResult; stdcall;
end;
type
LPFNLresultFromObject = function (riid: TGUID; wParam: WPARAM; pAcc:
LPACCESSIBLE): HResult; stdcall;
LPFNObjectFromLresult = function (lResult: LRESULT; riid: TGUID; wParam:
WPARAM; ppvObject: LPACCESSIBLE): HResult; stdcall;
LPFNAccessibleObjectFromWindow = function (wnd: HWND; dwId: DWORD; riid:
TGUID; ppvObject: pointer): HResult; stdcall;
LPFNAccessibleObjectFromPoint = function (ptScreen: TPOINT; pAcc:
LPACCESSIBLE; var pvarChild: olevariant): HResult; stdcall;
LPFNCreateStdAccessibleObject = function (wnd: HWND; idObject: longint;
riid: TGUID; ppvObject: LPACCESSIBLE): HResult; stdcall;
LPFNAccessibleChildren = function (paccContainer: LPAccessible;
iChildStart, cChildren: longint; rgvarChildren: pvariant; var pcObtained:
longint): HResult; stdcall;
function GetOleaccVersionInfo(pdwVer, pdwBuild: pdword): HResult; stdcall;
function LresultFromObject(riid: TGUID; wParam: WPARAM; pAcc:
LPACCESSIBLE): HResult; stdcall;
function ObjectFromLresult(lResult: LRESULT; riid: TGUID; wParam: WPARAM;
ppvObject: LPACCESSIBLE): HResult; stdcall;
function WindowFromAccessibleObject(pAcc: IACCESSIBLE; var phwnd: HWND):
HResult; stdcall;
function AccessibleObjectFromWindow(hWnd: HWnd; dwId: DWord; const riid:
TGUID; var ppvObject: pointer): HResult; stdcall;
function AccessibleObjectFromEvent(wnd: HWND; dwId: DWORD; dwChildId:
DWORD; pAcc: LPACCESSIBLE; var pvarChild: variant): HResult; stdcall;
function AccessibleObjectFromPoint(ptScreen: TPOINT; pAcc: LPACCESSIBLE;
var pvarChild: olevariant): HResult; stdcall;
function CreateStdAccessibleObject(wnd: HWND; idObject: longint; riid:
TGUID; ppvObject: LPACCESSIBLE): HResult; stdcall;
function CreateStdAccessibleProxyA(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
function CreateStdAccessibleProxyW(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
function CreateStdAccessibleProxy(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
function AccessibleChildren (paccContainer: IAccessible; iChildStart,
cChildren: longint; rgvarChildren: pvariant; var pcObtained: longint):
HResult; stdcall;
function GetRoleTextA(lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; stdcall;
function GetRoleTextW(lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; stdcall;
function GetRoleText (lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; stdcall;
function GetStateTextA(lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; stdcall;
function GetStateTextW(lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; stdcall;
function GetStateText (lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; stdcall;
implementation
function GetOleaccVersionInfo(pdwVer, pdwBuild: pdword): HResult; stdcall;
external ACCDLL;
function LresultFromObject(riid: TGUID; wParam: WPARAM; pAcc:
LPACCESSIBLE): HResult; external ACCDLL;
function ObjectFromLresult(lResult: LRESULT; riid: TGUID; wParam: WPARAM;
ppvObject: LPACCESSIBLE): HResult; external ACCDLL;
function WindowFromAccessibleObject (pAcc: IACCESSIBLE; var phwnd: HWND):
HResult; external ACCDLL;
function AccessibleObjectFromWindow(hWnd: HWnd; dwId: DWord; const riid:
TGUID; var ppvObject: pointer): HResult; external ACCDLL
name'AccessibleObjectFromWindow';
function AccessibleObjectFromEvent(wnd: HWND; dwId: DWORD; dwChildId:
DWORD; pAcc: LPACCESSIBLE; var pvarChild: variant): HResult; external
ACCDLL;
function AccessibleObjectFromPoint(ptScreen: TPOINT; pAcc: LPACCESSIBLE;
var pvarChild: olevariant): HResult; external ACCDLL
name'AccessibleObjectFromPoint';
function CreateStdAccessibleObject(wnd: HWND; idObject: longint; riid:
TGUID; ppvObject: LPACCESSIBLE): HResult; external ACCDLL;
function CreateStdAccessibleProxyA(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
external ACCDLL;
function CreateStdAccessibleProxyW(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
external ACCDLL;
function CreateStdAccessibleProxy(wnd: HWND; pszClassName: pchar;
idObject: longint; riid: TGUID; ppvObject: pointer): HResult; stdcall;
external ACCDLL name 'CreateStdAccessibleProxyA';
function AccessibleChildren (paccContainer: IACCESSIBLE; iChildStart,
cChildren: longint; rgvarChildren: Pvariant; var pcObtained: longint):
HResult; external ACCDLL;
function GetRoleTextA(lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; external ACCDLL;
function GetRoleTextW(lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; external ACCDLL;
function GetRoleText(lRole: DWORD; lpszRole: pchar; cchRoleMax: byte):
HResult; external ACCDLL name 'GetRoleTextA';
function GetStateTextA(lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; external ACCDLL;
function GetStateTextW(lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; external ACCDLL;
function GetStateText(lStateBit: DWORD; lpszState: pwidechar; cchState:
byte): HResult; external ACCDLL name 'GetStateTextA';
No código da Listagem 2, cabe apenas comentar logo o início onde são declarados os GUIDs (ver
BOX 1) da Interface IAccessible. As demais chamadas são apenas as declarações para o uso da DLL.
BOX 1. Globally Unique Identifier
GUID significa identificador global único. Este identificador é representado por uma string entre
colchetes. Sua declaração não é obrigatória. Cada interface é identificada por um índice único em todo
Sistema Operacional, o GUID.
O GUID é um valor binário de 16 ou 128 bytes, antes da declaração da lista de membros. Sua
declaração tem o seguinte formato: ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'] Onde cada x é um
digito hexadecimal (0 à 9 ou A à F). O número total de chaves únicas (2128 ou ~3.4×1038), sendo
assim, a probabilidade do mesmo número ser gerado duas vezes é muito pequena. Para gerar GUID,
basta dentro do IDE só pressionar CTRL + SHIFT + G simultaneamente no editor de código.
Finalizada a Unit Oleacc.pas, daremos continuidade ao desenvolvimento de nosso aplicativo. O
objetivo é que dada a posição do cursor do mouse, o aplicativo consiga obter os textos e/ou informações.
Sendo assim, no formulário principal adicione 3 componentes, 1 TTimer, e 2 TMemos. Conforme
mostra a Figura 1.
Figura 1. Tela principal da aplicação
Após a inserção dos componentes, modificaremos a propriedade Name do Form1 para
Frm_Principal, o Memo1 para MemoInfo, Memo2 para Mmo_TxtLeitor e por último o TTimer para
Tmr_Info. Configure no Timer a propriedade Interval 500 milissegundos.
Feita a tela, passaremos para a codificação. A primeira tarefa é adicionar a Unit Oleacc ao Uses do
nosso formulário. No escopo private definiremos as variáveis necessárias juntamente com o método
Get_Info, conforme a seguir:
...
private
TextoVelho,
TextoNovo: String;
Pt: TPoint;
procedure GetInfo();
...
Já na sessão global do formulário (onde encontra-se a definição de sua própria variável)
declararemos as seguintes variáveis:
...
var
Frm_Principal: TFrm_Principal;
Acc: IAccessible;
VarParent: OleVariant;
Res: HResult;
implementation
...
Dadas as preparações, passaremos para as primeiras implementações. A Listagem 3 exibe quatro
métodos extras que serão utilizados por nossa aplicação (GetAccRole, GetName, GetValue e GetRole).
Observe que estes métodos são globais.
Listagem 3. Funções extras do formulário
01
02
03
04
05
06
07
function GetAccRole(Role: LongInt): string;
var
R: Array[0..255] of Char;
begin
GetRoleText(Role, @R, 255);
Result := R;
end;
08 function GetName(Acc: IAccessible; varID: OleVariant) : string;
09 begin
10
Result := Getproperty(DISPID_ACC_NAME, Acc, varID);
11 end;
12 function GetValue(Acc: IAccessible; varID: OleVariant) : string;
13 begin
14
Result := Getproperty(DISPID_ACC_VALUE, Acc, varID);
15 end;
16 function GetRole(Acc: IAccessible; varID: OleVariant) : string;
17 begin
18
Result := Getproperty(DISPID_ACC_ROLE, Acc, varID);
19 end;
No código da Listagem 3, entre as linhas 01 e 07 temos a função GetAccRole. Essa função vai ser
a responsável em passar o parâmetro do objeto role, requisitando o texto e a descrição do mesmo. Entre
as linhas 08 a 19 os métodos fazem requisição à função Getproperty, que será mostrada mais adiante,
note também que as três possuem os mesmos cabeçalhos e chamadas, alterando apenas suas respectivas
constantes, onde no primeiro caso é utilizada a DISPID_ACC_NAME (linha 10), no segundo caso é
utilizada DISPID_ACC_VALUE (linha 14) e por fim, DISPID_ACC_ROLE (linha 18).
Conforme mencionado, a Listagem 4 explana o método Getproperty, bastante utilizado nas
funções declaradas na listagem anterior. Veremos a seguir sua implementação.
Listagem 4. Continuação das funções extras (Getproperty)
01 function Getproperty(Prop: Integer; Acc: IAccessible; varID:
OleVariant):string;
02 var
03
Str: WideString;
04
V: OleVariant;
05 begin
06
case Prop of
07
DISPID_ACC_NAME :Res := Acc.Get_AccName(VarId, Str);
08
DISPID_ACC_VALUE :Res := Acc.Get_AccValue(VarId, Str);
09
DISPID_ACC_ROLE :Res := Acc.Get_AccRole(VarId, V);
10
end;
11
case Res of
12
13
14
15
16
17
18
19
20
21
22
23
24
S_OK:
case Prop of
DISPID_ACC_NAME,
DISPID_ACC_VALUE: Result := Str;
DISPID_ACC_ROLE : Result := GetAccRole(V);
end;
S_FALSE
: Result := 'Resultado False';
E_INVALIDARG
: Result := 'Erro de Parametro';
DISP_E_MEMBERNOTFOUND : Result := 'Esse objeto não suporta
Acessibilidade'
else
Result := 'Erro Desconhecido';
end;
end;
Na Listagem 4 encontra-se a função Getproperty. Esse método é o responsável por requisitar os
pedidos à Interface. Quando chamada, é enviado a propriedade do pedido que se deseja. Entre as linhas
03 e 04 temos as variáveis utilizadas no exemplo. Observe entre as linhas 06 a 10 que é realizado um
Case a partir do parâmetro Prop (visto na Listagem 3). Vale ressaltar que aqui são chamados os próprios
métodos da interface IAccessible, os retornos são armazenados respectivamente na variável Res. Após
realizada a requisição, é realizada uma análise através de outro Case (partido do resultado obtido). Caso
seja S_Ok (linha 12), ou seja, se ocorreu tudo bem, é devolvido o resultado desta requisição. Caso
contrário, o resultado será reanalisado e específico (linhas 18 a 22).
Após encerrar estas codificações, abordaremos a principal implementação da aplicação, que é
justamente o método GetInfo, declarado no escopo private do formulário. Veja sua implementação
conforme a Listagem 5.
Listagem 5. Função Get_Info declarada na sessão private do formulário
01 procedure TFrm_Principal.GetInfo();
02 var
03
R, Z: array[0..255] of char;
04
Win: hwnd;
05
Info,
06
Nome,
07
Valor,
08
Role,
09
vWinTexto,
10
vClassNome: String;
11 begin
12
Info := Info + 'Nome : '+ GetName(Acc, VarParent) +#13#10;
13
Info := Info + 'Valor : '+ GetValue(Acc, VarParent) +#13#10;
14
Info := Info + 'Role : '+ GetRole(Acc, VarParent) +#13#10;
15
WindowFromAccessibleObject(Acc, Win);
16
GetClassName(Win, Z, 255);
17
Info := Info + 'Nome da Classe : '+ Z +#13#10;
18
GetWindowText(Win, R, 255);
19
Info := Info + 'TextoWindows : '+ R +#13#10;
20
Info := Info + '-------------------' +#13#10;;
21
MemoInfo.text := Info;
22
TextoNovo := '';
23
Nome := GetName(Acc, Varparent);
24
Valor := GetValue(Acc, Varparent);
25
Role := GetRole(Acc, Varparent);
26
vWinTexto := R;
27
vClassNome := Z;
28
if ((vClassNome <> '') and (vClassNome <> 'Resultado False') and
(vClassNome = 'TrayClockWClass')) then
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
TextoNovo := 'Relógio '+ vWinTexto +' '+
FormatDateTime('dddd", "dd "de" mmmm "de" yyyy', Now)
else
if ((Nome <> '') and (Nome <> 'Resultado False') and (Nome = 'Modo de
Exibição de Itens')) then
TextoNovo := 'Visualizando Pasta'
else
if ((Nome <> '') and (Nome <> 'Resultado False') and
(Nome <> 'Nome')) then
TextoNovo := Nome
else
if ((Valor <> '') and (Valor <> 'Resultado False') and
(Valor <> '0')) then
TextoNovo := Valor
else
if ((vWinTexto <> '') and (vWinTexto <> 'Resultado False')) then
begin
if (vWinTexto = 'FolderView') then
TextoNovo := 'Visualizando Pasta'
else
TextoNovo := vWinTexto;
end
else
if ((Role <> '') and (Role <> 'Resultado False') and (Role <>
'cliente')) then
TextoNovo := Role;
if ((TextoNovo <> '') and (TextoNovo <> TextoVelho)) then
begin
TextoVelho := TextoNovo;
Mmo_TxtLeitor.Lines.Add(TextoNovo);
end;
end;
Explicando o código da Listagem 5 entre as linhas 03 a 10 temos a declaração das variáveis
responsáveis por obter as informações a serem mostradas. Entre as linhas 12 a 14 passamos para a
variável Info os valores nome, valor e role respectivamente, através da chamada aos métodos GetName,
GetValue e GetRole, que por sua vez, chamarão a função Getproperty, que irá requisitar estas
informações à interface IAccessible. Na linha 15 estamos tentando o objeto utilizando o recurso do
Windows através do método WindowFromAccessibleObject, note que é passada a Interface (definida no
escopo global do formulário) e a variável Win que nada mais é do que o Handle da janela que se deseja
obter as informações. A seguir, na linha 16 é realizada a chamada de GetClassName, atribuindo o nome
da classe novamente à variável Infoa. Novamente na linha 18 utilizando a API do Windows através de
GetWindowText, e assim respectivamente, vamos montando o log das informações, atribuindo o
resultado à variável Info (linhas 18 a 21).
Agora para obter o texto principal, entre as linhas 23 a 27 temos os valores para verificar, qual
será o texto principal a ser mostrado. Essa parte de mostrar (obter) o texto principal, está entre as linhas
28 a 54. Aqui o principal sempre será a propriedade Name, porque como já explicado anteriormente, em
Get_AccName todo objeto que tem rótulo é usado por IAccessible. Então a prioridade para o texto
principal segue da seguinte maneira:
•
•
•
•
Nome: Para itens de lista como pastas e arquivos em geral, Captions entre outros rótulos;
Valor: Para objetos do tipo de barras, para títulos de janelas e alguns outros;
Texto Windows: Para textos em geral referente aos objetos do Windows;
Role: Para a descrição dos objetos roles;
Então primeiramente é feita uma verificação através da classe, verificando se o objeto é o relógio
do Microsoft Windows (linhas 28 e 29). Após essa verificação, a validação ocorre na ordem descrita nos
tópicos. Sendo assim, verific-se se o resultado não é falso, ou algum outro, ou seja, se IAccessible
conseguiu nos trazer a propriedade. Se sim, esse é o texto principal, se não será validado o próximo. Em
seguida, é realizada uma última verificação onde caso o texto seja diferente do anterior, ou seja, se
houve uma movimentação do mouse, se é um novo objeto. Depois de todas as verificações é adicionado
esse texto principal para o Mmo_TxtLeitor. Assim, conclui-se o código da Listagem 5.
Para finalizarmos o artigo, a última implementação a ser realizada é o evento OnTimer, descrito
na Listagem 6.
Listagem 6. Implentação do evento OnTimer
01
02
03
04
05
06
07
08
09
10
11
12
13
14
procedure TFrm_Principal.Tmr_InfoTimer(Sender: TObject);
var
Tpt: TPoint;
Res: HResult;
begin
GetCursorPos(Tpt);
if (Tpt.X = Pt.x) and (Tpt.y = Pt.y) then
Exit;
Pt := Tpt;
Res := AccessibleObjectFrompoint(Pt, @Acc, VarParent);
if Res <> S_OK then
Exit;
GetInfo();
end;
Na Listagem 6 como se pode ver, temos o evento OnTimer. O objetivo é utilizarmos o Timer
para validar se houve movimentação do mouse. Nas linhas 02 a 04 há a definição das variáveis. Note
que na linha 06 é utilizado o método GetCursorPos passando a variável declarada como parâmetro. Este
método retorna um ponteiro para a posição do cursor do mouse. A seguir, é verificado na linha 07 se as
posições atuais obtidas são as mesmas, ou seja, não houve mudança no cursor do mouse, se de fato a
afirmação for verdadeira, automaticamente é chamado o método Exit na linha 08. Caso contrário,
chamamos a função AccessibleObjectFromPoint, já explicada no início do artigo e analisamos o seu
resultado. Se o resultado obtido for diferente de S_Ok, então é chamado o Exit que novamente sai da
rotina (linhas 11 e 12). Por último é executada a função GetInfo para obter as informações.
Com isso a aplicação está finalizada. Nas Figuras 2 e 3 podemos executar a aplicação e ver seus
respectivos resultados quando o mouse se encontra sobre a barra de título da própria aplicação e sobre a
lixeira por exemplo
Figura 2. Mouse parado em cima da barra de título da própria aplicação
Figura 3. Mouse parado em cima da lixeira
Conclusão
Temos neste artigo uma boa visão de como a utilização de APIs do Windows podem desenvolver
Softwares, mais acessíveis em conjunto com o Delphi. Hoje em dia, estes recursos são diferenciais para
a sua aplicação e, consequentemente, aumentando o público alvo de seu projeto.
Uma das grandes vantagens da acessibilidade é o fato de termos a possibilidade de fazer e tornar
os nossos aplicativos cada vez mais atrativos para os usuários, não só se limitando a ler as informações,
ou usando reconhecimento de voz. Como vimos há várias situações em que podemos fazer o seu uso.
Como grandes empresas estão visando uma maior importância para estas questões da acessibilidade,
pode-se dizer que em um futuro próximo, Softwares deste tipo e com estes recursos, terão uma maior
procura e usabilidade.
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de
São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.
Uso de Voz em Delphi
Neste artigo veremos um pouco da tecnologia do SDK SAPI, que
permite fazer uso de voz no Delphi. Este recurso é muito
interessante para ler dados diversos nas mais variadas formas de
utilidade.
Uso de Voz em Delphi
Sistemas de empresas telefônicas e muitas outras hoje em dia, usam recursos de voz devido a
simplicidade gerada para entrada de dados. O mesmo poderia ser aplicado nos mais diversos sistemas,
por exemplo, em consulta de clientes, agendamentos entre outras situações. É mais fácil e muito mais
ágil informar os dados através da voz, no lugar do uso do mouse ou teclado. Obviamente em algumas
outras situações, é mais fácil ouvir as informações no lugar de ler. É justamente o que abordaremos
neste artigo, fazendo com que o nosso exemplo leia dados dos clientes previamente cadastrados por
meio de comandos de voz.
Em que situação o tema é útil
O tema é útil em casos onde há a necessidade de criação de consultas para agilizar processos, além
disso, também pode ser utilizado como forma de auxiliar usuários que sofram de algum tipo de
deficiência ou dificuldade em manusear o mouse ou teclado.
Em 11 de agosto de 2001, a Microsoft libera o SAPI 5.1 SDK na qual permite seu uso em qualquer
linguagem que suporte automação OLE (BOX 1). SAPI é o acrônimo de Speech Application
Programming Interface. Consiste em uma API desenvolvida pela Microsoft na qual possibilita o
reconhecimento de voz em aplicações Windows. Um exemplo conhecido de software que faz uso desta
API é o Microsoft Office.
BOX 1. OLE Automation
OLE Automation ou apenas Automation é um mecanismo comumente utilizado para realizar a
comunicação entre aplicações baseadas em COM (Component Object Model). A OLE permite o uso e
manipulação de aplicações, ou seja, uma aplicação controlando outra.
Os componentes do SAPI SDK podem ser utilizados para elaborar sistemas com reconhecimento de voz
e leitura de dados. Essa tecnologia que é muito robusta pode ser usada não só em Delphi, como em
diversas outras linguagens de programação.
Usando a tecnologia do SDK SAPI 5.1
Para seu uso em Delphi, é necessário instalar o SDK SAPI 5.1 e configurá-lo, sendo apenas um detalhe
mínimo, bastando importar sua Type Library e usar conforme a necessidade.
Em aplicações que irão usar leitura de textos, não é necessário ter nenhum reconhecimento de voz
(SDK) instalado na máquina do cliente, sendo assim, limita-se apenas ao uso das “vozes” que estão
instaladas no próprio Windows.
Além disso, podem ser instaladas vozes adicionais para o uso da aplicação, estas são identificadas por
nomes de pessoas como as de “Raquel” (Português) e “Alonso” (Espanhol). No caso do idioma em
português, existem outras opções, no entanto, a voz mais completa e de entonação melhor seria a voz
denominada como “Raquel”. Na seção Links foi disponibilizado seu download.
Para usar os recursos de reconhecimento de voz, deve-se obrigatoriamente atender alguns pré-requisitos.
Sendo assim, devem ser instalados no Windows, dentro de Painel de Controle, o reconhecimento de voz,
assim como um microfone e a parte de som, que deverá estar funcionando corretamente.
Leitura de textos
Para leitura de textos, não é necessário ter nenhum reconhecimento de voz instalado. O computador irá
apenas reproduzir o texto, usando os fonemas do idioma da voz selecionada para a “leitura” do texto.
Reconhecimento de comandos simples
Para um uso de reconhecimento de comandos simples, pra controlar botões e controles diversos dos
formulários em geral, basta criar um objeto de reconhecimento (SpSharedRecoContext) e uma gramática
para o mesmo. Esta gramática é o conjunto de palavras (comandos), que serão reconhecidos pela
aplicação e que podem ser carregados por meio de um arquivo, banco de dados, ou um XML que é o
mais comum. Desta forma os usuários podem falar o que for, que será descartado pelo objeto de
reconhecimento. Quando uma palavra for falada e estiver na gramática do reconhecimento (XML, por
exemplo) o aplicativo irá executar a ação correspondente do seu código.
Reconhecimento exato
Para um reconhecimento exato, é interessante que os comandos sejam palavras curtas e de pronúncias
bem distintas uma das outras, assim não haverá equívocos. Em nosso exemplo adotaremos o menu de
uma aplicação qualquer, com os itens “novo”, “abrir” e “sair”.
Na gramática do reconhecimento é interessante que os itens estejam exatamente descritos como “novo”,
“abrir” e “fechar”, para não causar nenhuma falha entre “abrir” e “sair”, por exemplo, devido a
semelhança, voz, entonação, velocidade da fala e etc.
Gramática de reconhecimento em XML
Para executar os itens de um menu, como o mencionado anteriormente, nosso objeto de reconhecimento
(SpSharedRecoContext) seria carregado com uma estrutura semelhante ao arquivo XML definido na
Listagem 1.
Listagem 1. Arquivo XML com a definição de reconhecimento para os valores novo, abrir e fechar
01 <?xml version="1.0"?>
02 -<GRAMMAR LANGID="809">
03
<!-- "Constant" definitions -->
04
-<DEFINE>
05
<ID VAL="1" NAME="RID_start"/>
06
<ID VAL="2" NAME="PID_cmdesolhido"/>
07
<ID VAL="3" NAME="PID_cmdvalor"/>
08
</DEFINE>
09
<!-- Rule definitions -->
10 -<RULE NAME="start" TOPLEVEL="ACTIVE" ID="RID_start">
11
<O>cmd</O>
12
<RULEREF NAME="cmd" PROPID="PID_cmdesolhido" PROPNAME="cmdesolhido"/>
13
</RULE>
14
-<RULE NAME="cmd">
15
-<L PROPID="PID_cmdvalor" PROPNAME="cmdvalor">
16
<P VAL="1">novo</P>
17
<P VAL="2">abrir</P>
18
<P VAL="3">fechar</P>
19
</L>
20
</RULE>
21 </GRAMMAR>
Uma prática que poderia ser adotada seria ao chamar uma tela de cadastro ou movimentações, por
exemplo, criar dinamicamente esse XML com os comandos da tela a partir de um banco de dados, onde
os atributos (VAL="1") seriam os códigos da tabela e os valores (“NOVO”) seriam os campos do
comandos de voz. Observe que a descrição dos itens encontram-se descritos códigos apresentados entre
as linhas 16 a 18 da Listagem 1, indicados pelas tags VAL.
Conhecendo o objeto (SpVoice)
Veremos a seguir as principais propriedades do objeto SpVoice, onde algumas situações será necessário
fazer uma ou mais mudanças para melhor se adequar a nossa necessidade:
· Priority - A propriedade “Priority” obtém e define o nível de prioridade da voz. O nível de prioridade
define a ordem em que o mecanismo de texto processa os pedidos de fala de um objeto de voz
(SpVoice). Níveis de prioridade mais elevados são atribuídos às vozes de tratamento de erros, já
menores níveis de prioridade são atribuídos às vozes normais. Por causa de seu nível de prioridade, os
pedidos de vozes de tratamento de erros (geralmente usados para falar as mensagens de erros), são
falados antes de outras solicitações normais de voz. As vozes de tratamento de erros podem aparecer
para interromper as vozes normais. Um objeto SpVoice, por padrão, é criado com a prioridade normal.
Para utilizar uma ou mais vozes de alerta, basta criá-la e definir sua propriedade “Priority”
adequadamente. Uma voz com uma definição de prioridades do tipo SVPAlert é tratada como uma voz
de alerta. Vozes de alerta são projetadas para serem o principal meio para tratamento de erros.
· Rate - É responsável por obter e definir a velocidade da fala da voz. É definida por intervalos de -10 a
10, que representam do mais lento ao mais rápido respectivamente. No início de cada método de fala, a
voz define a sua velocidade de leitura de acordo com o valor das suas propriedades Rate e pronuncia
todo o fluxo com essa taxa. Essa propriedade pode ser alterada a qualquer momento, porém, a
velocidade da fala real não irá refletir o valor da propriedade atual até que se inicie um novo fluxo.
· Voice - Obtém e define a voz ativa da coleção de vozes. Esta propriedade pode ser pensada como a
pessoa que irá pronunciar os textos, alguns exemplos de vozes são denominados de "Microsoft Mary" e
"Microsoft Mike". Para saber as vozes que estão disponíveis, basta usar o método “GetVoices”. Se já
não houver uma voz em uso, essa propriedade apontará para a padrão definida no Microsoft Windows.
· Volume - A propriedade “Volume” obtém e define o volume base do nível da voz. Valores entre o
intervalo de 0 a 100 representam os níveis mínimo e máximo de volume respectivamente. No início de
cada método de fala, a voz define o volume de acordo com o valor desta propriedade e pronuncia todo o
texto corrente nesse nível. Esta propriedade pode ser alterada a qualquer momento, porém, o nível de
volume atual não reflete o valor alterado até que se inicie um novo fluxo.
Eventos
Veremos a seguir os principais eventos que contém um objeto SpVoice. A fim de compreender os
eventos de voz, é necessário fazer a distinção entre o mecanismo de TTS que sintetiza a fala do texto e o
objeto SpVoice que se comunica com o mecanismo de TTS.
O mecanismo TTS funciona como se fosse um servidor e o objeto SpVoice como um cliente. O objeto
de voz envia ao TTS um pedido para falar uma sequência de texto, por sua vez, o TTS processa o
pedido. O intervalo de tempo entre um pedido e a produção do discurso é imprevisível. Os eventos do
SpVoice podem superar esta dificuldade, fornecendo meios de obter respostas em tempo real do TTS, o
que torna possível sincronizar funções do aplicativo com o discurso.
O objeto de voz envia os pedidos com os métodos Speak e SpeakStream. Esses enviam cadeias de texto
e arquivos de áudio para o TTS. Estes métodos podem ser chamados de forma síncrona ou assíncrona.
Com um pedido de fala chamado de forma síncrona, a execução do aplicativo é suspensa enquanto o
texto é falado e os eventos da fala do texto são recebidos depois que o fluxo foi falado. Esses são os
eventos disponíveis:
· AudioLevel - Ocorre quando o mecanismo de texto para fala (TTS) detecta uma mudança de nível de
áudio enquanto estiver enviando um fluxo para o objeto SpVoice.
· Bookmark - O evento Bookmark ocorre quando o TTS detecta um marcador enquanto estiver enviando
um fluxo para o objeto SpVoice. Deve-se observar que o evento marcador pode não estar sincronizado
com a palavra do texto falado, ou seja, pode não estar marcando a palavra correta do texto. Em algumas
circunstâncias, o TTS pode marcar mais cedo a palavra do fluxo do texto.
· Sentence – É disparado toda a vez que o TTS detecta uma sentença na frase ao enviar um fluxo para o
objeto SpVoice.
· StartStream e EndStream - StartStream ocorre quando o mecanismo de texto para fala começa a enviar
um fluxo para o objeto SpVoice. Já o evento EndStream ocorre quando se dá o fim do fluxo. Esses
eventos podem ser usados em conjunto para determinar a duração de um fluxo de texto a ser
pronunciado.
· Phoneme - O evento Phoneme ocorre quando é detectado um fonema ao enviar um fluxo para o objeto
SpVoice.
· Viseme - É um gatilho para cada reconhecimento. Ele requer imagens para representar os fonemas
identificados no discurso. Um fonema é a menor unidade de uma linguagem que pode transmitir um
significado, como o som “M” em Maria. Cada fonema tem sua respectiva forma facial. Animadores
normalmente associam fonemas com as formas que a boca toma ao criar vários sons. Estas formas da
boca por sua vez, são conhecidas tecnicamente como visemes. Animadores da Disney por exemplo, se
basearam em um gráfico de 13 posições arquetípicas da boca para representar a fala do idioma inglês.
Com uma certa aptidão artística fica fácil desenhar os visemes necessários para a representação de
qualquer idioma, usando imagens de rosto de uma pessoa, ou de um robô, como é visto hoje em dia em
vários filmes.
· VoiceChange - O evento VoiceChange ocorre quando o detecta uma mudança de voz ao enviar um
fluxo para o objeto SpVoice.
· Word - O evento Word ocorre quando o mecanismo detecta uma nova palavra ao enviar um fluxo para
o objeto SpVoice.
Métodos do objeto SpVoice
A seguir conheceremos alguns dos principais métodos do SpVoice. São eles:
· GetVoices - É responsável por retornar as vozes disponíveis no Microsoft Windows para o objeto
SpVoice. Os parâmetros de busca podem ser aplicados opcionalmente. Na ausência de parâmetros de
busca, todas as vozes são devolvidas para o objeto em ordem alfabética pelo nome da voz. Se não
houver vozes que correspondem aos parâmetros de busca, o retorno do método é vazio.
· Speak - O método Speak é para a fala do texto. Pode ser chamado de forma síncrona ou assíncrona.
Quando chamado de forma síncrona, o método não retornará até que o texto seja falado, quando
chamado de forma assíncrona, ele retorna imediatamente e a voz é pronunciada como um processo de
fundo. Quando o método síncrono é utilizado em uma aplicação, a execução do aplicativo é bloqueada
enquanto a voz fala, não permitindo o controle do usuário. Isso pode ser aceitável para aplicações
simples, ou aquelas sem interface gráfica do usuário (GUI), mas, quando a interação do usuário
enquanto o texto é falado se faz necessária, o modo assíncrono é mais apropriado. O método Speak
insere um fluxo na fila do TTS e retorna um número atribuído pelo mecanismo. Isso distingue esse fluxo
de outros que também estão na fila. Este número é um identificador temporário que funciona como um
índice para a fila do TTS. Um objeto de voz (SPVoice) pode enfileirar vários fluxos, e cada um desses
fluxos podem gerar eventos variados, sendo os eventos associados com o número do fluxo.
· Pause - É responsável por pausar a voz do objeto em uso, fechando o dispositivo de saída, permitindo
que seja utilizado por outros objetos de voz.
· Resume - Faz com que a voz do objeto que foi pausada continue falando.
· Skip - Ignora a voz pelo número especificado de itens dentro do fluxo atual do texto. Funciona como se
fosse um botão stop de um player, onde o número de itens informado no parâmetro é o que será
ignorado pelo objeto. Se desejar parar totalmente a fala, basta informar o parâmetro “MaxInt” para que o
fluxo que estiver sendo falado pelo objeto seja interrompido.
Instalando o componente
Para a instalação do ActiveX no Delphi, basta acessar o menu Component>Install Component (Figura
1). Na tela que se abre (Figura 2), selecione a opção “Import a Type Library”. Após o processo,
selecione o componente dentre os exibidos na lista, conforme a Figura 3. Após a seleção, será
adicionada a unit SpeechLib_TLB.pas (Figuras 4 e 5) com os novos componentes do SAPI 5.1.
Utilizando a paleta de componentes padrão, sua instalação estará na aba ActiveX.
abrir imagem em nova janela
Figura 1. Importando componente ActiveX
Figura 2. Opção para importar Type Library
Figura 3. Seleção das bibliotecas disponíveis.
Figura 4. Setando a aba de instalação do componente
Figura 5. Última opção do Wizard de instalação
Criando a aplicação
Após a instalação do componente, é criada uma aplicação do tipo VCL Application. Para isso,
selecionamos o menu New>VCL Application – Delphi.
Renomeamos o formulário principal para Frm_Principal e adicionamos alguns componentes nomeandoos conforme a seguir: três Labels (Lbl_Voz, Lbl_Pos e Lbl_Veloc), um TrackBar (tbRate), um Button
(Btn_Ouvir), um ComboBox, (Cbx_Voz), um DBGrid, (Dbg_Cadastro), um RichEdit (Rch_Obs), um
DBNavigator (Nvg_Cadastro), um ClientDataSet (Cds_Cadastro), um DataSource, (Ds_Cadastro) e por
último um SpVoice, (SpVoice). O layout do formulário é ajustado conforme a Figura 6. Para melhor
identificação na montagem da tela os Labels (Lbl_Voz, Lbl_Veloc, Lbl_Pos) possuem os respectivos
Captions “Selecione a Voz”, “Velocidade da Voz”, “00”.
Nota: Foram adicionadas as units OleServer e SpeechLib_TLB ao uses do formulário.
Figura 6. Tela Principal de cadastro
Para criar a comunicação com os dados e fazer a codificação necessária, adicionamos três fields no
ClientDataSet dando um duplo clique sobre o mesmo e selecionando a opção New Field. Os campos a
serem incluídos encontram-se na Tabela 1.
Tabela 1. Definição dos campos do ClientDataSet
Inseridos os campos, basta ativar o ClientDataSet usando o método CreateDataSet, fazendo com que
este esteja preparado para a inserção de dados. Esta opção pode ser utilizada em Design Time, clicando
de direita sobre o componente e selecionando-a no menu de contexto, ou em Runtime, por forma de
comandos, utilizando o método CreateDataSet.
Ligamos o componente Ds_Cadastro ao ClientDataSet por meio de sua propriedade DataSet. Em
seguida, informamos aos componentes Dbg_Cadastro e Nvg_Cadastro o DataSource configurado,
apontando para a propriedade de mesmo nome. Assim a parte de visualização de dados já está
configurada e podemos partir para a codificação.
No evento OnCreate do formulário vamos iniciar os componentes de voz e de dados, como mostra a
Listagem 2.
Listagem 2. Evento OnCreate do formulário
01 procedure TFrm_Principal.FormCreate(Sender: TObject);
02 var
03
I: Integer;
04
SOTokens: ISpeechObjectTokens;
05
SOToken: ISpeechObjectToken;
06 begin
07
SpVoice.EventInterests := SVEAllEvents;
08
SOTokens := SpVoice.GetVoices('', '');
09
10
11
12
13
14
for I := 0 to SOTokens.Count - 1 do
begin
SOToken := SOTokens.Item(I);
Cbx_Voz.Items.AddObject(SOToken.GetDescription(0), TObject(SOToken));
SOToken._AddRef;
end;
15
16
17
18
19
if Cbx_Voz.Items.Count > 0 then
begin
Cbx_Voz.ItemIndex := 0;
Cbx_Voz.OnChange(Cbx_Voz);
end;
20
21
TbRate.Position
Lbl_Pos.Caption
:= SpVoice.Rate;
:= IntToStr(TbRate.Position);
22
with Cds_Cadastro do
23
begin
24
Insert;
25
FieldByName('NOME').AsString
:= 'Angelina Jolie';
26
FieldByName('ENDERECO').AsString := 'Rua Gomes da Silva, Nº520';
27
FieldByName('TELEFONE').AsString := '(99)7218-3542';
28
FieldByName('DTCADASTRO').AsDateTime := Now;
29
Post;
30
end;
31 end;
Entre as linhas 03 a 05 são definidas as variáveis utilizadas no evento OnCreate. Vale salientar que
SOTokens será responsável por obter uma lista de vozes instaladas, já o SOToken (no singular), tratará
apenas uma única voz. Na linha 07 informamos ao componente que queremos visualizar todos os
eventos disponíveis no componente. A linha 08 utiliza o método GetVoices para obter toda a lista de
vozes instalada no computador. A seguir, na linha 09, é utilizado um laço para percorrer os itens
encontrados. Observe então que a variável SOToken recebe a voz atual do laço (linha 11), adicionandoas ao ComboBox (linha 12). O próximo passo é verificar se há itens no ComboBox (linha 15), fazendo
com que, caso seja verdadeiro, o primeiro item da lista seja selecionado (linha 17), acionando o evento
OnChange do próprio componente (linha 18).
Entre as linhas 20 e 21 os componentes visuais são atualizados com os valores baseados na posição da
barra e na propriedade Rate do SpVoice. Por último, entre as linhas 22 a 29, é simulada a inserção de um
registro no ClientDataSet.
Após terminar o evento OnCreate, implementaremos o OnDestroy do formulário. A Listagem 3 ilustra a
codificação do evento.
Listagem 3. Evento OnDestroy do formulário
01
02
03
04
05
06
07
procedure TFrm_Principal.FormDestroy(Sender: TObject);
var
I: Integer;
begin
for I := 0 to Cbx_Voz.Items.Count - 1 do
ISpeechObjectToken(Pointer(Cbx_Voz.Items.Objects[I]))._Release;
end;
O evento OnDestroy simplesmente percorre os itens do ComboBox (linha 05) e os libera da memória
(linha 06). Continuando, implementamos o evento OnChange do ComboBox conforme ilustrado na
Listagem 4.
Listagem 4. Evento OnChange do ComboBox
01 procedure TFrm_Principal.Cbx_VozChange(Sender: TObject);
02 var
03
SOToken: ISpeechObjectToken;
04 begin
05
SOToken := ISpeechObjectToken(Pointer(
Cbx_Voz.Items.Objects[Cbx_Voz.ItemIndex]));
06
SpVoice.Voice := SOToken;
07 end;
Podemos notar que simplesmente é obtido o objeto selecionado (a partir do ItemIndex do ComboBox –
linha 05) e atribuído à variável SOToken. A seguir, esta variável é informada ao componente SpVoice
(linha 06).
Na Listagem 5 é implementado o evento OnChange do componente TbRate.
Listagem 5. Evento OnChange da barra de velocidade (TbRate)
01 procedure TFrm_Principal.TbRateChange(Sender: TObject);
02 begin
03
SpVoice.Skip('Sentence', MaxInt);
04
SpVoice.Rate
:= TbRate.Position;
05
Lbl_Pos.Caption := IntToStr(TbRate.Position);
06 end;
Neste evento fazemos com que caso a barra seja modificada, automaticamente a voz seja parada (linha
03), atribuindo ao Rate (velocidade), o valor selecionado no componente e atualizando o valor no Label
(linhas 04 e 05 respectivamente).
Agora para finalizar, deverá ser implementado o evento OnClick do Button. O código encontra-se na
Listagem 6.
Listagem 6. Evento OnClick do botão Ouvir
01 procedure TFrm_Principal.Btn_OuvirClick(Sender: TObject);
02 begin
03
SpVoice.Skip('Sentence', MaxInt);
04
if (Cds_Cadastro.IsEmpty)then
05
begin
06
SpVoice.Speak('Não há dados cadastrado para leitura', SVSFlagsAsync);
07
Exit;
08
end;
09
if (Cds_CadastroNOME.AsString <> '') then
10
begin
11
SpVoice.Speak('Nome', SVSFlagsAsync);
12
SpVoice.Speak(Cds_CadastroNOME.AsString, SVSFlagsAsync);
13
end;
14
if (Cds_CadastroENDERECO.AsString <> '') then
15
begin
16
SpVoice.Speak('Endereço', SVSFlagsAsync);
17
SpVoice.Speak(Cds_CadastroENDERECO.AsString, SVSFlagsAsync);
18
end;
19
if (Cds_CadastroTELEFONE.AsString <> '') then
20
begin
21
SpVoice.Speak('Telefone', SVSFlagsAsync);
22
SpVoice.Speak(Cds_CadastroTELEFONE.AsString, SVSFlagsAsync);
23
end;
24
if (Cds_CadastroDTCADASTRO.AsString <> '') then
25
begin
26
SpVoice.Speak('Cadastrado', SVSFlagsAsync);
27
SpVoice.Speak(Cds_CadastroDTCADASTRO.AsString, SVSFlagsAsync);
28
end;
29
SpVoice.Speak(Rch_Obs.Text, SVSFlagsAsync);
30 end;
Assim como no anterior, interrompemos a voz caso esteja sendo falada alguma coisa (linha 03). Observe
que caso não existam dados ao clicar no botão, o próprio erro será pronunciado pelo componente (linha
06). Entre as linhas a 09 a 29 os campos são validados um a um, tendo pronunciados seus Captions e
seus respectivos conteúdos, juntamente com a observação do RichText ao final do código.
Conclusão
Neste artigo, conhecemos um pouco da tecnologia do SDK SAPI, que permite fazer uso de voz no
Delphi. Este recurso é muito interessante para ler dados diversos nas mais variadas formas de utilidade.
E claro, podemos ir mais além do que foi mostrado, fazendo uso do reconhecimento de voz em nas
nossas telas de consultas, informando os dados com a voz por exemplo. Além disso, seria possível
controlar menus, botões entre vários outros componentes simplesmente usando comandos de voz. Vale
apena ler a documentação e explorar os recursos para integração com as aplicações.
Até a próxima.
Links
Download do Speech Sdk 5.1
http://www.microsoft.com/en-us/download/details.aspx?id=10121
Download do Sapi Junto com a Voz Brasileira da Raquel
http://www.4shared.com/get/w3aZNhB5/realspeak_-_raquel_-_sapi5_-_p.html
Download de um Exemplo Usando o Reconhecimento de Voz
http://www.4shared.com/rar/rr_h4fV3/ReconVoz.html
Leia mais em: Uso de Voz em Delphi http://www.devmedia.com.br/uso-de-voz-emdelphi/28955#ixzz641ZTR4SW
Vanderson Cavalcante Freitas
Analista Desenvolvedor Delphi há mais de 5 anos, com experiência em médias e grandes empresas de
São Paulo. Formado em técnico em informática no ano de 2003, com diversos cursos em formação
específica, como Oracle, Delphi e C#.

Documentos relacionados

POO – Noções básicas de DELPHI - Prof. Sérgio Mayerle

POO – Noções básicas de DELPHI - Prof. Sérgio Mayerle CONVENÇÃO DE NOMEAÇÃO ......................................................................................................................... 27 MANIPULANDO COMPONENTES .............................

Leia mais

Elementos Graficos

Elementos Graficos No Capítulo 6, vimos que é possível pintar diretamente na superfície de um formulário, em resposta a um evento de mouse. Para ver esse comportamento, basta criar um novo formulário com o seguinte m...

Leia mais

desenvolvendo aplicações para bancos de dados desktop

desenvolvendo aplicações para bancos de dados desktop com elas que o Windows consegui cumprir sua promessa de ser um sistema amigável e fácil de usar também para os programadores, que sempre tiveram que pagar a conta da facilidade de uso para o usuári...

Leia mais

Delphi 5 - Guia do Desenvolvedor_capitulos do CD

Delphi 5 - Guia do Desenvolvedor_capitulos do CD O uso de variáveis globais é desencorajado. No entanto, elas podem ser usadas quando for necessário. Quando isso acontecer, você deverá manter as variáveis globais dentro do contexto em que são usa...

Leia mais