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