Desenvolvento APIs Do Windows em Delphi
Desenvolvento APIs Do Windows em Delphi
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#.
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;
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.
· 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:
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;
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:
· 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):
Função GetDoubleClickTime
() 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.
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.
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.
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:
· 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, deve-
se 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_
*;
· 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;
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.
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.
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.
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 bem-
sucedido (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_ID: lpszName é uma string com o nome do identificador predefinido para tocar;
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_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.
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.
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.
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.
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.
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:
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.
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.
01 ShellExecute (
02 ByVal hwnd As Long,
03 ByVal lpOperation As String,
04 ByVal lpFile As String,
05 ByVal lpParameters As String,
06 ByVal lpDirectory As String,
07 ByVal nShowCmd As Long
08 ) 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_ERR_DDEBUSY: A ação DDE não pôde ocorrer porque outras ações DDE estão em processo;
· 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;
· nshowCmd: Este argumento recebe um dos seguintes valores, especificando como exibir qualquer
janela que se abre na função:
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;
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.
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.
01 WinHelp (
02 ByVal hwndMain As Long,
03 ByVal lpHelpFile As String,
04 ByVal uCommand As Long,
05 dwData As Any
06 ) 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_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_MULTIKEY: Exibir o tópico especificado por uma palavra-chave em uma tabela de palavras-
chaves 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_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.
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.
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.
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#.
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 Funcionalidade
Criar Cadastro 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.
Excluir Cadastro Irá chamar a tela onde terá uma lista dos cadastros criados. Para que o usuário
possa selecionar um cadastro a ser excluído.
Sair Fecha a nossa aplicação.
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.
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.
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.
SET TERM ^ ;
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.
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 Funcionalidade
Frm_Principal Formulário principal da aplicação, onde ficam os menus que chamam
todos os outros formulários (Telas).
Frm_CriaCadastro Pode ser considerado o formulário mais importante, é onde será criado os
cadastros e relatórios.
Frm_ExcCadastro Será o formulário que apresentará todos os cadastrados criados, para que
possam ser excluídos.
Frm_Cadastro 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.
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.
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.
private
MenuCad,
MenuTabPer: TMenuItem;
ImgItMenu: Integer;
var
Frm_Principal: TFrm_Principal;
implementation
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.
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.
Frm_ExcCadastro.ShowModal;
finally
FreeAndNil(Frm_ExcCadastro);
end;
end;
Por fim, no menu Sair fecharemos nossa aplicação, como mostra a Listagem 5.
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 := TMenuItem.Create(MenuCad);
MenuTabPer.Caption := Menu;
MenuTabPer.ImageIndex := ImgItMenu;
MenuTabPer.OnClick := 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.
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;
Isso é feito pela procedure RemoveMenu, presente na Listagem 8, que recebe como parâmetro o nome
do menu a ser removido.
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.
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.
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.
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
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.
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
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
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
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.
É 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.
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.
if (Grd_Cadastro.SelectedIndex = 2) then
begin
Texto := Grd_Cadastro.Columns[2].Field.Text;
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.
if (Dados.IsEmpty) then
begin
Screen.Cursor := crDefault;
MontaScript;
CriaTabela;
InserirTabela;
LimpaGrade;
Screen.Cursor := crDefault;
except
on E:exception do
begin
Screen.Cursor := crDefault;
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.
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.
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.
procedure TFrm_CriaCadastro.Replace_Campos;
begin
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Texto',
'VARCHAR', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Observação',
'BLOB SUB_TYPE 1 SEGMENT SIZE 30', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Data', 'DATE',
[rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Hora', 'TIME',
[rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'DATE e TIME',
'TIMESTAMP', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Moeda', 'DOUBLE
PRECISION', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Numero',
'INTEGER', [rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Sim', 'S',
[rfReplaceAll]);
Mmo_Script.Lines.Text := StringReplace(Mmo_Script.Lines.Text, 'Não', 'N',
[rfReplaceAll]);
end;
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.
procedure TFrm_CriaCadastro.MontaScript;
var
CriaTabela,
AlteraTabela,
NomeTabela,
UltLinha,
Codigo: String;
I, Virgula: Integer;
begin
try
NomeTabela := Edt_Tabela.Text;
Dados.First;
Mmo_Script.Lines.Add(CriaTabela);
Mmo_Script.Lines.Add(Codigo);
Dados.Next;
end;
I := Mmo_Script.Lines.Count - 1;
UltLinha := Mmo_Script.Lines[I];
Virgula := Pos(',', UltLinha);
Mmo_Script.Lines.Add(AlteraTabela);
Replace_Campos;
except
on E:exception do
begin
Screen.Cursor := crDefault;
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.
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;
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);
· 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.
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 := RetornaCodigo('GEN_TAB_CAMPOS');
Params.ParamByName('TABELA').AsString := Tabela;
Params.ParamByName('CAMPO').AsString := 'CODIGO';
Params.ParamByName('COLUNA').AsString := 'Código';
Params.ParamByName('TIPO').AsString := 'STRING';
Params.ParamByName('RELATORIO').AsString := 'S';
Execute;
end;
Dados.First;
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;
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.
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;
Dados.First;
if (Dados.FieldByName('TIPOCAMPO').AsString ='')then
begin
Screen.Cursor := crDefault;
if (Dados.FieldByName('RELATORIO').AsString ='')then
begin
Screen.Cursor := crDefault;
Dados.Next;
end;
end;
O formulário de cadastro possui sete componentes referente a parte de relatórios. São eles:
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.
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.
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.
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.
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.
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;
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
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;
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.
ExcTabela;
end;
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;
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.
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#.
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;
· 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.
· 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:
· 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:
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.
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.
private
function fGetComputerName: String;
function fGetUserName: String;
function fGetSystemDirectory: String;
function fWindowsDirectory: String;
function fGetTempPath: String;
function fGetVersionEx: string;
Listagem 2. Implementação
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;
2: begin
case dwMajorVersion of
3: Result := 'Windows NT ' + IntToStr(dwMajorVersion) + '.' +
IntToStr(dwMinorVersion);
5: begin
case dwMinorVersion of
0: Result := 'Windows 2000';
1: Result := 'Windows XP';
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.
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 BFailIfExists - Se 0, a função irá substituir LpNewFileName caso ele já existe, caso contrário, a
função irá falhar.
· 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 bem-
sucedido, ou -1 se um algum erro ocorreu. Seus parâmetros são:
o LpFileName - O nome do arquivo a ser criado ou aberto;
§ OPEN_ALWAYS - Abre um arquivo existente e, se o arquivo não existir, ele será criado;
§ FILE_ATTRIBUTE_NORMAL - Um arquivo sem atributos (esse não pode ser usado com atributos);
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 bem-
sucedida, 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:
· 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 LpFindFileData - recebe informações de identificação sobre o próximo arquivo correspondente que foi
encontrado.
· 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.
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.
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).
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;
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.
TSearchRec = record
Time: Integer;
Size: Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
FindHandle: THandle;
FindData: TWin32FindData;
end;
o faArchive: Arquivos;
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:
o SW_SHOWNOACTIVATE -Mostra a janela em seu tamanho e a posição mais recente, mas não ativa;
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:
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:
· 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.
· 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_TOPMOST - Coloca a janela no topo, ou seja, acima de todas as outras janelas, de forma
permanente;
o SWP_NOACTIVATE – Não ativa a janela após movê-la, a menos que a mesma já esteja ativa;
o SWP_NOREDRAW - Não remove a imagem da janela em sua antiga posição, efetivamente deixando
uma imagem fantasma da tela;
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.
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.
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;
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 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.
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:
· 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.
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_SIZENESW - O cursor de duas pontas, apontando para o canto superior direito, e inferior
esquerdo.
§ IDC_SIZENWSE - O cursor de duas pontas, apontando para o canto inferior direito, e superior
esquerdo.
· 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.
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:
· SamDesired - Um ou mais dos seguintes valores, especificando o acesso de leitura / gravação desejado:
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 bem-
sucedida, 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:
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.
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.
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.
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.
procedure TFrm_Registro.NaoSetAutorum;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_CURRENT_USER;
Finally
Reg.Free;
Inherited;
end;
end;
procedure TFrm_Registro.SetAutorum;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_CURRENT_USER;
Finally
Reg.Free;
Inherited;
end;
end;
Try
Reg.RootKey := HKEY_CURRENT_USER;
Finally
Reg.Free;
Inherited;
end;
end;
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.
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#.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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).
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
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.
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.
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.
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.
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.
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.
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.
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}.
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.
Usando Windows e
Acessibilidade
Saiba como integrar ao Delphi recursos de
acessibilidade do windows
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
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.
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.
unit Oleacc;
Interface
uses
Windows, Variants, Classes;
const
ACCDLL = 'OLEACC.DLL';
const
DISPID_ACC_PARENT =-5000;
DISPID_ACC_CHILDCOUNT =-5001;
DISPID_ACC_CHILD =-5002;
DISPID_ACC_NAME =-5003;
DISPID_ACC_VALUE =-5004;
DISPID_ACC_DESCRIPTION =-5005;
DISPID_ACC_ROLE =-5006;
DISPID_ACC_STATE =-5007;
DISPID_ACC_HELP =-5008;
DISPID_ACC_HELPTOPIC =-5009;
DISPID_ACC_KEYBOARDSHORTCUT =-5010;
DISPID_ACC_FOCUS =-5011;
DISPID_ACC_SELECTION =-5012;
DISPID_ACC_DEFAULTACTION =-5013;
DISPID_ACC_SELECT =-5014;
DISPID_ACC_LOCATION =-5015;
DISPID_ACC_NAVIGATE =-5016;
DISPID_ACC_HITTEST =-5017;
DISPID_ACC_DODEFAULTACTION =-5018;
const
NAVDIR_MIN =$00000000;
NAVDIR_UP =$00000001;
NAVDIR_DOWN =$00000002;
NAVDIR_LEFT =$00000003;
NAVDIR_RIGHT =$00000004;
NAVDIR_NEXT =$00000005;
NAVDIR_PREVIOUS =$00000006;
NAVDIR_FIRSTCHILD =$00000007;
NAVDIR_LASTCHILD =$00000008;
NAVDIR_MAX =$00000009;
SELFLAG_NONE =$00000000;
SELFLAG_TAKEFOCUS =$00000001;
SELFLAG_TAKESELECTION =$00000002;
SELFLAG_EXTENDSELECTION =$00000004;
SELFLAG_ADDSELECTION =$00000008;
SELFLAG_REMOVESELECTION =$00000016;
SELFLAG_VALID =$00000032;
STATE_SYSTEM_UNAVAILABLE =$00000001;
STATE_SYSTEM_SELECTED =$00000002;
STATE_SYSTEM_FOCUSED =$00000004;
STATE_SYSTEM_PRESSED =$00000008;
STATE_SYSTEM_CHECKED =$00000010;
STATE_SYSTEM_MIXED =$00000020;
STATE_SYSTEM_INDETERMINATE =STATE_SYSTEM_MIXED;
STATE_SYSTEM_READONLY =$00000040;
STATE_SYSTEM_HOTTRACKED =$00000080;
STATE_SYSTEM_DEFAULT =$00000100;
STATE_SYSTEM_EXPANDED =$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 =$00000001;
ROLE_SYSTEM_MENUBAR =$00000002;
ROLE_SYSTEM_SCROLLBAR =$00000003;
ROLE_SYSTEM_GRIP =$00000004;
ROLE_SYSTEM_SOUND =$00000005;
ROLE_SYSTEM_CURSOR =$00000006;
ROLE_SYSTEM_CARET =$00000007;
ROLE_SYSTEM_ALERT =$00000008;
ROLE_SYSTEM_WINDOW =$00000009;
ROLE_SYSTEM_CLIENT =$00000010;
ROLE_SYSTEM_MENUPOPUP =$00000011;
ROLE_SYSTEM_MENUITEM =$00000012;
ROLE_SYSTEM_TOOLTIP =$00000013;
ROLE_SYSTEM_APPLICATION =$00000014;
ROLE_SYSTEM_DOCUMENT =$00000015;
ROLE_SYSTEM_PANE =$00000016;
ROLE_SYSTEM_CHART =$00000017;
ROLE_SYSTEM_DIALOG =$00000018;
ROLE_SYSTEM_BORDER =$00000019;
ROLE_SYSTEM_GROUPING =$00000020;
ROLE_SYSTEM_SEPARATOR =$00000021;
ROLE_SYSTEM_TOOLBAR =$00000022;
ROLE_SYSTEM_STATUSBAR =$00000023;
ROLE_SYSTEM_TABLE =$00000024;
ROLE_SYSTEM_COLUMNHEADER =$00000025;
ROLE_SYSTEM_ROWHEADER =$00000026;
ROLE_SYSTEM_COLUMN =$00000027;
ROLE_SYSTEM_ROW =$00000028;
ROLE_SYSTEM_CELL =$00000029;
ROLE_SYSTEM_LINK =$00000030;
ROLE_SYSTEM_HELPBALLOON =$00000031;
ROLE_SYSTEM_CHARACTER =$00000032;
ROLE_SYSTEM_LIST =$00000033;
ROLE_SYSTEM_LISTITEM =$00000034;
ROLE_SYSTEM_OUTLINE =$00000035;
ROLE_SYSTEM_OUTLINEITEM =$00000036;
ROLE_SYSTEM_PAGETAB =$00000037;
ROLE_SYSTEM_PROPERTYPAGE =$00000038;
ROLE_SYSTEM_INDICATOR =$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.
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.
...
private
TextoVelho,
TextoNovo: String;
Pt: TPoint;
procedure GetInfo();
...
...
var
Frm_Principal: TFrm_Principal;
Acc: IAccessible;
VarParent: OleVariant;
Res: HResult;
implementation
...
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.
• 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.
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
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.
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.
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.
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.
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.
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.
· 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.
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.
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.
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.
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
09 for I := 0 to SOTokens.Count - 1 do
10 begin
11 SOToken := SOTokens.Item(I);
12 Cbx_Voz.Items.AddObject(SOToken.GetDescription(0), TObject(SOToken));
13 SOToken._AddRef;
14 end;
20 TbRate.Position := SpVoice.Rate;
21 Lbl_Pos.Caption := 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), adicionando-
as 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.
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.
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).
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.
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