Revista The Club Megazine - 10/2003 A utilização, reprodução, apropriação, armazenamento em banco de dados, sob qualquer forma ou meio, de textos, fotos e outras criações intelectuais em cada publicação da revista “The Club” são terminantemente proibidos sem autorização escrita dos titulares dos direitos autorais. Copyright© The Club® 2003 EDITORIAL Editorial THE CLUB Av. Celso Ferreira da Silva, 190 Jd. Europa - Avaré - SP - CEP 18.707-150 Informações: (0xx14) 3732-3689 Suporte: (0xx14) 3733-1588 - Fax: (0xx14) 3732-0987 Internet http://www.theclub.com.br Cadastro: [email protected] Suporte: [email protected] Informações: [email protected] Dúvidas Correspondência ou fax com dúvidas devem ser enviados ao - THE CLUB, indicando "Suporte". Opinião Se você quer dar a sua opinião sobre o clube em geral, mande a sua correspondência para a seção "Tire sua dúvida". Reprodução A utilização, reprodução, apropriação, armazenamento em banco de dados, sob qualquer forma ou meio, de textos, fotos e outras criações intelectuais em cada publicação da Revista “The Club” são terminantemente proibidos sem autorização escrita dos titulares dos direitos autorais. Copyright© The Club® 2003 Olá amigos, É com muito prazer que trazemos para vocês mais uma edição da revista The Club Megazine. Antes de mais nada gostaria de agradecer a todos os nossos associados que nos enviam temas de matérias que gostariam de vê-las publicadas em nossa revista. E aproveito também a oportunidade para convidar você que gostaria que abordassemos um determinado assunto para que envie um e-mail para [email protected], que teremos o maior prazer em lhe atender. Nesta edição temos várias informações vitais para o desenvolvimento de qualquer programa, como por exemplo o trabalho com campos lookup. Uma das grandes preocupações, senão a maior, de cada programador é em relação a performance da sua aplicação. E todos sabem que um campo lookup pode fazer com que a sua aplicação fique lenta, mas nesta edição você verá os macetes para não comprometer a sua aplicação. Outra preocupação dos programadores é em relação ao visual da aplicação. Quem trabalha com o Windows XP pôde notar que o formato das caixas de texto, caixas de seleção mudaram, mas quando você roda a sua aplicação que foi compilada principalmente em versões anteriores ao Delphi 7 o visual continua como era no Windows 2000, Windows 98. A partir de agora você poderá fazer com que a sua aplicação fique com a cara do Windows XP. Aprenda nesta edição a utilizar o XP Manifest. Voltando ao tema sobre acesso a dados, vamos falar mais sobre este poderoso componente chamado ClientDataSet. Veja nesta edição como trabalhar com tipo Aggregate e GroupState. Você aprenderá como efetuar cálculos simples no Aggregate e também melhorar a apresentação dos dados utilizando o GroupState. Você já imaginou incluir dentro do seu executável um arquivo de som? Nesta edição você verá uma matéria que mostra como é extremamente simples fazer este tipo de trabalho. Falando sobre internet nós estamos trazendo nesta edição uma matéria sobre envio de e-mails utilizando os excelentes componentes da palheta Indy. Estes componentes começaram a vir com o Delphi a partir da versão 6. E como não poderia deixar de ser também estamos trazendo as colunas de Dicas & Truques e também Perguntas & Respostas, como sempre com informações muito interessantes. Por enquanto é só, até o próximo mês com mais novidades ! Abraços e até lá. Impressão e acabamento: Impressos Gril - Gril Gráfica e Repr. Ind. Ltda. Tel.: (0xx14) 3762.1345 - Fax: (0xx14) 3762.1259 Rua São Paulo, 447 - Cep 18.740-000 Taquarituba - SP Tiragem: 5.000 exemplares Celso Jefferson Paganelli Presidente - The Club Diretor - Presidente Celso Jefferson M. Paganelli Diretor Técnico Mauro Sant’Anna Delphi é marca registrada da Borland International, as demais marcas citadas são registradas pelos seus respectivos proprietários. Editorial ........................................................................ Perguntas & Respostas ...................................................... Lookup - Trabalhando com campos virtuais no dbExpress Macetes para não comprometer a performance .................................... Usando o XP Manifest no Delphi ........................................... Enviando Emails via Delphi ................................................. Retrospectiva ................................................................. Usando Aggregates e GroupState no ClientDataset ...................... Distribuindo um arquivo dentro do executável ........................... Dicas & Truques .............................................................. MeGAZINE 3 03 04 06 11 13 16 18 26 28 Perguntas & Respostas Pergunta: Gostaria de saber como capturar o conteúdo digitado em um célula de um StringGrid e jogar para a área de transferência, porém, isso com a célula ainda em edição. Resposta: Quando uma célula do StringGrid está em edição, automaticamente é instanciado um objeto do tipo TinplaceEditor, através do qual é possível obter o texto que está sendo digitado, veja abaixo como proceder: Pergunta: Gostaria de saber como mudar o tamanho de um campo no Sql-Server. Por exemplo: o campo NOME tem 20 posições e eu gostaria de passar para 30 posições usando Delphi. Resposta: No caso do SQLServer, poderá executar a instrução abaixo utilizando um componente Query, por exemplo. ALTER TABLE CADGRUPOS ALTER COLUMN DESCRICAO VARCHAR(100) type TSGrid = class(TStringGrid) public property InplaceEditor; end; var Form1: TForm1; Dúvida enviada por João Roberto, São José do Rio Preto/SP. Pergunta: Como faço para ir para um determinado diretório através de um comando do Delphi (o diretório que está o executável que está rodando)? implementation {$R *.dfm} Resposta: procedure TForm1.SpeedButton1Click(Sender: TObject); begin {Transfere do StringGrid para a area de transf.} TSGrid(StringGrid1).InplaceEditor.CopyToClipboard; { Descarrega da area de transferencia para o Memo } Memo1.PasteFromClipboard; end; Dúvida enviada por Informenge Processamento de Dados Ltda., Sorocaba/SP. 4 Poderá utilizar as seguintes funções: ChDir(ExtractFilePath(Application.ExeName)); Onde a função ChDir, faz muda a pasta corrente, e a função ExtractFilePath extrai um path, neste caso o path do executável. Dúvida enviada por PHN Consultoria, Rio do Sul/SC. Pergunta: Quando estou no evento DBNavigator.BeforeAction é possível cancelar ou abortar a ação MeGAZINE Perguntas & Respostas que seria tomada, por exemplo, Refresh? Resposta: No evento OnBeforeAction do DBNavigator poderá fazer o seguinte tratamento: if Button = nbRefresh then Abort; Pergunta: Há como tornar a propriedade Ctrl3D visível no Object Inspector no Delphi5? Resposta: Sim, para isso, clique com o botão direito do mouse sobre o Object Inspector, vá em “View” e marque o ítem “Legacy”. Dúvida enviada por RDC Informática, Gramado/RS. Dúvida enviada por Moked do Brasil, São Paulo/SP. Pergunta: Estou começando a usar o IntraWeb agora e achei no Suporte On-Line do TheClub uma dica entitulada de “INTRAWEB - UTILIZANDO ARQUIVO DE ESTILO CSS”. Fiz todos os passos como descritos na dica, funcionou tudo direitinho (usei um memo para testes), porém ele some com a scrollbars do browser! O que fazer para aplicar o estilo e ao mesmo tempo mostrar a scrollbar do browser? Resposta: O que está ocorrendo é que o Sr. está alterando as configurações de “BODY” e com isso, irá afetar diretamente o browser. Caso necessite apenas alterar as configurações de “Memos”, deverá criar o css alterando apenas as configurações referentes “TextArea”, como mostra o exemplo abaixo: Pergunta: Estou desenvolvendo uma função em meu aplicativo onde necessito alterar o foco da janela ativa entre o meu aplicativo Delphi e uma janela de um documento aberto no word. Gostaria de saber como posso alternar via programação o foco da janela, entre o Delphi é o word (no meu documento) pois o usuario pode ter algum outro documento do word aberto também. Resposta: Isso é possível executando um FindWindow para pesquisa a Handle da janela do Word e depois enviar uma mensagem para colocá-la em evidência: a:link, a:active, a:visited { color: #CBAC57; text-decoration: underline} var W: Hwnd; begin W := FindWindow(‘OpusApp’, if W <> 0 then BringWindowToTop(W) else Caption := ‘Não achei’; end; a:hover { color: #B28816; text-decoration: none} Input {border: 0} TextArea { border: 0 scrollbar-face-color: #000000; scrollbar-shadow-color: #CBAC57; scrollbar-highlight-color: #CBAC57; scrollbar-3dlight-color: #FFFFFF; scrollbar-darkshadow-color: #FFFFFF; scrollbar-track-color: #FFFFFF; scrollbar-arrow-color: #CBAC57; overflow: hidden } nil); { Se souber o caption da janela. } var W: Hwnd; begin W := FindWindow(‘OpusApp’, if W <> 0 then BringWindowToTop(W) else Caption := ‘Não achei’; ‘Document9’); end; Dúvida enviada por Concentro Marcas Ltda., Santa Dorotéira/MS. Dúvida Virtual Technology, Belo Horizonte/MG. MeGAZINE 5 Delphi Lookup Trabalhando com campos virtuais no dbExpress Macetes para não comprometer a performance por Alessandro Ferreira ([email protected]) Introdução Temos recebido uma grande quantidade de emails de programadores que estão migrando suas aplicações desktop para ambiente Client/Server ou mesmo N-Tier com dúvidas em relação a campos virtuais que até então eram criados no objeto TTable utilizando o prático recurso de “Lookup”. Sem dúvidas, um campo lookup é bastante simples e prático para trabalhar, mas em um ambiente Client/Server, onde as tabelas tendem a ter um volume expressivo de dados, o comprometimento da performance é inevitável, visto que para cada registro da tabela principal, será necessário uma pesquisa na tabela detalhe, afim de trazer o valor correspondente para o campo lookup. Neste pequeno artigo, iremos demonstrar uma técnica simples, porém, não muito utilizada por falta de conhecimento, mas que sem dúvidas traz ótimos resultados principalmente em relação a performance, vamos lá! Banco de dados e Conexão Para este artigo, vamos utilizar o banco de dados Interbase/ Firebird, aproveitando o banco Employee.gdb que é instalado juntamente com este servidor, mas especificamente as tabelas Sales, Customer e Employee, as quais possuem relacionamento entre si, conforme apresentado na Figura 1: Figura 1 Relacionamentos 6 MeGAZINE Delphi Para começarmos, crie um novo projeto (File | New | Application) e salve-o. Após isso, adicione um DataModule (File | New | DataModule) ao mesmo, nomeando-o como dmPrincipal, e save a unit como unDM. Continuando, adicione um componente sqlConnection, dê um duplo clique sobre o mesmo e adicione uma conexão para o banco Employee.gdb, conforme sugere a Figura 2. Continuando, iremos detalhar o Select utilizado no componente sdsSales. Observe, que na tabela SALES, temos apenas o código do cliente (CUST_NO) e o código do empregado (SALES_REP), até aqui, nenhuma novidade... Porém, nossa necessidade é trazer o nome do cliente e o nome do empregado, contudo, sem utilizar campo Lookup, visto os mesmos não serem recomendados neste ambiente. Para isso, fizemos uma junção entre as tabelas através da cláusula LEFT OUTER JOIN, onde informamos o nome da tabela onde iremos buscar a informação e na cláusula “ON” informamos quais os campos de relacionamento. Dessa forma, automaticamente serão criados campos adicionais, o quais nomeamos de NOMECLIENTE e NOMEEMPREG, como o próprio nome sugere, irão trazer o “nome do cliente” e “nome do empregado”, respectivamente. DataSetProvider e ProviderFlags e ClientDataSet Adicione agora, três componentes DataSetProvider fazendo as configurações sugeridas no Quadro 2. Figura 2 – Conexão sqlConnection. Clique em OK, depois altere a propriedade LoginPrompt para False, Connected para True e Name para cnxEmployee, com isso, temos pronta a nossa conexão. Para fazer acesso as tabelas, adicione três componentes sqlDataSet e configure as propriedades conforme sugere o Quadro 1. Para prosseguirmos, adicione os TFields no sdsSales (clicando com o botão direito e selecionando “Add All Fields”). Agora, iremos fazer a configuração da propriedade ProviderFlags dos campos resultantes de joins (nossos campos “virtuais). Quando trabalhamos com componente DataSetProviders, estes componentes são responsáveis em gerar as instruções de atualização ao banco de dados (Inserts, Updates e Deletes) e para isso, estes analisam os campos disponíveis no componente sqlDataSet ao qual está ligado. Observe que entre os campos que foram adicionados ao sdsSales, os campos resultantes de joins também foram adicionados, dessa forma, quando o DataSetProvider tentar atualizar a tabela SALES, ele irá tentar atualizar os campos NOMECLIENTE e Quadro 1 – Configurações dos sqlDataSets. Quadro 2 – Configurações nos DataSetProviders. MeGAZINE 7 Delphi NOMEEMPREG, visto não ter como diferenciar se estes campos existem fisicamente na tabela ou não, e consequentemente gerando uma exceção avisando que não encontrou os referidos campos. Felizmente temos uma forma bastante simples para informar ao DataSetProvider quais campos deverão ser atualizados. Para isso, selecione os campos NOMECLIENTE e NOMEEMPREG e marque False para todas as opções da propriedade ProviderFlags destes dois campos, pois, dessa forma o DataSetProvider saberá que não deve envolver estes campos no processo de atualização da tabela SALES. Por último, vamos adicionar três componentes ClientDataSets através dos quais iremos fazer a manutenção dos dados, etc... Confira as configurações no Quadro 3. Quadro 3 – Configuração dos ClientDataSets Implementando o Formulário de Edição Continuando, iremos implementar agora o formulário responsável em fazer a edição dos dados da tabela SALES, veja a sugestão deste formulário na Figura 3. Dica: Poderá encontrar um artigo sobre implementação de formulários de pesquisa em nossa revista de Setembro/2003 no artigo “Pesquisa em Banco de Dados – Aprenda a implementar um formulário genérico para pesquisas”, a qual também está disponível em nosso site, www.theclub.com.br. Estando pronto o nosso formulário para edição, vamos implementar a validação do código do cliente e empregado, o qual o usuário poderá digitar diretamente nos DBEdits ou, caso não os saiba, poderá clicar no botão de pesquisa para localizar os mesmos. Iremos fazer a validação no evento OnExit dos próprio DBEdits, como demonstra o código apresentado na Listagem 1. procedure TfmSales.DBEdit2Exit(Sender: TObject); begin with dmPrincipal do begin if (dsSales.State in [dsInsert, dsEdit]) then { Valida código digitado } if not LocateSQL(‘CUSTOMER’, cdsCustomer, [‘CUST_NO’], [DBEdit2.Text]) then begin ShowMessage(‘Cliente não cadastrado!’); DBEdit2.SetFocus; end else { Atualiza o campo “Virtual” } cdsSales.FieldByName(‘NOMECLIENTE’). AsString := dsCustomer.FieldByName (‘CUSTOMER’).AsString; end; end; procedure TfmSales.DBEdit3Exit (Sender: TObject); begin with dmPrincipal do begin if (dsSales.State in [dsInsert, dsEdit]) then { Valida código digitado } if not LocateSQL(‘EMPLOYEE’, dmPrincipal.cdsEmployee, [‘EMP_NO’], [DBEdit3.Text]) then begin ShowMessage(‘Empregado não cadastrado!’); DBEdit3.SetFocus; Figura 3 – Formulário edição da tabela SALES. 8 MeGAZINE Delphi end; finally fmPesquisa.Free; end; end; end; end else { Atualiza o campo “Virtual” } dmPrincipal.cdsSales.FieldByName (‘NOMEEMPREG’).AsString := dmPrincipal.cdsEmployee.FieldByName (‘FULL_NAME’).AsString; end; procedure TfmSales.sbtnEmpregadoClick(Sender: TObject); begin with dmPrincipal do begin fmPesquisa := tfmPesquisa.Create (Self, dmPrincipal.cdsEmployee, ‘EMPLOYEE’); try if (fmPesquisa.ShowModal=mrOK) and (dsSales.State in [dsInsert,dsEdit]) then begin { Atribui valores retornados da Pesquisa } cdsSales.FieldByName(‘SALES_REP’). AsString := cdsEmployee.FieldByName (‘EMP_NO’).AsString; { Atualiza o campo “Virtual” } cdsSales.FieldByName(‘NOMEEMPREG’). AsString := cdsEmployee.FieldByName (‘FULL_NAME’).AsString; end; finally fmPesquisa.Free; end; end; end; Listagem 1 – Validações no OnExit. Neste exemplo, utilizamos uma função chamada LocateSQL a qual criamos exatamente para fazer validação de valores, passando como parâmetro o nome da tabela, o componente ClientDataSet (referente a tabela “lookup”), a lista de campos e a lista de valores, e tendo como resultado um tipo boolean. Caso a função encontre os valores pesquisados, já teremos o ClientDataSet posicionado no referido valor, basta pegar por exemplo o nome do cliente e atribuir ao campo “virtual” (resultante do join)., visto a atualização do campo “virtual” não ser automática no caso de novos registros... Vamos agora para a implementação do código dos botões de pesquisa, onde estamos utilizando a abordagem sugerida no artigo da revista de Setembro/2003, conforme sugerido anteriormente. Confira a Listagem 2. procedure TfmSales.sbtnClienteClick(Sender: TObject); begin with dmPrincipal do begin fmPesquisa := tfmPesquisa.Create (Self, cdsCustomer, ‘CUSTOMER’); try if (fmPesquisa.ShowModal=mrOK) and (dsSales.State in [dsInsert,dsEdit]) then begin { Atribui valores retornados da Pesquisa } cdsSales.FieldByName(‘CUST_NO’). AsString := cdsCustomer.FieldByName(‘CUST_NO’). AsString; { Atualiza o campo “Virtual” } cdsSales.FieldByName(‘NOMECLIENTE’). AsString := cdsCustomer.FieldByName (‘CUSTOMER’).AsString; end; Listagem 2 – Código dos botões de pesquisa. Na Listagem 3 estamos apresentando o código da função LocateSQL que utilizamos para fazer as validações dos valores digitados nos DBEdits. MeGAZINE function LocateSQL(TableName: String; DataSet: TClientDataSet; const KeyFields, KeyValues: Array of Variant; const SQLPadrao: string = ‘’): Boolean; var i: Integer; aSql, aWhere: String; 9 Delphi begin try with DataSet do begin // monta cláusula where. for i := 0 to High(KeyFields) do begin aWhere := aWhere+’UPPER(‘+KeyFields[i]+’) = ‘+QuotedStr(UpperCase(KeyValues[i]))+’ ‘; if i < High(KeyFields) then aWhere := aWhere+’ AND ‘; end; // monta Select. if SQLPadrao<>’’ then aSql := Format(‘%s where %s ‘, [PreparaSQLPadrao(SQLPadrao), aWhere]) else aSql := Format(‘Select * From %s where %s’, [TableName, aWhere]); Close; CommandText := aSql; Open; Result := not IsEmpty; end; except on E: Exception do begin ShowMessage(‘Erro no LocateSQL: ‘+E.Message); Result := False; end; end; end; Quadro 4 – Parâmetros da função LocateSQL. para a substituição de campos Lookup dentro do ambiente Client/ Server ou mesmo N-Tier. A princípio, isso pode parecer um pouco mais complicado para quem está vindo de aplicações desktop, mas podemos afirmar que o resultado apresentado utilizando a técnica aqui exposta traz ótimos resultados e compensa este trabalho. Outro detalhe a ressaltar, é o fato que esta abordagem pode ser extendida à aplicações que utilizam BDE, IBX, ADO, ou qualquer outra engine de acesso que esteja trabalhando. Forte abraço à todos e até a próxima, Listagem 3 – Função LocateSQL. Download Acompanhe no Quadro 4 a explicação dos parâmetros requeridos pela função LocateSQL. O projeto de exemplo utilizado neste artigo está disponível para download em: Em resumo, a função apenas monta uma instrução SQL com uma cláusula where, fazendo uso dos parâmetros informados, atribuiu esta instrução à propriedade CommandText do ClientDataSet (também recebido como parâmetro), abre o mesmo e caso retorne algum registro, resulta True, caso contrário, False. Conclusão Tentamos demonstrar neste simples artigo uma abordagem 10 http://www.theclub.com.br/revista/download/dbxLookup.zip. Sobre o autor Alessandro Ferreira, Consultor Técnico do The Club [email protected] MeGAZINE Delphi Usando o XP Manifest no Delphi Por Claudinei Rodrigues – [email protected] Aqui você vai encontrar as informações sobre como deixar a sua aplicação compilada em versões anteriores ao Delphi 7, com os controles iguais ao Windows XP. O Windows XP tem um gerente de temas que muda a aparência da maioria dos controles e padrão das janelas. A Microsoft afirma que existe uma versão mais velha do arquivo comctl32.dll que contém o código para manter as diferentes plataformas do Windows. A introdução de “temas” no Windows XP foi incluída dentro do código contido no arquivo comctl32.dll. Agora são mantidas as duas versões no mesmo controle. A mais antiga (versão 5.8) é a versão que é compatível com todas as versões do Windows incluindo a versão XP, e a mais nova (versão 6) é compatível apenas com o XP (e algumas futuras plataformas do Windows). Por default, todos os programas escritos no Windows XP optarão pela versão 5.8. Para incorporar a versão 6.0, você deve incluir em sua aplicação uma declaração, que chamamos de Manifest, para que o sistema operacional leia o gerente dos temas e assim adote o ambiente do Windows XP. O que seria este Manifest? O Manifest é um documento XML que deve ser incluído dentro do arquivo de recursos do seu executável. Normalmente este recurso é reservado para outras coisas como imagens, ícones e cursores de mouse. Qualquer informação pode ser incluída em um arquivo de recurso. O documento XML quando colocado de forma correta dentro de um arquivo de recurso permitirá ao Windows XP decidir por qual versão da comctl32.dll ele irá utilizar. Para incluir este código XML em sua aplicação você deve primeiro conhecer as constantes apresentadas pela Microsoft. Quando incluído um novo recurso no executável, existe um grupo de números e itens associados a este recurso. O grupo de números é normalmente classificado com um nome amigável. Por exemplo, se você usar um demo que acompanha o Delphi chamado ResXPlor que está disponível no diretório C:\Program Files\Borland\Delphi5\Demos\Resxplor, você poderá ver os grupos Strings, Bitmaps, Icons ou Cursors. Estes nomes são apenas representações amigáveis de um grupo de números. O grupo de números para o Manifest é 24, conforme o cabeçalho em C distribuído pela Microsoft. O número do item do Manifest para determinar a versão do comctl32.dll é 1. Esta informação vem a ser necessária quando você for construir o seu MeGAZINE 11 Delphi novo arquivo de recurso para ser ligado aos seus executáveis. Para criar um arquivos .RES nós vamos precisar primeiro criar um arquivo .RC que designa o seu documento XML para um grupo apropriado e para uma linha apropriada. No arquivo zip contido neste documento, você irá encontrar dois arquivos: WindowsXP.rc e WindowsXP.Manifest. O arquivo WindowsXP.rc contém suas instruções para incluir o documento (XML) WindowsXP.Manifest como item 1 ligado ao grupo 24. O conteúdo do WindowsXP.RC é o seguinte: </dependentAssembly> </dependency> </assembly> Agora que já temos os dois arquivos, nós vamos precisar usar o compilador de arquivos de recurso do Delphi. Fazendo isto teremos um arquivo .RES que poderemos incluir em nossas aplicações. A partir de uma linha de comando digite o seguinte: C:\ProjetoTeste\brcc32 WindowsXP.rc Depois de você ter compilado o arquivo WindowsXP.rc, você verá o arquivo WindowsXP.res no mesmo diretório. 1 24 “WindowsXP.Manifest” O manifest, é um documento XML que contém informações sobre a aplicação que você está escrevendo e também informação a respeito da versão da comctl32.dll para uso. A seguir você poderá ver um exemplo de um código XML que você poderá fazer algumas modificações para ser utilizado na sua aplicação. <?xml version=”1.0" encoding=”UTF-8" standalone=”yes”?> <assembly xmlns=”urn:schemas-microsoft-com:asm.v1" manifestVersion=”1.0"> <assemblyIdentity name=”www.theclub.com.br” processorArchitecture=”x86" version=”5.1.0.0" type=”win32"/> <description>Windows Shell</description> <dependency> <dependentAssembly> <assemblyIdentity type=”win32" name=”Microsoft.Windows.Common-Controls” version=”6.0.0.0" processorArchitecture=”x86" publicKeyToken=”6595b64144ccf1df” language=”*” /> 12 O ultimo passo para fazer com que a sua aplicação se torne compatível com o Windows XP é incluir este recurso em sua aplicação. O caminho mais fácil para fazer isto é incluir em seu arquivo .DPR ou no formulário principal da sua aplicação a seguinte diretiva: {$R WindowsXP.RES} O melhor local para se fazer isto é logo após a linha {$R *.DFM}. Uma vez incluída esta declaração em sua aplicação, basta compilar e executar. O gerenciador de tema do Windows vai fazer com que a sua aplicação fique com os controles iguais do Windows XP. Até aqui a única discrepância encontrada foi com o componente TListView. Se você utilizar o componente TListView com a propriedade ViewStyle como vsReport você terá um problema com a propriedade TColumns. Em tempo de execução ocorrerá um erro se você tentar utilizar o cabeçalho de colunas, mas apenas com a propriedade ViewStyle com vsReport. Download do exemplo: http://www.theclub.com.br/revista/download/demoxp.zip Sobre o autor Claudinei Rodrigues, Consultor Técnico do The Club [email protected] MeGAZINE Delphi Enviando Emails via Delphi Enviando e anexando arquivos com Indy por Alessandro Ferreira, [email protected] Introdução TIdSMTP Desde o Delphi 6, a Borland introduziu uma suíte de componentes chamada Indy, a qual possui uma grande variedade de componentes, para as mais diversas tarefas possíveis, principalmente relacionados a internet. Finalidade Apesar de estar disponível apenas a partir do D6, você poderá baixar gratuitamente este pacote de componentes e instalar em seu Delphi 5, bastando para isso acessar o site do fabricante, http://www.indyproject.org/. Componente responsável pela implementação do protocolo SMTP (Simple Mail Transfer Protocol Client), ou seja, o protocolo utilizado no envio de emails. Propriedades principais Neste pequeno artigo, demonstrarei como criar uma aplicação para enviar emails via Delphi, com a possibilidade de enviar arquivos anexos ao mesmo, vamos lá... Componentes AuthenticationType Tipo de autenticação requerida pelo host SMTP AuthSchemesSupported Esquemas de autenticação aceitas pelo host SMTP Password Senha para conexão/autenticação UserName Usuário para conexão/autenticação TidMessage Para este exemplo, iremos utilizar os componentes TIdSTMP (aba Indy Clients) e o componente TIdMessage (aba Indy Misc), os quais serão explicados no Quadro 1. Finalidade Propriedades principais Implementação Bem, agora vamos partir para a implementação do nosso projeto de exemplo. Para começar, crie um novo projeto (menu File | New | Application) e adicione alguns componentes, como sugere a Figura 1. Encapsular o conteúdo e definições da mensagem From Informar "quem" está enviando o email Recipients Informar destinatários Subject Informar assunto do email Body Corpo/texto da mensagem Quadro 1 – Componentes Indy utilizados. MeGAZINE 13 Delphi Figura 1 – Layout sugerido A seguir, na Listagem 1 apresentamos o código do sbtnAnexar, através do qual iremos selecionar arquivos em disco e ir adicionando ao ListArquivos (TListBox) responsável em armazenar todos os arquivos que iremos anexar ao nosso email. procedure TForm1.sbtnAnexarClick(Sender: TObject); begin if OpenDialog1.Execute then ListaArquivos.Items.Add(OpenDialog1.FileName); end; Listagem 1 – Código do botão Anexar. Agora, vamos para a implementação do código para envio do email no evento OnClick do botão BT_Envia, confira a Listagem 2. procedure TForm1.BT_EnviaClick(Sender: TObject); var i: integer; begin BT_Envia.Enabled := not BT_Envia.Enabled; try { configura o Email. } with IdMsgSend do begin 14 MeGAZINE From.Text := EdFrom.Text; Recipients.EMailAddresses := EdTo.Text; Subject := EdSubject.Text; Body.Assign(MTexto.Lines); end; { Configura os anexos, caso houver... } for i := 0 to ListaArquivos.Items.Count-1 do TIdAttachment.Create (IdMsgSend.MessageParts, ListaArquivos.Items[i]); { Envia o Email. } with IdSMTP1 do begin { configura com o seu servidor SMTP. } Host := ‘smtp.seuprovedor.com.br’; try Connect; Send(IdMsgSend); finally if Connected then Disconnect; end; end; finally Delphi BT_Envia.Enabled := not BT_Envia.Enabled; ListaArquivos.Clear; end; end; with IdSMTP1 do begin AuthenticationType := atLogin; Host := ‘smtp.provedor.com.br’; UserId := ‘sua_conta’; Password := ‘sua_senha’; try Connect; Send(IdMsgSend); finally if Connected then Disconnect; end; end; Listagem 2 – Código completo do botão Enviar. A primeira linha do código, deixa o BT_Envia desabilitado durante o processo de envio do email. Após isso, partimos para as configurações de Origem do Email, Destinátário, Assunto e o texto de nosso email. Feito isso, partimos para adicionar os arquivos à serem anexado ao email (caso existam), fazendo um looping nos ítens do componente ListaArquivos, criando uma “parte” dentro do IdMsgSend (TidMessage) através do objeto TidAttachment, o qual descreve ao IdMsgSend as informações sobre cada arquivo à ser anexado, recebendo como parâmetro um MessagePart e o caminho+nome do referido arquivo. Estando a definição da mensagem definida no componente IdMsgSend, iremos partir para a configuração da conexão SMTP, que será responsável em enviar o email, utilizando-se de uma conexão pré-estabelecida. Confira no Quadro 2. Observe que adicionamos algumas linhas em relação ao código original apresentado anteriormente, confira o Quadro 3. IdSmtp. AuthenticationType Quandro informado o valor atLogin, prevê que serão informados usuário e senha para autenticação IdSmtp.Host Endereço do servidor SMTP de seu provedor IdSmtp.UserId Nome do usuário para autenticação IdSmtp.Connect Conectar ao servidor SMTP (*) IdSmtp.Password Senha do usuário para autenticação IdSmtp.Send() Envia o email, passando como parâmetro a mensagem encapsulada no componente IdMsgSend IdSmtp.Disconnect Desconecta do servidor SMTP Quadro 2 – Parâmetro no IdSmtp (*) Antes de chamar o método “Connect” do componente IdSMTP, você já deve estar conectado a internet, ou seja, o método “Connect” do referido componente não faz a “discagem” para o provedor, e sim somente, conecta ao servidor SMTP utilizando-se de uma conexão pré-estabelecida. Conclusão Procurei neste artigo, demonstrar uma forma bastante simples de enviar emails com arquivos anexados, utilizando componente da suite Indy. No site do fabricante, http:// www.indyproject.org/, poderá encontrar documentação e alguns projetos de exemplo, os quais abordam basicamente as funcionalidades do Indy. Caso tenha sugestões a respeito de temas que gostaria ver publicado aqui na The Club Megazine, sintase a vontade em nos enviar que iremos fazer o possível para atendê-lo! Forte abraço à todos e até a próxima, Download O projeto de exemplo utilizado neste artigo está disponível para download em http://www.theclub.com.br/revista/download/ EmailIndyAttach.zip. Servidores x Autenticação Geralmente, a autenticação não é exigida no envio de emails e sim somente no recebimento dos mesmos. Contudo, existem alguns provedores exigindo a autenticação também no envio de emails e felizmente isso está previsto no componente IdSMTP, conforme mostra a Listagem 3. MeGAZINE Sobre o autor Alessandro Ferreira, Consultor Técnico do The Club [email protected] 15 The Club Megazine Nossa retrospectiva continua. Este mês, você pode relembrar da revista de 2001, onde publicamos um artigo das ações que os usuários de sua aplicação podem iniciar pressionando um botão em um toolbar. Também foi publicado como ver o tipo de conexão de internet que o usuário possui, e outros truques na nossa seção de dicas. Também tivemos várias páginas com perguntas enviadas pelos nossos leitores. Também importante, publicamos um artigo sobre XML e ADO.NET, escrita por Mauro Sant´anna, já nos dando um vislumbre do mundo .NET, onde o Delphi 8 (Octane) já estará trabalhando como ambiente padrão. O nosso colaborador Mario Bohm, também escreveu um artigo interessante sobre Queries e SubQueries. Todas estas matérias relacionada aqui, e outras publicadas na mesma revista, podem ser encontradas em nosso site: http://www.theclub.com.br. O mês que vem iremos lembrar da revista do mês de Setembro de 2000. 16 MeGAZINE Delphi Usando Aggregates e GroupState no ClientDataset por André Colavite - [email protected] Este artigo descreve como usar Aggregates para efetuar simples calculos e também como usar o Group State para melhorar a apresentação dos dados ao usuário. No total cinco estatisticas são suportadas pelo aggregate. São elas count (Contador), minimum (Menor), maximum (Maior), sum (Somatória) e average (Média). Aggregates são objetos que podem executar automaticamente calculos básicos baseando-se nos dados armazenados no ClientDataSet. Group State é a informação que identifica a relativa posição do registro dentro de um grupo de registros, baseando-se no índice. Essas duas características juntas permitem que você adicione habilidades para facilitar a manutenção em seus aplicativos. São de dois tipos os objetos que você pode usar para criar os aggregates: TAggregate e TAggregateFields. Um TAggregate é descedente da classe TCollectionItem e o TAggregateFields é descendente da classe TField. Se você não está familiarizado com o Aggregate ou Group State, você pode estar se perguntando por que estou abordando essas duas características no mesmo artigo. A resposta é simples. Ambos estão associados com o groupping level, que é uma característica relacionada ao índice. Como a discussão do Aggregate necessariamente envolve o Grouping Level, a abordagem do group state é natural. Entendendo o Aggregate O Aggregate é um objeto que pode executar automaticamente calculos básicos através de um ou mais registros do ClientDataSet. Por exemplo, imagine que você tem um ClientDataset que contem uma lista de compras para seus clientes. Se cada registro contem um campo que identifica o cliente, o numero de items comprado, e o valor total da compra, um aggregate pode calcular a soma de todas as compras através de todos os registros da tabela. Contudo outro aggregate pode calcular o numero médio de itens comprados para cada cliente. 18 Enquanto esses dois tipos de Aggregates são semelhantes em sua configuração, eles diferem em seu uso. Especificamente o TAggregatefield, por ser descendente de TField, pode ser associado com um controle data-ware, permitindo que o valor do aggregate possa ser mostrado automaticamente. Para comparação, o TAggregate é um objeto cujo valor deve ser lido explicitamente em tempo de execução. Criando campo Aggregate Campos agregado são campos virtuais, persistentes. Apesar de serem semelhantes a outros campos virtuais, como os campos calculados e campos de lookup, há uma diferença muito importante. Especificamente, adicionar um ou mais campos Aggregates não impedem a criação dinâmica de campos em tempo de execução, isso quer dizer que não nos obriga a incluir os demais campos na lista de Tfields. Por outro lado se você adicionar qualquer outro tipo de campo tipo Data, Lookup ou Calculado, esses impedem o ClientDataSet de criar outros campos em tempo MeGAZINE Delphi de execução. Sendo assim os campos Aggregates podem ser criados em tempo de desenvolvimento, tendo ou não outros campos criados na lista de Tfields. A criação de campos Aggregates requer vários passos específicos em sua configuração, sendo assim veremos agora quais são esses passos: o Adicionar um campo Aggregate no ClientDataSet. Isso poderá ser feito em tempo de desenvolvimento através da lista de TFields ou em tempo de execução através da propriedade Agregates do ClientDataSet; * Propriedade Expression, desse campo Aggregate, definir o calculo que o campo irá executar; * Propriedade Indexname, indicar o índice que irá controlar o agrupamento; * Propriedade GroupingLevel, indicar qual o nível de agrupamento dos registros; * Propriedade Active, selecionar o valor True para ativá-lo; * Propriedade Visible, selecionar o valor True; * Configurar a propriedade AggregatesActive do ClientDataSet para True. Para um melhor entendimento sobre a criação de campo Aggregate, vamos criar um simples projeto de exemplo, seguindo os passos abaixo: Imagem 1 Nos próximos passos, criaremos o campo Aggregate através do Fields Editor do ClientDataSet. 1. Clique com o botão direito do mouse sobre o ClientDataSet e selecione a opção Fields Editor; 2. Clique com o botão direito do mouse sobre o Fields Editor e selecione a opção New Field, assim será apresentado o form New Field; 3. Configure a propriedade Name para CustomerTotal e depois na opção Field Type selecione o Aggregate, conforme Imagem2; 1. Crie um novo projeto; 2. Adicione no form principal um DBNavigator, um DBGrid, um ClientDataSet e um DataSource; 3. Configure a propriedade Align do DBNavigator para alTop, e a propriedade Align do DBGrid para alClient; 4. Configure a propriedade DataSource dos componentes DBNavigator e DBGrid para DataSource1 e a propriedade DataSet do DataSource para ClientDataSet1; 5. Configure a propriedade FileName do ClientDataSet para a table Orders.cds (or Orders.xml) localizado no diretório \Program Files\Common Files\Borland Shared\Data; Neste instante seu form principal deve ficar parecido com a Imagem1: MeGAZINE Imagem 2 19 Delphi 4. Clique sobre o OK para fechar a janela e você poderá ver o novo campo Aggregate adicionado no Fields Editor, veja que ele foi adicionado numa lista separada, conforme Imagem3. MAX( ShipDate ) + 30 Expressões Incorretas Algumas expressões são incorretas, como por exemplo, não podemos incluir uma função agregada como uma expressão de outra função agregada. Veja um exemplo incorreto: SUM( Max( AmountPaid ) ) <-- incorreta Também não podemos usar um calculo entre uma função agregada e um campo da tabela. Por exemplo, se Quantity é o nome de um campo, a seguinte expressão está incorreta: SUM( Price ) * Quantity <-- incorreta Em nosso exemplo em particular, queremos calcular o Total do campo AmountPaid e para isso usaremos os seguintes passos: Imagem 3 Definindo o Expression do Aggregate A propriedade Expression do Aggregate define o cálculo que o campo Aggregate irá executar. Esta expressão pode consistir de constantes, campos de valor e funções agregadas. As funções agregadas são AVG, MIN, MAX, SUM e COUNT. Para definir um cálculo que totaliza o campo AmountPaid da tabela Orders.cds, iremos montar uma expressão usando a função SUM, conforme o exemplo a seguir: SUM( AmountPaid ) O argumento de uma função agregada pode incluir dois ou mais campos em cada expressão. Por exemplo, temos dois campos na tabela, um nomeado Quantity e o outro nomeado Price, você poderá montar uma expressão da seguinte forma: SUM( Quantity * Price ) Numa expressão também podemos incluir constantes. Por exemplo, se a taxa de imposto é 8.25%, você pode criar um campo Aggregate que calculará o total do imposto, usando uma expressão da seguinte forma: 1. Selecione o campo Aggregate no fields Editor; 2. Usando o Object Inspector, configure a propriedade Expression com a expressão SUM(AmountPaid) e depois a propriedade Currency para True. Configurando o índice e o GroupingLevel Um Aggregate necessita saber sobre quais registros executará o cálculo. Isso será feito usando as propriedades IndexName e GroupingLevel do Aggregate. No caso, se você deseja executar um cálculo entre todos os registros do ClientDataSet, você pode deixar o IndexName em branco e o GroupingLevel configurado para 0 (Zero). Se você deseja que o Aggregate execute calculos entre um grupo de registros, você deve ter um índice cujo campo inicial determine o grupo. Por exempo, se deseja calcular a somatória do campo AmountPaid separado por cada cliente, sendo o cliente identificado pelo campo CustNo, você deve configurar o IndexName com o nome do índice cujo primeiro campo seja o CustNo. Se deseja executar o calculo por cada cliente e por cada data de compra, tendo os campos CustNo e SaleDate, você deve configurar no IndexName o índice que tem os campos CustNo e SaleDate como os dois primeiros campos. SUM( Total * 1.0825 ) Você pode configurar a propriedade Expression para executar uma operação entre duas funções agregadas, veja exemplo: MIN( SaleDate )-MIN( ShipDate ) Como também podemos executar uma operação entre uma função e uma constante, veja exemplo: 20 O índice atribuido na propriedade IndexName pode ter mais campos que o número de campos que você deseja agrupar e é nesse momento que o GroupingLevel entra. Você configura o GroupingLevel para o número de campos do índice que deseja considerar como grupo. Por exemplo, imagine que você configure o IndexName para um índice criado pelos campos CustNo, SaleDate e PurchaseTypes nessa ordem. Se configurar o GroupingLevel para 0, o cálculo do Aggregate será executado MeGAZINE Delphi sobre todos os registros do ClientDataSet, sem agrupamento. Configurando o GroupingLevel para 1, o cálculo será executado por cada cliente (lembre-se que o campo CustNo é o primeiro campo do índice). Configurando o GroupingLevel para 2 o cálculo será executado por cada cliente e por cada data da compra. propriedade IndexName para CustIdx. 5. Depois, usando o Fields Editor, selecione o campo Aggregate, configure a propriedade IndexName para CustIdx e a propriedade GroupingLevel para 1. O Object Inspector deve ficar parecido com a Imagem5. É interessante notar que a classe TIndexDefs, sendo a classe usada para definir o índice, também tem a propriedade GroupingLevel. Se você configurar essa propriedade para cada índice, o índice conterá informações adicionais sobre cada agrupamento de registro. Contanto que você esteja configurando o GroupingLevel de um Aggregate para um valor maior que 0, você pode melhorar o desempenho do Aggregate configurando o GroupingLevel do índice pelo menos a um valor tão alto quanto o GroupingLevel do Aggregate. Note, porém, que um índice cuja propriedade GroupingLevel esteja configurada para um valor maior que 0, leva um tempo um pouco maior para gerar uma atualização, já que isto também deve produzir informações de agrupamento. Esta sobrecarga é mínima, mas deve ser considerada caso a velocidade de geração e manutenção do índice seja uma preocupação. O próximo passo encaminha você através do processo de criação de um índice do campo CustNo, e então configurar o campo Aggregate para usar este índice com o GroupingLevel igual a 1. 1. Selecione o ClientDataSet e no object inspector selecione a propriedade IndexDefs. Clique no botão da propriedade IndexDefs para mostrar o IndexDefs Editor. Veja Imagem4 Imagem5 Trabalhando o campo Aggregate O campo aggregate está quase pronto. Para que ele possa trabalhar, devemos ainda configurar as suas propriedades Active e Visible e também a propriedade AggregatesActive do ClientDataSet para True. Veja os passos a seguir: 1. Selecione o campo Aggregate e, através do object inspector, configure as propriedades Active e Visible para True; 2. Agora, selecione o ClientDataSet e configure a propriedade AggregatesActive para True. Depois disso o aggregate será calculado automaticamente quando o ClientDataSet estiver ativo. O campo Aggregate está quase pronto, este é mais um passo, que ligará o campo Aggregate com um componente de controle de dados. Os próximos passos demonstrarão como deixar o campo Aggregate visível num DBGrid. Imagem 4 2. Click no botão Add New do IndexDefs Editor para adicionar um novo índice; 3. Selecione o novo índice e usando o object inspector configure a propriedade Name para CustIdx, a propriedade Fields para CustNo, e a propriedade GroupingLevel para 1. Feche o IndexDefs Editor. 4. Com o ClientDataSet ainda selecionado, configure a 1. Selecione o ClientDataSet e configure a sua propriedade Active para True; 2. Selecione agora o componente DBGrid, clique com o botão direito do mouse e selecione a opção Columns Editor; 3. Clique no botão Add All Fields para adicionar as colunas dos campos do ClientDataSet. Neste momento podemos observar que a coluna do campo aggregate não foi adicionada, conforme Imagem6; MeGAZINE 21 Delphi 6. Feche o Columns Editor; 7. Se você seguiu todos os passos, o campo Aggregate foi adicionado e deve estar visível como a terceira coluna do DBGrid, conforme Imagem8. Dicas adicionais sobre o Aggregate Imagem 6 4. Agora iremos criar uma nova coluna para mostrar o campo aggregate, portanto clique no botão Add New para criar a nova coluna; 5. Nesta nova coluna configure a propriedade FieldName para CustomerTotal. Para tornar a visualização desse campo mais fácil, mova esta nova coluna dentro da lista deixando-a como a terceira coluna do DBGrid, conforme Imagem7. 1. A propriedade AggregatesActive do ClientDataSet é usada para ligar e desligar os Aggregates em tempo de execução. Configurando o AggregatesActive para False é extremamente útil, quando você precisa adicionar, remover ou alterar inúmeros registros e essas alterações afetarem os calculos do Aggregate. Pois se você efetuar as alterações nos dados do ClientDataSet, e a cada alteração os calculos forem atualizados, essas alterações ficarão mais lentas. Sendo assim deixe o AggregatesActive como False e após efetuar suas alterações volte para True, para que assim o campo Aggregate seja recalculado. 2. Outra dica, no lugar de ligar e desligar todos os Aggregates, a propriedade Active de cada Aggregate permite manipulálo individualmente em tempo de execução. Isso pode ser útil se você tem muitos Aggregates, mas somente um ou dois serão atualizados durante as alterações no ClientDataSet. Subsequentemente reativando o Aggregate o recalculo será executado automaticamente. Em tempo de execução você pode ler a propriedade ActiveAggs do ClientDataSet para visualizar quais Aggregates estão atualmente ativos para um determinado nível de agrupamento. Criando itens Aggregate Itens Aggregates são criados através da propriedade Aggregates do ClientDataSet. Esses itens Aggregates são semelhantes ao campos Agregates, pois executam automaticamente cálculos simples. Porém, os valores dos itens Aggregates somente podem ser lidos em tempo de execução e não podemos ligá-los a componentes de controles de dados (DBGrid). A configuração dos itens Aggregates são iguais as dos campos Aggregates. Os passos seguintes demonstram como adicionar e usar os items Aggregates no projeto: Imagem 7 22 1. Selecione o ClientDataSet e no object inspector selecione a propriedade Aggregates; Clique no botão da propriedade para abrir a janela do Editor; conforme Imagem9. 2. Clique no botão Add New e adicione dois itens Aggregates; 3. Selecione o primeiro Aggregate e usando o object inspector configure as propriedades: Expression: AVG(AmountPaid) AggregateName: CustAvg IndexName: CustIdx GroupingLevel: 1 Active: True MeGAZINE Delphi Imagem 8 Visible: True. 4. Selecione o segundo Aggregate e através do object inspector configure as propriedades: Expression: MIN(SaleDate) AggregateName: FirstSale IndexName: CustIdx GroupingLevel: 1 Active: True Visible: True. 5. Adicione um componente PopupMenu em seu projeto. Dê duplo click sobre o popupmenu e crie um item colocando em seu Caption o seguinte texto: Sobre este Cliente. 6. Configure a propriedade popupmenu do DBGrid para Popupmenu1; 7. Finalizando, crie o evento onclick do item do popupmenu e inclua a seguinte instrução: procedure TForm1.SobreesteCliente1Click (Sender: TObject); begin Imagem 9 Imagem 10 ShowMessage(‘A média de venda para este Cliente é ‘ + FormatFloat(‘###,##0.00’, ClientDataSet1. Aggregates[0].Value) +#13+‘A primeira venda para este Cliente foi em ‘+ DateToStr(ClientDataSet1. Aggregates[1].Value)); end; 8. Executando o seu projeto neste momento, o seu Form deverá ficar igual a Imagem10. 9. Para visualizar o valor dos itens Aggregates criados, pressione o botão direito do mouse sobre o DBGrid e selecione a opção “Sobre este Cliente”, onde será apresentado o ShowMessage contendo as informações dos dois itens MeGAZINE 23 Delphi Como deve ser, se o registro atual for o primeiro registro no grupo, GetGroupState retornará o valor gbFirst. Se o registro for o último registro no grupo, retornará gbLast. Quando GetGroupState é chamado para um registro em algum lugar no meio do grupo, o retorno será gbMiddle. Finalmente, se o registro atual for o único registro do grupo, GetGroupState devolverá os valores gbFirst e gbLast. Imagem 11 Aggregates. Conforme Imagem11. Pronto, aqui terminamos a configuração dos Aggregates em nosso projeto. Entendendo o GroupState Group State recorre à posição relativa de um determinado registro dentro do seu grupo. Usando Group State você pode saber se um determinado registro é o primeiro registro dentro de seu grupo (dado o índice atual), o último registro de seu grupo, nem o primeiro nem o último registro do seu grupo ou ainda o único registro no grupo. Você verifica o estado de um registro em particular dentro do grupo, chamando o método GetGroupState do ClientDataSet. Este método tem a seguinte sintaxe: function GetGroupState(Level: Integer): TGroupPosInds; GetGroupState pode ser particularmente útil para suprimir informações redundantes, ao exibir os dados de um ClientDataSet em um componente de multiplos registros, como o DBGrid. Por exemplo, veja o form principal do nosso projeto. Observer que o campo Aggregate CustomerTotal exibe o valor em todos os registros, embora esteja sendo calculado para cada Cliente separadamente. Não é só redundante como desnecessário, e torna a leitura dos dados mais difícil. Usando GetGroupState você pode testar se o registro em particular é o primeiro registro do grupo, e nesse caso exibir o valor do campo CustomerTotal. Para os registros que não são os primeiros registros do grupo (baseado no índice de CustIdx), você simplesmente omite a apresentação do valor. Determinando o Group state a omissão ou exibição dos dados podem ser controladas através do evento OnGetText do campo CustomerTotal. Abaixo podemos observar um simples exemplo deste evento onGetText: Quando você chamar o GetGroupState, você passa um valor inteiro que indica o nível de agrupamento. Passando o valor 0 (zero) o GetGroupState retornará a posição relativa do registro corrente dentro do dataset completo. Passando o valor 1 retornará o group state do registro corrente respeitando o primeiro campo do índice atual, enquanto que passando o valor 2 retornará o group state do registro corrente respeitando os dois primeiros campos do índice atual, e assim por diante. GetGroupState devolve as flags da classe TGroupPosInd. TGroupPosInd é declarada da seguinte forma: TGroupPosInd = (gbFirst, gbMiddle, gbLast); Imagem12 24 MeGAZINE Delphi procedure TForm1.ClientDataSet1CustomerTotalGetText(Sender: TField; var Text: String; DisplayText: Boolean); begin if gbFirst in ClientDataSet1.GetGroupState(1) then Text := FormatFloat(‘###,##0.00’, Sender.Value) else Text := ‘’; end; Se você também deseja omitir o campo CustNo dentro do grupo, então deve adicionar todos os campos na lista de TFields do ClientDataSet e depois criar o evento onGetText do campo CustNo, onde iremos incluir a seguinte instrução: procedure TForm1.ClientDataSet1CustNoGetText(Sender: TField; var Text: String; DisplayText: Boolean); begin if gbFirst in ClientDataSet1.GetGroupState(1) then Text := Sender.Value else Text := ‘’; end; A Imagem12 mostra o resultado do nosso projeto em execução. Observe que os campos CustNo e CustomerTotal são apresentandos somente no primeiro registro de cada grupo. Conclusão Neste artigo podemos conhecer um pouco dos Aggregates e do GroupState do ClientDataSet e com essas informações espero poder lhes ajudar a criar várias formas de visualização dos dados em seu projeto. Um abraço a todos e até o próximo artigo. Download O projeto de exemplo deste artigo está disponível para download em: http://www.theclub.com.br/revista/download/Aggregates.zip MeGAZINE 25 Delphi Distribuindo um arquivo dentro do executável por André Colavite - [email protected] Nesta matéria iremos descrever como distribuir um determinado arquivo dentro do executável do projeto, pois assim ao distribuir a aplicação levaremos somente o executável para o cliente e o projeto se encarregará de extrair o determinado arquivo quando este for necessário. O determinado arquivo pode ser de qualquer tipo imagem, som, dll, doc ou até outro executável. O processo consiste em transformar o determinado arquivo em um arquivo de recurso .res e depois incluí-lo em nosso projeto. Dentro do projeto criaremos a rotina que permitirá descarregar o arquivo num diretório no momento em que for utilizá-lo. Transformando o arquivo num Res Nos passos seguintes iremos descrever como gerar um arquivo .RES a partir de um arquivo comum, neste caso iremos criar a partir de um arquivo WAV. * No diretório do projeto crie um arquivo texto e coloque a extensão .RC, exemplo SOM.RC. Este arquivo poderá ser criado através do NOTEPAD; * No arquivo .RC criaremos uma linha onde colocaremos informações sobre o arquivo a ser transformado em recurso. Podemos incluir mais de um arquivo como recurso, neste caso iremos criar uma linha para cada arquivo dentro deste .RC; * A linha criada dentro do RC ficará da seguinte forma: 26 SOMWAV WAVE MUSICA.WAV O arquivo .RC tem a seguinte estrutura: SOMWAV é o nome utilizado pelo projeto para achar o recurso; WAVE somente identifica o tipo de arquivo; MUSICA.WAV indica o arquivo a ser transformado em recurso. Caso o arquivo esteja num diretório diferente, teremos que indicar o caminho completo e o nome do arquivo, por exemplo C:\WINDOWS\MUSICA.WAV; O próximo passo será compilar o arquivo .RC gerando um arquivo .RES, e para isso iremos utilizar um compilador que vem junto com o Delphi chamado BRCC32.EXE, que normalmente está no diretório Bin do Delphi. Para executar o compilador abra a janela do DOS vá até o diretório onde se encontra o arquivo .RC e execute a seguinte instrução: BRCC32 SOM.RC Após executar a instrução um arquivo .RES será gerado, contendo o mesmo nome do arquivo .RC.. Pronto, estando com o arquivo .RES criado, o próximo passo será criar o projeto. Trabalhando com o novo arquivo de recurso Nos passos seguintes criaremos um simples projeto contendo duas partes. A primeira parte terá uma procedure para MeGAZINE Delphi descarregar o arquivo de SOM no diretório do executável e a segunda parte irá executar o arquivo de SOM sem descarregar o arquivo no diretório. Crie um novo projeto; Abra a unit do form principal e logo abaixo da instrução {$R *.dfm} inclua uma instrução {$R SOM.RES} Essa instrução indica ao compilador do Delphi que o arquivo SOM.RES será compilado junto ao projeto. Descarregando o arquivo de SOM Para descarregar o arquivo de Som de dentro do executável utilizaremos uma classe chamada TResourceStream. Essa classe permite acesso ao recursos compilados do projeto. Criaremos agora a primeira procedure ao qual irá descarregar o arquivo de Som no diretório do projeto. Veja abaixo a instrução da procedure: procedure DescarregaArquivo; var nArq: string; Res: TResourceStream; Begin nArq := ExtractFilePath(Application.ExeName)+ ’\MUSICA.WAV’; if not FileExists(nArq) then begin Res := TResourceStream.Create (Hinstance, ‘SOMWAV’, ‘WAV’); try Res.SavetoFile(nArq); finally Res.Free; end; end; end; No Form coloque um componente Button e no evento onclick desse Button coloque a instrução para chamar a procedure criada. Como estamos utilizando um arquivo de SOM iremos tocar esse arquivo após descarregá-lo, sendo assim utilizaremos mais uma instrução dentro do evento onClick.Veja a instrução a seguir: sndPlaySound(‘MUSICA.WAV’, snd_ASync); end; Pronto, a primeira parte do projeto já está pronto, vamos testá-lo. Compile o projeto e depois copie o arquivo .exe do projeto para um novo diretório. Em seguida execute o projeto e pressione o botão, neste momento o arquivo será descarregado no diretório do executável e será tocado em seguida. Criando a segunda parte do projeto Nesta segunda parte do projeto, iremos tocar o arquivo de som presente no executável sem descarregá-lo no diretório. Para isso iremos incluir em nosso projeto mais um componente Button, e no evento onClick do novo button coloque a seguinte instrução: procedure TForm1.TocarClick(Sender: TObject); begin if not PlaySound(‘SOMWAV’, Hinstance, SND_RESOURCE or SND_NODEFAULT or SND_SYNC) then ShowMessage(‘Erro ao tocar o SOM’); end; Pronto, agora recompile o seu projeto e faça o mesmo teste realizado anteriormente. Copie o executável para outro diretório e execute o projeto. Pressione o novo botão e veja que o som será tocado sem descarregar o arquivo no diretório. Espero que tenham gostado dessa matéria, um grande abraço a todos e até a próxima. O projeto dessa matéria está disponível para donwload em nosso site: http://www.theclub.com.br/revista/download/RecursoWAV.ZIP procedure TForm1.DescarregaClick(Sender: TObject); begin DescarregaSom; { Toca o arquivo WAV } MeGAZINE Sobre o autor André Colavite Consultor Técnico do The Club [email protected] 27 Dicas & TTruques ruques Treeview – Foco uses Veja neste exemplo como mandar o foco à um ítem do treeview quando clicar no botão de expansão (+): // evento OnMouseDown do Treeview. procedure TForm1.TreeView1MouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var T: TTreeNode; begin // Pega o ítem através das coordenadas do mouse. T := Treeview1.GetNodeAt(X,Y); if T <> nil then Treeview1.Selected := T; RVClass, RVProj, RVCsStd; procedure TForm1.btnChamaRelClick (Sender: TObject); var Pagina: TRavePage; Report: TRaveReport; QualPagina: String; begin // Nome da página dentro do projeto Rave. QualPagina := ‘Page2’; // Abre RvProject. RvProject1.Open; // Pega referência do “Report” // dentro do projeto Rave. Report := RvProject1.ProjMan.ActiveReport; // Pega referência da “Page” dentro do “Report”. Pagina := RvProject1.ProjMan.FindRaveComponent (‘Report1.’+QualPagina,nil) as TRavePage; // Indica a página inicial. Report.FirstPage := Pagina; // Executa o relatório. RvProject1.Execute; end; Rave Report – Indicar página inicial O Rave Reports permite ter várias páginas de relatório (cada página com um layout) para um mesmo relatório. Neste exemplo, iremos demonstrar como definir via programação qual página será apresentada como inicial. 28 end; MeGAZINE Dicas & TTruques ruques dbExpressPlus {$R *.dfm} Na sessão Dicas & Truques da revista de Setembro/2003, publicamos uma dica a respeito da suíte de componentes dbExpressPlus, a qual possui vários componentes adicionais para incrementar a dbExpress. Contudo, logo após a publicação detectamos um problema de instalação da dbExpressPlus com uma outra suíte de componentes bastante utilizada chamada RxLib. O que ocorre é que ambas suítes possuem um componente com a mesma classe, o TSQLScript, e quando a dbExpressPlus vai ser instalada, ocorre um erro de compilação em seu pacote, visto já haver a classe TSQLScript registrada. Uma forma que encontramos para resolver o problema, foi abrir a unit SQLScript (dbExpressPlus) e alterar o nome da classe de TSQLScript para TSQLScriptPlus. Para fazer essa alteração abra o pacote DbExprPlus_r70.dpk e dentro desse pacote dê duplo click sobre o arquivo SQLScript.pas, ao qual será aberto... function GetDesktopListViewHandle: THandle; var S: String; begin Result := FindWindow(‘ProgMan’, nil); Result := GetWindow(Result,GW_CHILD); Result := GetWindow(Result, GW_CHILD); SetLength(S, 40); GetClassName(Result, PChar(S), 39); if PChar(S) <> ‘SysListView32’ then Result := 0; end; procedure TForm1.Button1Click(Sender: TObject); begin SendMessage(GetDesktopListViewHandle, LVM_ARRANGE, 0, 0); end; Estando com o .Pas aberto selecione no menu do Delphi a opção Search / Replace, onde iremos configurá-lo da seguinte forma: Na propriedade “Text to find” indique o TSQLScript Na propriedade “Replace with” indique o novo nome TSQLScriptPlus Depois clique sobre o botão Replace All para alterar todas as palavras TSQLScript pela TSQLScriptPlus automaticamente. O Delphi irá perguntar se deseja alterar, nesse momento click no botão All. Pronto Salve a unit e depois compile essa pacote, lembre-se essa pacote somente será compilado. Agora abra o pacote DbExprPlus_d70.dpk e compile, ele irá indicar que não encontrou a classe TSQLScript, nesse momento altere a linha dentro do SQLPlusEditors.Pas ao qual está mencionando o TSQLScript pelo TSQLScriptPlus e salve o arquivo, volte na janela do pacote DbExprPlus_d70.dpk, compile e instale o pacote. Pronto agora não irá apresentar a mensagem de erro e será instalado corretamente. Cálculo de Parcelas Neste simples exemplo iremos demonstrar como fazer a divisão de um valor em parcelas, fazendo o tratamento da diferença, jogando para a primeira ou última parcela, na figura 1 poderá verificar o layout sugerido para o formulário deste exemplo. Desktop do Windows – auto-arranjar icones Esta dica pode ser útil após a instalação de sua aplicação, onde geralmente é disponibilizado um ícone na área de trabalho do Windows e em muitos casos sendo necessário arranjá-los após a instalação. Figura 1 – Layout sugerido implementation uses CommCtrl; Acompanha agora o código do botão “Gerar”: MeGAZINE 29 Perguntas & Respostas Parcelas[High(Parcelas)] := Parcelas[High(Parcelas)] + (Total - TotParc); procedure TForm1.btnGerarClick(Sender: TObject); var Total, ValorParc, TotParc: Extended; NParc: Integer; Parcelas: Array of Extended; i: Integer; begin { Variáveis auxiliares } Total := 0; ValorParc := 0; TotParc := 0; NParc := 0; ListParcelas.Clear; { mostra parcelas no ListBox } for i := Low(Parcelas) to High(Parcelas) do ListParcelas.Items.Add(FormatFloat(‘###,##0.00’, Parcelas[i])); end; O exemplo referente esta dica está disponível para download em: http://www.theclub.com.br/revista/downloads/ CalcParcelas.zip DataSetProvider – Não carregar “Details” Quando trabalhamos com ClientDataSet/DataSetProvider na manutenção em tabelas relacionadas (master/detail), na maioria dos casos utilizamos campos do tipo TDataSetFields no dataset master, o qual traz o conteúdo da tabela detalhe juntamente com o pacote de dados da master. Contudo, existem situações que por questões de performance por exemplo não queremos que os dados das tabelas detalhes sejam carregados juntamente com os dados da master. Felizmente existe no componente DataSetProvider várias configurações e entre elas o controle dos datasets detalhes. Para que os TDataSetFields não sejam incluídos nos pacotes de dados, faça o seguinte: 1. Configure no DataSetProvider a propriedade Options | poFetchDetailsOnDemand para “True”; 2. Configure no ClientDataSet detalhe (que está ligado ao ClientDataSet master via DataSetField), a propriedade FetchOnDemand para “False”; Pronto, com isso, somente será incluido no pacote de dados, os registros referentes a tabela master. Quando necessitar visualizar os registros detalhes, deverá executar o método FetchDetails do ClientDataSet master: cdsMaster.FetchDetails; { tenta converter o valor do Edit } try Total := StrToFloat(edValor.Text); except ShowMessage(‘Valor Inválido!’); Abort; end; { Verifca número de parcelas } if speParcelas.Value < 2 then Exit else NParc := speParcelas.Value; { Pega somente a parte inteira da divisão } ValorParc := Trunc(Total / NParc); { Ajusta array que guardará o } {valor de cada parcela } SetLength(Parcelas, NParc); { Atribui valor de cada parcela e acumula total } for i := Low(Parcelas) to High(Parcelas) do begin Parcelas[i] := 0; Parcelas[i] := ValorParc; TotParc := TotParc + Parcelas[i]; end; { Verifica em qual parcela será “jogada” a } { diferença } if rg_diferenca.ItemIndex = 0 then Parcelas[Low(Parcelas)] := Parcelas[Low(Parcelas)] + (Total - TotParc) else 30 QRChart no Delphi 7 Alguns associados têm nos consultado a respeito do componente QRChart no Delphi 7, informando não estar encontrando este componente no D7. O que ocorre, é que o QuickReport não vem mais instalado por padrão na IDE do D7, e consequentemente o QRChart também não, visto trabalharem em conjunto. Porém, ambos componentes podem ser adicionados sem problemas, bastando para isso acessar o menu “Components | Install Packages | Add” e adicionar os seguintes pacotes: QuickReport QRChart MeGAZINE -> -> \Delphi7\bin\dclqrt70.bpl \Delphi7\bin\dcltqr70.bpl