setembro 2012 setembro 2012 índice Editorial 04 Artigo 05 Android - Ciclo de vida de uma Atividade Autor: Thiago Cavalheiro 11 FireMonkey - Utilizando mestre/detalhe e formulário pesquisa Autor: Lucas de Oliveira Delphi Postos de Combustível - Automação das Bombas Autor: Victory Fernandes Delphi Android 16 Delphi - Parte VI Autor: Luciano Pimenta 21 Desafio The Club Dicas 28 Legenda 30 Iniciante Intermediário Avançado setembro 2012 03 Bem-vindo Olá amigos do The Club, neste mês de Setembro além da comemoração da Independência do Brasil, estamos comemorando e agradecendo aos queridos associados pela 216ª (Ducentésima décima sexta) edição da Revista The Club, 18 anos desde a primeira edição. A revista técnica de programação mais tradicional do Brasil. Neste mês nosso consultor técnico Lucas de Oliveira nos traz mais informações e dicas de como utilizar Mestre/Detalhe e formulário de Pesquisa com o auxílio do FireMonkey. Já nosso colaborador Luciano Pimenta continua com o curso de Delphi – parte VI sempre com novidades e dicas que servirão de base para o programador iniciante e de aprendizado para o mais experiente. Victory Fernandes nos mostra como funciona a Automação das Bombas em postos de Combustíveis, muito útil para quem pretende implementar em seus sistemas este tipo de funcionalidade. Eu continuo escrevendo artigos relacionados ao Sistema Android, sendo que neste mês descrevo os métodos com exemplos práticos da classe Activity, uma das principais classes para se desenvolver nesta plataforma. A seção de “Dicas Delphi” está como sempre recheada das melhores dicas que com certeza será de grande utilidade. Como sempre, estamos sempre à disposição para sugestões de artigos. Um Forte abraço, Av. Profº Celso Ferreira da Silva, 190 Jd. Europa - Avaré - SP - CEP 18.707-150 Informações e Suporte: (14) 3732-1529 Internet http://www.theclub.com.br Cadastro: [email protected] Suporte: [email protected] Informações: [email protected] Skype Cadastro: theclub_cadastro Skype Suporte: theclub_linha1 theclub_linha2 theclub_linha3 www.twitter.com/theclubbr Copyright The Club 2012 Diretor Técnico Marcos César Silva Diagramação Eduardo Massud Arte Vitor M. Rodrigues Revisão Cíntia Amaral Colunistas Marcos César Silva Thiago Cavalheiro Montebugnoli Lucas de Oliveira Luciano Pimenta Impressão e acabamento: GRIL - Gráfica e Editora Taquarituba-SP - Tel. (14) 3762-1345 Reprodução Thiago Montebugnoli - Editor Chefe [email protected] 04 setembro 2012 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 Megazine” são terminantemente proibidos sem autorização escrita dos titulares dos direitos autorais. Delphi© é marca registrada da Embarcadero Technologies®™, as demais marcas citadas são registradas pelos seus respectivos proprietários. Android Android Ciclo de vida de uma Atividade Olá amigos do The Club, neste artigo estudaremos mais a fundo uma das classes mais importantes do Sistema Android, a Activity (Atividade). Nos artigos anteriores trabalhamos por padrão com apenas uma Atividade e especificamente com o Evento Oncreate(), ou seja, o evento que é executado no momento da criação da classe. Existem diversos eventos que devem ser trabalhados em uma Atividade, como por exemplo o OnStart(), OnExecute(), OnStop() entre outros. Terei como base para escrever este artigo as informações contidas bo site oficial do Sistema Android http://developer.android.com/guide/components/activities.html Conceito de Atividade Uma atividade é um componente de aplicação que fornece uma tela com a qual os usuários podem interagir, a fim de fazer alguma coisa, como discar para um número de telefone, tirar uma foto, enviar um e-mail ou visualizar um mapa. Cada atividade é dada uma janela na qual apresenta uma interface para o usuário. A janela tipicamente preenche a tela podendo ser menor do que a tela ou setembro 2012 05 flutuando acima das outras. Uma aplicação geralmente consiste de múltiplas atividades que estão ligadas entre si. Cada atividade pode, então, iniciar outra atividade, a fim de executar ações diferentes. Quando uma atividade é interrompida porque uma nova atividade é iniciada, ela é notificada dessa mudança de estado através de métodos da atividade de retorno de chamada do ciclo de vida. Existem vários métodos de retorno que uma atividade pode receber que estaremos aprendendo ao decorrer do artigo. Entendendo o ciclo de uma Atividade A Imagem 01 nos proporciona uma idéia geral do funcionamento de uma atividade, vejamos todas as etapas detalhadamente a seguir. Chamado quando uma atividade parou por algum motivo. Executando o OnStart() de uma forma automática. OnResume() Chamado apenas antes da atividade começar a interagir com o usuário. Neste ponto, a atividade está no topo da pilha de atividades. OnPause() Chamado quando o sistema está prestes a começar a retomar outra atividade. OnStop() Este método é chamado quando a atividade está sendo encerrada. OnDestroy() Método responsável por liberar da memória a atividade, sendo a última chamada que a mesma irá receber. Em conjunto, estes métodos definem o ciclo de vida completo de uma atividade. Ao implementar esses métodos, você pode monitorar três loops aninhados no seu ciclo de vida: • A vida inteira de uma atividade acontece entre a chamada para onCreate() e a chamada para OnDestroy(). Sua atividade deve realizar a configuração de estado “global” (como a definição de layout) no onCreate(), e liberar todos os recursos restantes no onDestroy(). Exemplo: se sua atividade tem um segmento em execução em segundo plano para fazer download de dados a partir da rede, pode criar esse segmento no onCreate() e, em seguida, parar o segmento no evento onDestroy(). • O tempo de vida visível de uma atividade acontece entre a chamada para OnStart() e a chamada para onStop(). Durante este tempo, o usuário pode ver e interagir com ela. Por exemplo, o onStop() é chamado quando uma nova atividade começa e esta não é mais visível. Entre estes dois métodos, você pode manter os recursos que são necessários para mostrar a atividade para o usuário. Figura 01: Ciclo de Vida de uma Atividade. O ciclo de vida de uma atividade possui os seguintes métodos: OnCreate() O primeiro evento utilizado quando iniciamos a Atividade, no momento de sua criação. Este método é passado um objeto do tipo “Bundle” contendo o estado anterior de uma atividade, sendo sempre seguido pelo onStart(). • O tempo de vida de primeiro plano de uma atividade acontece entre a chamada do método onResume() e a chamada para o método OnPause(). Durante este tempo, a atividade está na frente de todas as outras atividades na tela e tem o foco de entrada do usuário. Uma atividade pode fazer freqüentemente a transição de dentro e fora do plano. Implementando os métodos de uma forma prática Para isto abra seu Eclipse e clique em “File/New/Android Project” e crie um projeto em Android, recomendo a criação na versão 2.2 ou 2.3. Navegue até a atividade principal, no meu caso seria o caminho: “CiclodeVida\src\pct. CiclodeVida\CiclodeVidaActivity” e implemente os métodos criados anteriormente. Ver código completo a seguir. OnStart() Executado quando a atividade está visível para o usuário. Utiliza-se o onResume() se a atividade vem para o primeiro plano, ou o onStop() quando não está visível. OnRestart() 06 package pct.CiclodeVida; import android.app.Activity; import android.os.Bundle; public class CiclodeVidaActivity extends setembro 2012 Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); } @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); } @Override protected void onStop() { super.onStop(); } @Override protected void onDestroy() { super.onDestroy(); } } Usaremos o LogCat, que nada mais é que uma janela que nos detalha de uma forma prática todos as tarefas executadas no sistema Android, para isto clique no menu “Window/Show View/Other” e selecione o item “LogCat”. Ver Imagem 02. Codificaremos o exemplo criado anteriormente para facilitar a identificação dos métodos. Utilizaremos o método Log.i() que possui dois parâmetros do tipo texto, o primeiro seria a TAG para identificação da mensagem informativa e o segundo a própria mensagem que será exibida. Passaremos o nome de nossa Atividade e o método que está sendo executado respectivamente. Ver código completo a seguir. package pct.CiclodeVida; Figura 02: Selecionado o Item LogCat. import android.os.Bundle; import android.util.Log; public class CiclodeVidaActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.i(“CiclodeVida”,”Método OnCreate()”); } @Override protected void onStart() { super.onStart(); Log.i(“CiclodeVida”,”Método OnStart()”); } import android.app.Activity; setembro 2012 07 @Override protected void onResume() { super.onResume(); Log.i(“CiclodeVida”,”Método OnResume()”); } @Override protected void onPause() { super.onPause(); Log.i(“CiclodeVida”,”Método OnPause()”); } @Override protected void onStop() { super.onStop(); Log.i(“CiclodeVida”,”Método OnStop()”); } Figura 04: Clicando no Botão Home. Ver Imagem 04 e 05. Para simular o evento OnDestroy() devemos fechar a aplicação teclando o ESC e abri-la novamente. @Override protected void onDestroy() { super.onDestroy(); Log.i(“CiclodeVida”,”Método OnDestroy()”); } } Ver Figura 06. Ao executarmos o exemplo podemos conferir as etapas da criação e execução da Atividade. A Imagem 03 nos dá uma noção quando iniciamos a aplicação. Clicando no botão “home” e ao voltarmos na mesma tela principal podemos conferir a execução dos eventos OnPause() e OnStop(). Figura 05: Evento OnPause() e OnStop(). Desta forma como foi explicada e aplicada a um exemplo prático fica fácil o entendimento do ciclo de vida de uma atividade. Criando um exemplo com múltiplas atividades Figura 03: Evento OnCreate(), OnStart() e OnResume(). 08 setembro 2012 Para entendermos melhor montarei um exemplo utilizando mais de uma atividade, sendo que cada tela possuirá a sua correspondente. Focaremos em um exemplo simples, no qual a tela principal possuirá um botão que chama a tela secundária. Crie um novo projeto em Android (como foi criado nos artigos anteriores) e adicione um botão na tela principal. Na tela secundária adicione outro para voltar no início. setContentView(R.layout.secundaria); } } O segundo passo seria registrar a nova atividade criada anteriormente no arquivo “AndroidManifest.xml”, para que possamos posteriormente trabalhar com ela. Podemos fazer isto utilizando a IDE do Android, em “Application Nodes” clicando no botão “Add...” para criar a atividade desejada. Ver Imagem 08 e 09. Figura 06: Evento OnDestroy(). Figura 08: AndroidManifest. Figura 07: Lay-Out do exemplo. O Lay-out deverá ficar idêntico ao da Imagem 07. A maneira para criarmos uma atividade é muito simples, clique com o botão direito no caminho “src\pct.VariasAtividades” e escolha “New/Class”. Uma atividade nada mais é que uma classe que herda da classe Activity. Devemos utilizar o “extends” para indicar que a classe Secundaria está sendo herdada da classe Activity, ver código em seguida. package pct.VariasAtividades; import android.app.Activity; public class Secundaria extends Activity { @Override public void onCreate(Bundle savedInstanceState) Figura 09: Criando uma Atividade. Depois de efetuar as etapas abordadas anteriormente clique sobre a atividade e em “Atributes for Secundaria(Activity)” no campo “Name” Escolha a Atividade “Secundaria.java” como referência. Ver Imagem 10. E o código XML correspondente ficou da seguinte maneira: { super. onCreate(savedInstanceState); <activity android:name=”Secundaria”> </ activity> setembro 2012 09 } } }); } Código referente à Tela Secundária package pct.VariasAtividades; import import import import import Figura 10: Atribuindo à classe “Secundaria.java”. Codificando o Projeto de Exemplo A tela principal é aquela que servirá como base para chamar a Secundária. Programe no evento OnClick do botão o comando StartActivity() que tem como parâmetro o nome da Atividade principal seguido do nome da classe secundária, a qual devemos invocar. Devemos seguir a mesma lógica para programar o botão voltar na tela Secundária. Veja a seguir o código completo da aplicação. Código referente à Tela Principal package pct.VariasAtividades; import import import import import android.app.Activity; android.content.Intent; android.os.Bundle; android.view.View; android.widget.Button; public class VariasAtividadesActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btConsultar = (Button) findViewById(R.id.button1); btConsultar.setOnClickListener(new View. OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(VariasAtividadesActivity. this,Secundaria.class)); 10 setembro 2012 android.app.Activity; android.content.Intent; android.os.Bundle; android.view.View; android.widget.Button; public class Secundaria extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.secundaria); Button btConsultar = (Button) findViewById(R.id.button1); btConsultar.setOnClickListener(new View. OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(Secundaria. this,VariasAtividadesActivity.class)); } }); } } Conclusão Vimos neste artigo o ciclo de vida da principal classe do Sistema Android, as denominadas Atividades. Estas classes possuem vários métodos importantes e necessários para o bom funcionamento do sistema. Neste caso abordamos de uma forma prática e rápida aplicando em um exemplo simples e não esquecendo de sempre manter o foco no aprendizado. Sobre o autor Thiago Cavalheiro Montebugnoli Thiago Cavalheiro Montebugnoli é tecnólogo, formado pela Faculdade de Tecnologia de Botucatu – SP (FATEC) foi consultor técnico do The Club, já desenvolveu softwares utilizando a plataforma .NET, Delphi junto com Banco de Dados SQL Server e Firebird. Atualmente trabalha no Centro de Processamento de Dados da Prefeitura Municipal de Itaí-SP. Possui as seguintes certificações: MCP - Microsoft Certified Professional, MCTS - Microsoft Certified Technology Specialist, MCAD - Microsoft Certified Application Developer e MCSD - Microsoft Certified Solution Developer. [email protected] Fire Monkey Utilizando mestre/ detalhe e formulário pesquisa Olá pessoal, neste mês eu vou aprofundar um pouco mais no assunto FireMonkey e acesso a banco de dados. O exemplo que utilizarei irá abordar vários temas que envolvem diariamente a rotina de desenvolvimento de uma aplicação com acesso a banco de dados na já tradicional VCL, porém utilizaremos a plataforma FireMonkey. O principal foco será o formulário mestre/detalhe, que é inevitável para exibir os dados de um banco devidamente normalizado. Além do relacionamento mestre/detalhe, irei demonstrar uma forma simples de como utilizar um formulário de pesquisa padrão para pesquisar pelo nome de um registro e gravar a sua chave de identificação, é a mesma função de um DBLookUpComboBox, porém com a opção de pesquisar ao invés de apenas listar todos os registros de uma só vez. No exemplo, será abordada uma das mais clássicas formas de se explicar um formulário mestre/detalhe, com uma tabela de vendas e uma de itens de venda. E para completar o exemplo criaremos uma tabela de clientes para ser vinculada a venda e uma de produtos para ser vinculada a tabela de itens de venda. Explicado o exemplo, vamos agora aos procedimentos para construí-lo. Criação do banco de dados utilizado no exemplo Na listagem 1 segue o banco de dados nomeado “FIREMONKEY” que será criado para ser utilizado no exemplo: CREATE TABLE CLIENTES ( ID_CLIENTE INTEGER NOT NULL, NOME VARCHAR (50) CHARACTER SET NONE COLLATE NONE, CPF VARCHAR (15) CHARACTER SET NONE COLLATE NONE, PRIMARY KEY (ID_CLIENTE) ); CREATE GENERATOR GEN_ID_CLIENTE; CREATE TABLE PRODUTOS ( ID_PRODUTO INTEGER NOT NULL, setembro 2012 11 DESCRICAO VARCHAR (80) CHARACTER SET NONE COLLATE NONE, VALOR_CUSTO NUMERIC (15, 2), VALOR_VENDA NUMERIC (15, 2), PRIMARY KEY (ID_PRODUTO) ); CREATE GENERATOR GEN_ID_PRODUTO; CREATE TABLE VENDAS ( ID_VENDA INTEGER NOT NULL, DATA DATE, VALOR_TOTAL NUMERIC (15, 2), ID_CLIENTE INTEGER, PRIMARY KEY (ID_VENDA) ); seguida clique em Modify Connection, e é só configurar a localização do banco de dados também nomeado como FIREMONKEY.GDB. Bom, até aqui temos uma conexão estabelecida com a base de dados, basta agora adicioná-la no DM (Data Module), é só arrastar a conexão FIREMONKEY para o nosso DM e pronto, é adicionado automaticamente um componente TSQLConnection já configurado com a conexão FIREMONKEY. Uma boa dica, é criar um arquivo de configuração para a conexão, é bem simples, dê um duplo clique na propriedade Params do componente de conexão FIREMONKEY, em seguida clique no botão “Code Editor...” e copie os parâmetros da conexão. Abra um bloco de notas e na primeira linha escreva semas aspas “[FIREMONKEY]” e logo abaixo cole os parâmetros de conexão do projeto. Por fim salve o arquivo com o nome Connect.ini na pasta do executável do projeto. Agora basta colocar o código da listagem 2 no evento onBeforeConnect da conexão FIREMONKEY. CREATE GENERATOR GEN_ID_VENDA; CREATE TABLE ITENS_VENDA ( ID_ITEM_VENDA INTEGER NOT NULL, ID_PRODUTO INTEGER, QTDE INTEGER, ID_VENDA INTEGER, TOTAL_ITEM NUMERIC (15, 2), VALOR_ITEM NUMERIC (15, 2), PRIMARY KEY (ID_ITEM_VENDA) ); CREATE GENERATOR GEN_ID_ITEM_VENDA; Listagem 1 – Banco utilizado de exemplo Podemos ver que utilizaremos generators, as variáveis do banco de dados, para gerar o auto-incremento das chaves primárias de todas as tabelas, porém este auto-incremento será realizado pela aplicação, para melhorar o desempenho do formulário mestre/detalhe. Mas vamos por partes, para não nos confundirmos com o exemplo. Criando o projeto FireMonkey HD Aplication - Deplhi procedure TDM. FIREMONKEYBeforeConnect(Sender: TObject); begin FIREMONKEY. LoadParamsFromIniFile(‘Connect.ini’); end; Listagem 2 – lendo parâmetros de conexão de arquivo ini Desta forma fica mais fácil distribuir a sua aplicação, basta o arquivo Connect.ini estar na mesma pasta do executável e indicar o caminho completo do banco de dados no parâmetro Database do arquivo de configuração. Configurando os componentes de acesso a dados Vamos agora acessar as tabelas do banco de dados. Adicione ao DM, 4 SQLDataSet (dbExpress), 3 DataSetProvider (DataAccess), 4 ClientDataSet (DataAccess) e 1 DataSource (DataAccess). Ligue os SQLDataSet com o componente de conexão, e nomeie-os da seguinte maneira, sdsClientes, sdsProdutos, sdsVendas e sdsItensVenda, na propriedade CommandText de cada um siga as consultas da tabela 1 para preencher cada um dos componentes. Após criarmos o banco de dados iremos criar um novo projeto Firemonkey HD Aplication – Delphi, no Delphi XE2. Salve o projeto com o nome “FireMonkey MestreDetalhe”, salve a primeira unit como unPrincipal, esta fará a chamada dos outros formulários, porém ainda não temos o que configurar nela, apenas coloque o nome de frmPrincipal. Vamos criar agora um módulo onde ficará a conexão da aplicação com o banco de dados, para criar basta seguir o menu (New/Other/Delphi projects/ Delphi Files/DataModule), salve a unit do data module como unDm e coloque seu nome de DM, na paleta Data Explorer, localizada ao lado direito da tela, crie uma nova conexão com o banco de dados FireBird, que é o servidor de banco de dados utilizado neste exemplo, o nome da conexão utilizado neste exemplo é FIREMONKEY, depois de criada a conexão FIREMONKEY, agora devemos informar o caminho do banco de dados, basta abrirmos o nó FIREBIRD no Data Explorer e clicar com o botão direito do mouse em FIREMONKEY, em 12 setembro 2012 Tabela 1 – Consultas SQL para acessar as tabelas do banco de dados Nos componentes DataSetProvider dê os nomes da seguinte maneira, dspClientes, dspProdutos e dspVendas, não terá um DataSetProvider para Itens de vendas por que este é o detalhe da venda, já explicarei como fazer a ligação. Ainda configurando os DataSetProvider vamos deixar a propriedade UpDateMode para upWhereKeyOnly, abra o nó da propriedade Options e deixe True a opção poAllowCommandText. Agora nos resta configurar os componentes ClientDataSet, nomeie-os como cdsClientes, cdsProdutos, cdsVendas e cdsItensVenda. Nos três primeiros pode configurar as suas propriedades ProviderName para os seus respectivos DataSetProviders. Lembrando que o relacionamento mestre/detalhe do exemplo será a tabela de vendas com a de itens de venda. Agora vamos carregar os TFields dos componentes SQLDataSet, deixando de lado por enquanto o sdsItensVenda, basta carregar os TFields e configurá-los lembrando de deixar as ProviderFlags dos campos chaves (pfInUpdate, pfInWhere e pfInKey = True e Required = False) e os demais campos das tabelas (pfinUpdate = True e pfinWhere e pfInKey = False). Como vimos na SQL do sdsItensVenda, passa-se um parâmetro chamado ID_VENDA, para chamar os TFields deste componente primeiro precisamos ligar ele ao sdsVendas, pois ele irá utilizar o mesmo DataSetProvider, aí surge a necessidade do componente DataSource, este fará o vínculo do sdsItensVenda ao sdsVendas. Coloque o nome do DataSource de dsVendas e na sua propriedade DataSet preencha sdsVendas. Nas propriedades do sdsItensVenda, preencha em DataSource o nome dsVendas. Os dois componentes já estão vinculados, basta agora carregarmos os TFields do sdsItensVenda, caso dê algum erro ao carregar os campos basta ativar o componentes sdsVendas marcando True na sua propriedade Active. Não se esqueça de configurar os TFields do sdsItensVenda também, da mesma f0rma como foram configurados os demais. Nos componentes cdsClientes e cdsProdutos basta carregar os Tfields Também. Faça o mesmo com o cdsVendas, e note que surgirá um campo chamado sdsItensVenda, não mecha em nenhuma configuração deste campo ele servirá para ligar o cdsItensVenda com o DataSetProvider. SDS : TSQLDataSet; begin SDS := TSQLDataSet.Create(nil); with SDS do begin SQLConnection := Con; Close; CommandText := ‘SELECT GEN_Id(‘ + Gen + ‘, 1) AS CODIGO FROM RDB$DATABASE’; Open; Result := SDS.FieldByName(‘CODIGO’). AsInteger; Close; Free; end; end; // EXEMPLO DE UTILIZAÇÃO procedure TDM. cdsClientesBeforePost(DataSet: TDataSet); begin if (DataSet.State = dsInsert) then cdsClientesID_CLIENTE.AsInteger := AutoInc(‘GEN_ID_CLIENTE’, FIREMONKEY); end; Listagem 3 – Função AutoInc Esta função garantirá que ao criar um item de venda, o seu campo ID_VENDA seja preenchido com a chave primária da venda em foco no momento, amarrando assim o item a venda. Esta função deve ser no evento BeforePost Vamos então a esta ligação, na propriedade DataSetField do cdsItensVenda selecione o campo cdsVendassdsItensvenda. Agora é só carregar os TFields deste componente. Veja como ficaram as ligações dos componentes de acesso aos dados na figura 1. Caso ainda tenha ficado alguma dúvida sobre a relação de cada componente, você pode dar uma lida no artigo “Dbexpress - Criando um relacionamento Master/Detalhe” do mês de agosto de 2003 da revista TheClub, este artigo está bem completo sobre este tema e está disponível na sessão de revistas do site http://www.theclub.com.br. Como foi dito anteriormente para o formulário mestre/detalhe, o auto-incremento será feito pela aplicação via código. Será criado uma função que recebe como parâmetro o nome do generator e o nome da conexão, para retornar um número inteiro, que será o valor da chave já incrementado. Segue na listagem 3 o corpo da função AutoInc e um exemplo de sua utilização. // CORPO DA FUNÇÃO AUTOINC function TDM.AutoInc(Gen: String; Con: TSQLConnection): Integer; var Figura 1 – Interligações entre os componentes de acessos a dados setembro 2012 13 dos ClientDataSet, fazendo com que os campos chaves recebam o resultado da função. Criando as telas de cadastro No artigo “Criando aplicações com Firemonkey e acesso a banco de dados” do mês de agosto de 2012 da revista TheClub, foi descrito como fazer as ligações dos componentes visuais comuns com os componentes de acesso a dados através de expressões de ligações, ou LiveBindings. Bom, vamos refrescar um pouco a memória. Vou descrever como fazer a tela de vendas, e a tela de clientes e produtos segue o mesmo padrão. Crie um novo form através do menu (File/New/FireMonkey HD Form - Delphi), salve-o como unVendas e dê o nome de frmVendas. Adicione ao formulário um componente DataSource (DataAccess) dê o nome de dsVendas , através do menu (File/Use Unit) selecione a unDm, para poder visualizar os componentes de acesso a dados, na propriedade DataSet do dsVendas localize e selecione o DM.cdsVendas. No evento onCreate do frmVendas abra o cdsVendas através do método open e no evento onClose feche o cdsVendas através do método close. Agora vamos adicionar no frmVendas um componente BindScopeDB (LiveBindings) coloque seu nome como BindScopeDBVendas, este fará a ligação do DataSource com as LiveBindings, selecione dsVendas em sua propriedade DataSource, mas tome o cuidado de não selecionar o DM.dsvendas, este que serve apenas para ligar o componente sdsItensVenda ao sdsVendas. Adicione um BindingList (LiveBindings), este apenas centralizará todas as expressões de ligações do formulário. Agora vamos à lista de componentes visuais para o cadastro de venda, 1 BindNavigator (LiveBindings), 4 Edit (Standard) os nomes serão edtIdVenda (Enabled = False), edtData, edtIdCliente e edtClienteNome (Enabled = False), 1 StringGrid (Grids) de nome sgItensVenda, 1 Label (Standard) de nome lblTotalVenda e a fonte em negrito e para finalizar a lista de componentes 5 SpeedButtons (Additional) com os nomes sbLocalizarVenda, sbIncluirItem, sbEditarItem, sbExcluirItem e sbPesqCliente. Figura 3 – Selecionando o field a ser ligado ao componente visual e liga-lo ao DM.cdsItensVenda pela sua propriedade DataSet. Adicione também um BindScopeDB (LiveBindings) e nomeie-o como BindScopeDBItens e selecione em sua propriedade DataSource o dsItensVenda. Agora sim vamos configurar a propriedade LiveBindings da grid sgItensVenda. Selecione a sgItensVenda e clique na propriedade LiveBindings, porém agora selecione “New LiveBinding...” a na próxima janela escolha TBindDBGridLink (ver figura 4). Ainda nas propriedades da sgItensVenda abra o nó LiveBindings e na propriedade DataSource selecione BindScopeItens. Selecione o componente edtIdVenda e clique na propriedade LiveBindings, selecione a opção “Link to DB Field...” (ver figura 2). Na próxima janela seleciona o Field que deseja ligar ao edtIdVenda (ver figura 3), neste caso seria o ID_VENDA. Repita este processo aos outros componentes, edtData, edtIdCliente, edtClienteNome e lblTotalVenda, selecionando os devidos campos aos seus componentes visuais. Figura 4 – Criando uma ligação para a grid sgItensVenda Figura 2 – Selecionando um link direto a um Field Pronto caso queria configurar os campos que irão aparecer na grid basta dar um clique duplo na propriedade columns e adicionar as colunas e configurar a largura de cada uma. No componente BindNavigator, selecione em sua propriedade BindScope o nome BindScopeDBVendas. Agora falta configurarmos a grid sgItensVenda, porém como sabemos ela é o detalhe da venda, ou seja, é outro escopo de ligação. As ligações dos componentes visuais estão prontas, e que resta agora é fazer a devida codificação para tornar esta tela de vendas funcional. Como disse antes os formulários de cadastro de clientes e de produtos terão que estar funcionando para que se possam adicionar os seus registros tanto na venda como nos itens da venda. Vamos adicionar outro DataSource (DataAccess) de nome dsItensVenda Basta seguir estes passos para fazer as ligações dos componentes visuais 14 setembro 2012 Figura 7 – Sugestão de layout para a tela de cadastro de itens de venda venda ao clicar no botão de pesquisa de clientes, e localizar o cliente correto na tela de pesquisa, basta dar um ok para fechar a tela de pesquisa e o cliente já ficar vinculado à venda em questão. Figura 5 – Sugestão de layout do formulário de vendas Lembrando que todos os artigos da revista The Club citados aqui como referência, estão disponíveis na sessão de revista do site http://www.theclub. com.br. Conclusão Como foi dito logo acima, ainda não terminamos o exemplo, pois está faltando algumas funcionalidades além é claro da manipulação do detalhe da venda. Porém, até aqui vimos como criar um relacionamento mestre/detalhe com o banco de dados utilizado de exemplo e vimos também como criar as telas de cadastros para o projeto. Figura 6 – Exemplo de layout do formulário de clientes e produtos com o cdsClientes e o cdsProdutos. Nas figuras 5 e 6 segue exemplos de layout dos formulários. Lembrando que o campo chave será gerado a cada novo registro sem a preocupação do usuário preencher, por isso o componente que faz ligação ao ID_CLIENTE está desabilitado. Agora teremos que criar uma tela para manipular os registros dos itens das vendas, através do menu (File/New/FireMonkey HD Form - Delphi), salve-o com o nome de unItensVenda e chame o formulário de frmItensVenda, adicione 5 Edit (Standard) com os nomes edtIdProduto, edtProdutoNome (Enabled = false), edtQtde, edtValorUnitario e edtValorTotal, e mais 3 SpeedButton (Additional) com os nomes sbPesqProduto, sbCancelar e sbConfirmar. Repita os mesmos processos de ligação aos componentes de acesso a dados descritos anteriormente para os componentes visuais desta tela. Na segunda parte do artigo será mostrado como criar uma tele de pesquisa padrão no FireMonkey, além de mostrar como utilizar esta tela para implementar pesquisas simples e pesquisas para vincular um registro a tela de vendas, e por fim mostraremos também como manipular o detalhe da venda, neste caso os itens da venda. Espero que tenham gostado do que viram até aqui e que tenha sido de alguma utilidade à vocês leitores. Um grande abraço a todos e até a segunda parte do artigo. Sobre o autor Lucas Vieira de Oliveira Consultor Técnico The Club. Veja na figura 7 o exemplo da tela de cadastro de itens. Ainda não fizemos nenhuma programação em nenhum dos formulários, e por se tratar de uma tela de vendas é evidente que exigirá alguns cálculos dos valores dos registros, porém, o que ainda está faltando para o nosso exemplo ficar bem funcional é criarmos uma tela de pesquisa padrão. [email protected] Para possibilitar buscas de registros em cada tela, por exemplo, na tela de setembro 2012 15 Postos de Combustível Automação das Bombas O processo de automação de postos de combustível envolve diversas etapas, tais como automação da loja de conveniências, automação da pista, automação da retaguarda, controle de tanques de armazenamento e bombas de combustível.Neste artigo são apresentadas informações relevantes a respeito do desenvolvimento de um sistema de automação de bombas de combustível. Através do qual é possível monitorar toda a atividade das bombas, bem como intervir remotamente nas mesmas em tempo real, efetuando presets de abastecimento em dinheiro e/ou volume, bloqueando e/ou liberando abastecimentos, alterando preços dos produtos, dentre outras funcionalidades. Componentes do Sistema Tipicamente, um sistema de bombas de combustível automatizado segue um esquema semelhante ao apresentado na Figura 01. Nele, vemos que as bombas de combustível são ligadas ao Concentrador 16 setembro 2012 Universal de Bombas – CUB. O CUB é o equipamento responsável por centralizar as informações e dados enviados pelas bombas e gerenciar a comunicação entre o servidor da aplicação e a pista. Neste exemplo, apresentamos as funcionalidades do CUB do fabricante Gilbarco. O CUB é peça central no desenvolvimento de sistemas de automação de bombas de combustível e por vezes constitui fator restritivo ao maior número de postos automatizados, principalmente devido ao seu relativo alto preço que, quando associado aos custos de compra e manutenção dos sistemas de software necessários, podem invialibilizar este tipo de aplicação em postos de menor porte. O CUB é ligado, via RS232, a um servidor de aplicação, de preferência dedicado somente a esta função, onde rodam o software de configuração do CUB distribuído pelo fabricante e o software de monitoria de bombas desenvolvido pela software house. Alguns cuidados devem ser tomados ao realizar este tipo de automação sob pena de prejuízos e/ou problemas sérios no correto andamento das atividades na pista, como é o caso, por exemplo, da importância de se ter um servidor de aplicação dedicado. Uma vez configuradas as bombas para trabalhar sob supervisão do CUB, caso o servidor de aplicação trave e/ou caia por algum motivo, todas as operações da pista são interrompidas até que o sistema seja regularizado ou que as bombas sejam re-configuradas para operar de forma não supervisionada. O sistema pode também ter acesso via Internet, caso a software house disponibilize algum tipo de acesso remoto às informações gerencias do posto. Tipicamente se faz necessário desenvolver alguma interface deste tipo, pois a maioria dos proprietários de postos de combustível os administra remotamente, principalmente no caso específico de postos de estrada e cidades do interior. Figura 02: Simulador de Bombas de Combustível da Gilbarco (PumpSim. exe) Figura 01: Esquema típico de sistema de automação de bombas de combustível Ambiente de Desenvolvimento Para iniciar o desenvolvimento de uma aplicação de automação de bombas de combustível, deve-se fazer o download do Software Development Kit - SDK do fabricante. No caso da Gilbarco, o SDK é composto por um simulador de bombas de combustível, uma ferramenta de configuração do CUB e um simulador do CUB. Para iniciar o desenvolvimento, é necessário instalar, configurar e executar o simulador de bombas de combustível (PumpSim.exe), mostrado na Figura 02, em um computador que deve ser ligado, via RS232, ou computador de desenvolvimento. O simulador será responsável por enviar informações ao computador de desenvolvimento, permitindo que o programador simule todas as operações que podem ser executadas pelo frentista em uma bomba de combustível real. Após executar o simulador no computador auxiliar, deve-se executar a ferramenta de configuração do CUB (CUBConfig.exe) no computador de desenvolvimento, conforme mostrado na Figura 03. Esta ferramenta permite configurar um CUB real ou um CUB Simulado, indicando dentre outras coisas, quantas bombas de combustível estão ligadas ao mesmo. Uma vez configurado para o modo CUB Simulado, deve-se executar o software Simulador de CUB (CubDemo.exe) na máquina de desenvolvimento. Surgirá então uma tela tipo MS-DOS conforme mostrado na Figura 04, iniciando assim a comunicação entre o servidor simulado de bombas de combustível (PumpSim.exe) e o simulador de CUB (CubDemo.exe). Figura 03: Ferramenta de configuração do CUB da Gilbarco (CUBConfig.exe) Quando o simulador de CUB é executado, surge no canto inferior direito da tela, um ícone para cada bomba, conforme configuração feita na ferramenta de configuração de CUB (CUBConfig.exe). Inicialmente, todas as bombas aparecem marcadas com um “X”, e apresentadas no estado “Fora do Ar”. À medida que vai sendo estabelecida a comunicação entre o servidor simulado de bombas de combustível (PumpSim.exe) e o simulador de CUB (CubDemo.exe), as bombas mudam de estado, conforme mostrado na Figura 05. Os estados das bombas podem ser vistos passando o mouse sobre cada um dos respectivos ícones da barra do Windows. Internamente na aplicação de automação os estados constituem constantes retornadas pelo CUB, e devem ser declaradas conforme segue: setembro 2012 17 através das funções disponibilizadas pelo fabricante, presentes na CUB32.dll. Segue a lista das funções e suas respectivas declarações: Figura 04: Simulador de CUB da Gilbarco (CubDemo.exe). Figura 05: Ícones das bombas criados pelo Simulador de CUB da Gilbarco (CubDemo.exe). //--- Declaração de Constantes Globais de Estado das Bombas --const SFORADOAR = ‘0’; const SDISPONIVEL = ‘1’; const SABASTECENDO = ‘2’; const sFIMDEVENDA = ‘3’; const SBICOFORA = ‘5’; const SBOMBAFECHADA = ‘6’; const SPAUSA = ‘8’; const SDESCONHECIDO = ‘7’; const SAUTORIZADA = ‘9’; Faz-se necessário que o desenvolvedor crie uma representação icônica para cada um dos estados que podem ser assumidos pela bomba dentro da aplicação, de forma a facilitar a representação dos mesmos para o usuário final. Na Figura 06 apresentamos uma legenda completa dos estados, o momento em que os mesmos ocorrem, bem como uma representação icônica proposta. Figura 06: Legendas de estados assumidos pelas bombas. Comunicação com o CUB A comunicação entre o CUB e aplicação de automação desenvolvida é feita 18 setembro 2012 //--- Declaração de Funções disponíveis em CUB32.dll --function CUBAbrirBomba(NroBomba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBLerEstado(NroBomba: LongInt; Estado: Pointer): LongInt; stdcall; external ‘CUB32.dll’; function CUBAutorizarBomba(NroBomba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBDesautorizarBomba(NroBomba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBFecharBomba(NroBomba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBContinuarAbastecimento(NroBom ba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBSetarNivelDePreco(NroBomba: LongInt; NivelDePreco: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBLerRTM(NroBomba: LongInt; var RealTimeMoney: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBConfirmarLeituraVenda(NroBom ba: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBDescarregarDll: LongInt; stdcall; external ‘CUB32.dll’; function CUBProgramarPreco(NroProduto: LongInt; NivelDePreco: LongInt; NovoPreco: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBMapearProduto(NroBomba: LongInt; NroBico: LongInt; NroProduto: LongInt ) : LongInt; stdcall; external ‘CUB32.dll’; function CUBAutorizarBico(NroBomba: LongInt; NivelDePreco: LongInt; NroBico: LongInt ): LongInt; stdcall; external ‘CUB32.dll’; function CUBLerTotais(NroBomba: LongInt; NroProduto: LongInt; NivelDePreco: LongInt; EncerranteLitros: Pointer; EncerranteDinheiro: Pointer; var Preco: LongInt): LongInt; stdcall; external ‘CUB32.dll’; function CUBLerVenda(NroBomba: LongInt; var NroProduto: LongInt; var NivelDePreco: LongInt; var Mililitros: LongInt; var Dinheiro: LongInt; var Preco: LongInt ): LongInt; stdcall; external ‘CUB32.dll’; function CUBPresetLitros(NroBomba: LongInt; NivelDePreco: LongInt; NroBico: LongInt; CentiLitrosMaximo: LongInt ): LongInt; stdcall; external ‘CUB32.dll’; function CUBPresetDinheiro(NroBomba: LongInt; NivelDePreco: LongInt; ValorMaximo: LongInt ): LongInt; stdcall; external ‘CUB32.dll’; function CUBLerPaginaTag( NroTag : LongInt; NroPagina: LongInt; DataBuffer: Pointer) : LongInt; stdcall; external ‘CUB32.dll’; Dentre as funções da CUB32.dll destacam-se: Ler Estado da Bomba (CUBLerEstado): Esta função retorna o estado atual de todas as bombas ou de uma bomba específica. function CUBLerEstado(NroBomba: LongInt; Estado: Pointer): LongInt; stdcall; external ‘CUB32.dll’; Parâmetros: • long NroBomba - {recebe valor de de 0 a 99} • char *Estado - {Buffer alocado pelo PDV onde será informado o estado solicitado} Retorno: • 0 = O CUB Server recebeu a ordem corretamente. • 1 = Erro nos parâmetros. O número de bomba é incorreto. • 2 = Erro de comunicação entre a dll e o servidor. • Outro = Código de erro retornado pelo Windows. Ver winsock2.h ou winerror.h. Nota: • Se NroBomba = 0, será informado o estado de todas as bombas, sendo cada byte o estado de uma delas. • Se NroBomba <> 0, só será informado o estado da bomba solicitada. Leitura de Venda das Bombas (CUBLerVenda): Esta função deve ser utilizada quando a bomba está em estado de “FIMDEVENDA” (após o fim de um abastecimento na pista). Após ter chamado esta função e gravados os dados na base de dados do software de automação de bombas, deve ser chamada a função CUBConfirmarLeituraVenda para que a bomba fique livre para um novo abastecimento. function CUBLerVenda(NroBomba: LongInt; var NroProduto: LongInt; var NivelDePreco: LongInt; var Mililitros: LongInt; var Dinheiro: LongInt; var Preco: LongInt ): LongInt; stdcall; external ‘CUB32.dll’; Parâmetros: • long NroBomba - {recebe valor de de 1 a 99} • long *NroProduto - {pointer onde será informado o código de produto} • long *NivelPreco - {pointer onde será informado o nível de preço} • long *Mililitros - {pointer onde será informada o volume do abastecimento} • long *Dinheiro - {pointer onde será informado o valor do abastecimento} • long *Preço - {pointer onde será informado o preço unitário do produto} Retorno: • 0 = CUB Server recebeu a ordem corretamente. • 1 = Nº de bomba incorreto ou bomba não está em estado FIMDEVENDA. • 2 = Erro de comunicação entre a dll e o servidor. • Outro = Código de erro retornado pelo Windows. Ver winsock2.h ou winerror.h. Leitura da Venda em Tempo Real Esta função permite que o usuário veja na tela do computador os valores do abastecimento que esteja acontecendo na pista. O valor retornado não é totalmente real-time. Existem vários atrasos envolvidos no processo de comunicação que fazem com que o valor não seja totalmente preciso, mas a aproximação é muito boa. function CUBLerRTM(NroBomba: LongInt; var RealTimeMoney: LongInt): LongInt; stdcall; external ‘CUB32.dll’; Parâmetros: • • bomba} long NroBomba - {recebe valor de de 1 a 99} long *RTM - {pointer onde será devolvido o ultimo valor lido da Retorno: • 0 = CUB Server recebeu a ordem corretamente. • 1 = Erro nos parâmetros. Número de bomba errado ou bomba não configurada. • 2 = Erro de comunicação entre a dll e o servidor. • Outro = Código de erro retornado pelo Windows. Ver winsock2.h setembro 2012 19 ou winerror.h. Tipicamente, para implementar o tratamento mínimo da comunicação com o CUB e fazer a automação das bombas, é necessário iniciar um novo projeto, adicionar um componente TTimer e implementar o seguinte algoritmo: 1. Lê o estado de todas as bombas juntas (CUBLerEstado) 2. Faz um loop para processar o estado lido de cada uma das bombas 3. Caso o estado da bomba tenha mudado em relação ao estado anterior 3.1. Atribue o novo estado à bomba atualizando as representações visuais 3.2. Caso o novo estado = FIMDEVENDA 3.2.1. Faz a leitura das informações da venda (CUBLerVenda) 3.2.2. Confirma a leitura da venda para o CUB (CUBConfirmarLeituraVenda) 3.2.3. Lê os encerrantes de dinheiro e litros da bomba (CUBLerTotais) 3.2.4. Atualiza a tela e/ou a base de dados do sistema Uma vez implementado o algoritmo, podemos caprichar na interface visual do sistema, de forma a obter um resultado semelhante ao mostrado na Figura 07, onde o usuário é capaz de visualizar o estado de todas as bombas e executar todas as funções disponibilizadas pela SDK de forma muito fácil e intuitiva. nologias e consequentemente o crescimento de todos os setores da economia ligados a ela. Sob a pretensa justificativa de proteção à classe frentista, o estado: • Onera os proprietários de postos e consequentemente os consumidores finais, que no final das contas pagam pelos altos custos operacionais dos postos. • Dificulta diretamente o crescimento de toda uma cadeia de pesquisa e desenvolvimento de alta tecnologia em hardware e software que, importante ressaltar, também gera emprego e renda e é base sólida para o crescimento e desenvolvimento de um país. Questões como estas devem ser resolvidas através de políticas de educação, qualificação e recolocação de mão-de-obra. Pois a nossa diferença, quando comparados aos demais países, não está na nossa capacidade técnica de desenvolvimento e aplicação da tecnologia, mas sim no fato de que os países desenvolvidos, antes mesmo de automatizar seus postos, descobriram através da educação, que o crescimento e desenvolvimento de um país em uma economia de mercado globalizada, se dá com o mínimo possível de intervenção do estado. Conclusão Este artigo serve como uma rápida introdução ao processo de automação de bombas de combustível, cobrindo de forma geral os principais conceitos e ferramentas envolvidas no processo de implementação deste tipo de solução. A aplicação dos conceitos apresentados permite o desenvolvimento de soluções completas com diversos recursos avançados, criados sob medida para as necessidades deste nicho de mercado. No entanto o desenvolvedor deve estar pronto para encarar um mercado altamente fechado que dificulta e muito, a entrada de novas soluções. Principalmente no que diz respeito às bandeiras, estas procuram evitar que seus filiados utilizem softwares de terceiros que não aqueles já previamente acordados com a bandeira em questão. Sistemas como estes envolvem o correto funcionamento e operação de diversos dispositivos e por conseqüência devem estar associados a contratos de manutenção, pois os postos normalmente não dispõem de mão-de-obra especializada para tanto. No entanto um problema muito importante a ser contornado pelo desenvolvedor, e que dificulta bastante a entrada neste mercado, são as distâncias físicas, principalmente quando se trata de postos de estrada ou cidades do interior. Figura 07: Aplicação completa de automação de bombas de combustível da TKS Software. Aplicações como mostrada na Figura 07 permitem o controle completo da pista a partir de uma central, que pode estar localizada, por exemplo, na loja de conveniências do posto. Através de funções de preset em valor e volume, há muito é possível tecnicamente que os postos sejam operados com número mínimo de frentistas ou até mesmo sem eles, como ocorre na grande maioria dos países desenvolvidos. No entanto, por motivos populistas, protecionistas ou até mesmo eleitoreiros, o Brasil insiste em resistir ao progresso, impedindo por lei que os postos de combustíveis adotem tal tipo de tecnologia de forma a se modernizar, reduzindo seus custos operacionais e entrando em sintonia com as tendências mundiais. Este tipo de tecnologia deve ser usado somente para fins de automação, supervisão e controle da pista e seus abastecimentos, não podendo ser usado para mudança do conceito de atendimento dos postos. Posturas intervencionistas como estas dificultam a popularização das tec20 setembro 2012 É importante ressaltar, no entanto, que o desenvolvimento e conseqüente implantação do sistema em um cliente final requer, dentre outras coisas, conhecimentos a respeito da legislação normativa específica de postos de combustível, bem como conhecimentos técnicos sobre a interface e configuração as bombas, tópicos estes não abordados neste artigo. Maiores informações podem ser obtidas junto ao fabricante no site www.gilbarco.com.br. Sobre o autor Victory Fernandes Professor do Departamento de Engenharia da UNIFACS, Engenheiro Mestrando em Redes de Computadores, e desenvolvedor sócio da TKS Software - Soluções de Automação e Softwares Dedicados. Pode ser contatado em [email protected], ou através dos sites www.igara.com.br – www. igara.com.br/victory [email protected] Delphi Parte VI Vimos no artigo anterior, a alteração do nosso projeto onde parametizamos os cadastros e utilizamos uma maneira mais prática para trabalhar com dbExpress. Criamos um cadastro onde precisamos pesquisar qual registro queremos alterar, sem a necessidade de colocar registros desnecessários em memória no ClientDataSet.Neste artigo veremos como utilizar o ClientDataSet para usar dados em memória e usar transação para cadastros master/detail. Teremos um cadastro de vendas com a venda em si e seus itens (produtos). As mesmas possuem tabelas especificas, mas tem um relacionamento, assim, precisamos criar a venda e nos itens, temos que vincular o mesmo com a venda. Nesse caso, não podemos ter problemas de erros ao acrescentar nada (vendas ou itens), pois não podemos ter itens sem venda e nem venda, sem itens. Por isso, vamos usar transação, que nos dá a certeza que todos os dados serão inseridos. O que é transação O contexto de transação é um dos mais importantes na tecnologia cliente/ servidor. Ela garante que os dados serão inseridos corretamente no banco através da aplicação, ou seja, possui consistência. Uma transação por si só, é qualquer operação de escrita em uma tabela do banco. Essa operação pode ser inserção, exclusão ou atualização. Comandos que alteram a estrutura dos objetos, não tem transação vinculada. Vamos a um exemplo simples para entendermos. Saque em um caixa eletrônico. Imagine que você está realizando o saque e após digitar a senha esta esperando o dinheiro. Nesse momento, acontece um blecaute e a energia do caixa é cortada. Você ainda não pegou o dinheiro, mas ele já foi debitado de sua conta? Uma transação em um ambiente de aplicação é chamada de “tudo ou nada”, ou seja, tudo deve ser executado (verificação de saldo, baixa do valor sacado, liberação do dinheiro) ou nada deve ser feito (principalmente o lançamento de débito na conta). Toda transação deve ter um inicio e um final. O final vai depender do que setembro 2012 21 ocorrer no decorrer da mesma. Ela pode finalizar com sucesso, onde o comando Commit será executado no banco, ou ela será cancelada, onde o Roolback irá desfazer tudo que foi realizado até o inicio da transação. Abaixo, temos a Listagem 1 e 2, onde temos dois exemplos de utilização de transação com dbExpress. A Listagem 1 utiliza um exemplo até a versão 2010 do dbExpress. A Listagem 2 temos a utilização de transações no Delphi XE2. Listagem 1. Executando transações com dbExpress var trans: TTransactionDesc; begin try t.IsolationLevel := xilREADCOMMITTED; SQLConnection.StartTransaction(trans); //comandos que deseja executar no banco SQLConnection.Commit(trans); except //caso ocorra algum erro, o comando Rollback cancela tudo SQLConnection. Rollback(trans); end; end; Listagem 2. Executando transações com dbExpress no Delphi XE2 var trans: TDBXTransaction; begin try trans := SQLConnection. BeginTransaction(TDBXIsolations. ReadCommitted); //comandos que deseja executar no banco SQLConnection.CommitFreeAndNil(trans); except //caso ocorra algum erro, o comando Rollback cancela tudo SQLConnection. RollbackFreeAndNil(trans); end; end; Temos que ter cuidado na configuração do nível de isolamento. Read commited indica que vamos ler (se for o caso), apenas os dados gravados, ou seja, os dados reais do banco. Se usarmos a opção dirty read (xilDIRTYREAD) vamos poder ler os dados pendentes, ou seja, dados que ainda não foram persistidos no banco. Esses dados podem estar pendentes de uma transação e caso aconteça algo, podem ser descartados, assim, podemos também visualizar dados que não foram inseridos no banco. Analise qual a melhor forma que você precisa usar em sua aplicação. Lembrando também que o nível de isolamento está relacionado ao banco de dados, que pode variar de acordo com o mesmo. Na Listagem 2, a configuração do nível de isolamento esta no BeginTransaction e para a confirmação e cancelamento do código, temos respectivamente CommitFreeAndNil e RollbackFreeAndNil. Criando o formulário de cadasto Nesse exemplo, não iremos usar a tela de cadastro semelhante aos cadastros anteriores. Vamos criar uma tela do zero, pois as funcionalidades são diferentes das telas de cadastro. A idéia aqui como já comentado, é ter uma tela onde possamos indicar informações da venda (código do cliente, código do empregado, data de venda e valor) e os itens dessa venda (produto). Vamos deixar o usuário escolher em uma pesquisa, o cliente e empregado e digitar a data da venda. A questão do valor da venda, está ligada a soma do valor dos produtos multiplicados pela quantidade. Esse cálculo e a atribuição ao campo, faremos via código. Veja na Figura 1 como ficara a tela de cadastro. Não precisamos criar os componentes de pesquisa para cliente e empregado. Os respectivos ClientDataSets estão no Data Module de pesquisa. Precisamos apenas criar o de produto, caso você não tenha feito, como sugestão no último artigo. Veja no código a seguir, o comando SQL para a pesquisa de produto: select nCdProduto, sNmProduto from PRODUTO where UPPER(sNmProduto) like :sNmProduto Configure o parâmetro semelhante ao que fizemos no artigo anterior. Agora, configure os DataSources do cliente, empregado e produto para os respectivos ClientDataSets do Data Module de pesquisa. Faça o mesmo para os DBTexts dos campos. Criando uma tela de pesquisa genérica Como podemos ver na Figura 1, a tela de vendas terá uma pesquisa para cliente, empregado e produto. Não vamos criar três telas de pesquisa, vamos criar apenas uma, para ser usada nos dois casos (genérica). Diferente do que fizemos com a tela de pesquisa de setor. Veja que as duas são bastante parecidas. Na Listagem 1 criamos uma variável do tipo TTransactionDesc. Após configuramos o nível de isolamento da transação, utilizando a opção read comitted, iniciamos a transação usando BeginTransaction do componente SQLConnection. Nota: Você pode adaptar a tela de pesquisa de setor para ficar genérica, como mostraremos agora. Após executar os comandos no banco, finalizamos a transação com o Commit. Caso ocorra, algum erro, nosso código esta dentro de um bloco try... except, assim podemos usar o Rollback para cancelar todos os comandos. A tela de pesquisa em si, será simples. Basicamente, basta um grid, um edit e um botão de confirmação. A boa notícia, que podemos utilizar a técnica de apenas mudar o DataSet esperado pelo Grid. setembro 2012 Veja que para chamar os métodos Close e Open precisamos apenas usar a propriedade DataSet do DataSource. Como a propriedade Params está implementada em ClientDataSet, precisamos fazer um cast para podermos configurar a respectiva propriedade. Por fim, apenas informamos na barra de status a quantidade de registros retornados pela pesquisa e se forem retornados registros o OK ficara habilitado. O botão OK, apenas fecha e muda a propriedade ModalResult do formulário. Precisamos disso, para que o formulário chamador da tela saiba qual o retorno esperado. Usamos o seguinte código: Figura 1. Tela de cadastro de vendas Nota: poderíamos criar um componente especifico para esse tipo de funcionalidade. Veremos isso no decorrer do nosso curso. Veja na Figura 2 a tela de pesquisa. Close; ModalResult := mrOk; Agora, na tela de cadastro de venda, precisamos fazer a chamada em cada botão para o formulário de pesquisa genérico. Na Listagem 4 temos como será a chamada dos botões. Listagem 4. Chamada para a tela genérica de pesquisa private nCdCliente: integer; nCdEmpregado: integer; nCdProduto: integer; Figura 2. Tela de pesquisa genérica do sistema Ao olhar o código, você irá notar que temos configuração para o nome das colunas do Grid, além de mostrar a quantidade de registros encontrados. No Edit da tela de pesquisa, vamos codificar seu evento OnKeyPress com o código da Listagem 3. Listagem 3. Codificando o KeyPress do Edit if Key = #13 then begin dsPesquisa.DataSet.Close; (dsPesquisa.DataSet as TClientDataSet). Params[0].AsString := UpperCase(‘%’+ edtPesquisa.Text + ‘%’); dsPesquisa.DataSet.Open; StatusBar1.SimpleText := Format(‘%d registro(s) encontrado(s)’, [dsPesquisa.DataSet.RecordCount]); btnOk.Enabled := dsPesquisa.DataSet. RecordCount > 0; ... end; ... Cliente try frmPesquisa := TfrmPesquisa. Create(self); DMPesquisa.cdsPesquisaCliente.Open; frmPesquisa.dsPesquisa.DataSet := DMPesquisa.cdsPesquisaCliente; frmPesquisa.Descricao := ‘Nome do cliente’; if frmPesquisa.ShowModal <> mrOk then DMPesquisa.cdsPesquisaCliente.Close else cdsCliente := DMPesquisa. cdsPesquisaCliente.FieldByName( ‘nCdCliente’).AsInteger; finally frmPesquisa.Free; end; Empregado try frmPesquisa := TfrmPesquisa. Create(self); DMPesquisa.cdsPesquisaEmpregado.Open; frmPesquisa.dsPesquisa.DataSet := DMPesquisa.cdsPesquisaEmpregado; frmPesquisa.Descricao := ‘Nome do empregado’; if frmPesquisa.ShowModal <> mrOk then DMPesquisa.cdsPesquisaEmpregado.Close else setembro 2012 cdsEmpregado := DMPesquisa. cdsPesquisaEmpregado.FieldByName( ‘nCdEmpregado’).AsInteger; finally frmPesquisa.Free; end; Colocando os dados em memória Agora, precisamos configurar o ClientDataSet da tela para receber os dados do produto e armazenar os mesmos em memória. Clique com o botão direito no cdsItens e escolha Fields Editor. No editor, basta usar a combinação de teclas Crtl + N para abrir a tela para criar o campo confirme vemos a Figura 4. Produto try frmPesquisa := TfrmPesquisa. Create(self); DMPesquisa.cdsPesquisaProduto.Open; frmPesquisa.dsPesquisa.DataSet := DMPesquisa.cdsPesquisaProduto; frmPesquisa.Descricao := ‘Nome do produto’; if frmPesquisa.ShowModal <> mrOk then DMPesquisa.cdsPesquisaProduto.Close else cdsProduto := DMPesquisa. cdsPesquisaProduto.FieldByName( ‘nCdProduto’).AsInteger; finally frmPesquisa.Free; end; Primeiro, criamos variáveis auxiliares que vão armazenar o código dos respectivos cadastros, que serão usados no armazenamento dos itens. Veja que a diferença do código que chama a tela de pesquisa, fica por conta da configuração do ClientDataSet de pesquisa que vamos utilizar. Na ordem, criamos o formulário, abrimos o componente de pesquisa e configuremos o DataSource da tela. Após, indicamos o nome da coluna do Grid e por fim, verificamos o retorno da tela modal. Devemos fazer isso, por que o DBText com o nome do cliente, empregado ou produto, está vinculado ao componente de pesquisa. Se o usuário não escolher nada e fechar a tela, continuaríamos com a vinculação do nome, o que não é correto. Por isso, fechamos o componente de pesquisa, se o usuário não escolher nenhum item na tela de pesquisa. Veja na Figura 3 a tela em execução. Figura 3. Tela de pesquisa genérica 24 Dica: Você pode disponibilizar para o usuário, digitar o código ou o nome ao invés do botão de pesquisa. O sistema faz a busca e caso não retorne nenhum registro, você apresenta a tela de pesquisa. Fica como dica. setembro 2012 Vamos criar o primeiro campo, que será o que vai armazenar o código do produto. Em Name digite “nCdProduto” e em Type escolha Integer. Como escolhemos Integer não precisamos configurar o Size. Na Tabela 1 temos os outros campos que devem compor o cdsItens. Tabela 1. Campos do ClientDataSet Para o campo Total, precisamos configurar a sua propriedade Expression para “SUM(SubTotal)”. Para o campo SubTotal precisamos acessar o evento OnCalcFields do cdsItens e adicionar o seguinte código: cdsItensSubTotal.AsCurrency := (cdsItensnVlProduto.AsCurrency * cdsItensnQtProduto.AsFloat); No código, apenas realizamos o cálculo do subtotal, que é a multiplicação do valor pela quantidade do produto. Por fim, altere para True a propriedade AggregatesActive do cdsItens. Agora, precisamos codificar o botão que vai armazenar cada produto escolhido pelo usuário. Veja na Listagem 5 o código do botão. Listagem 5. Botão de adicionar o item if (nCdProduto = 0) then ShowMessage(‘É necessário escolher um produto’) else if (edtValor.Text = ‘’) then ShowMessage(‘Campo Valor: preenchimento obrigatório.’) else if (edtQuantidade.Text = ‘’) then ShowMessage(‘Campo Quantidade: preenchimento obrigatório.’) else begin if not cdsItens.Active then cdsItens.CreateDataSet; cdsItens.Insert; cdsItensnCdProduto.AsInteger := nCdProduto; Figura 4. Criando o campo no cdsItens cdsItenssNmProduto.AsString := DMPesquisa.cdsPesquisaProduto. FieldByName(‘sNmProduto’).AsString; cdsItensnVlProduto.AsCurrency := StrToCurr(edtValor.Text); cdsItensnQtProduto.AsFloat := StrToFloat(edtQuantidade.Text); cdsItens.Post; edtValor.Text := ‘’; edtQuantidade.Text := ‘’; DMPesquisa.cdsPesquisaProduto.Close; nCdProduto = 0; end; O código faz primeiramente algumas validações para saber se o usuário escolheu um produto e preencheu os campos Valor e Quantidade. Após, é verificado se o ClientDataSet não esta ativo, vamos chamar o método CreateDataSet que cria em memória o espaço necessário para adicionarmos os itens. Adiante, simplesmente, passamos para os campos do cdsItens, os valores necessários e no final, limpamos controles de tela e componentes. Você já pode testar a inserção de itens no cadastro (Figura 5). Para mostrar o total geral, basta adicionar um DBText abaixo do Grid e vincular o mesmo ao campo Total do cdsItens. Para o botão de excluir (ao lado do Grid), basta chamar o Delete do cdsItens. Criando as Stored Procedures Na Listagem 5 temos as Stored Procedures que vamos utilizar na inserção da venda e itens. Figura 5. Inserindo itens na tela de vendas INSERT INTO VENDA (nCdCliente, nCdEmpregado, tDtVenda, nVlVenda) VALUES (@nCdCliente, @nCdEmpregado, @ tDtVenda, @nVlVenda) select @nCdVenda = @@IDENTITY from VENDA END GO CREATE PROCEDURE INSERT_ITEM @nCdVendaint, @nCdProdutoint, @nVlItem decimal(9,2), @nQtItem decimal(9,2) AS BEGIN INSERT INTO ITENS (nCdVenda, nCdProduto, nVlItem, nQtItem) VALUES (@nCdVenda, @nCdProduto, @ nVlItem, @nQtItem) UPDATE PRODUTO SET nQtEstoque = nQtEstoque - @ nQtItem WHERE nCdProduto = @nCdProduto END Listagem 5. Stored Procedures para inserir venda e itens CREATE PROCEDURE INSERT_VENDA @nCdClienteint, @nCdEmpregadoint, @tDtVenda datetime, @nVlVenda decimal(9,2), @nCdVendaint output AS BEGIN Note que as duas SP são bastante simples, apenas estamos usando o comando INSERT nas respectivas tabelas. Uma diferença fica na INSERT_VENDA, onde, após executar o comando INSERT, retornamos o código da venda (que é um identity) usando o atributo @@IDENTITY. Esse retorno esta em um parâmetro de saída, ou seja, um parâmetro declarado no cabeçalho da Stored Procedure, mas que não preenchemos o mesmo, ele será preenchido no corpo da SP. Assim, na aplicação, pegamos esse valor do código da venda para inserir na INSERT_ITEM, para fazermos o relacionamento das tabelas. Nessa procedure, ainda fizemos uma regra de negócio, onde ao incluir o item da venda, vamos diminuir a sua quantidade na tabela PRODUTO. Assim, concentramos na procedure a funcionalidade de baixa o estoque. setembro 2012 25 Usando componetes SQLStoredProc Agora, vamos adicionar no Data Module, dois SQLStoredProc e vincular os mesmos com as Stored Procedures criadas anteriormente. Note que ao adicionar as SPs a propriedade Params é preenchida automaticamente (Figura 6). Figura 6. Parâmetros da Stored Procedure no SQLStoredProc Agora, vamos criar uma função responsável por repassar os dados dos itens e vendas para os respectivos componentes SQLStoredProc. Veja na Listagem 6 o código. Listagem 6. Método para gravar a Venda e seus itens function InserirVenda(nCdCliente, nCdEmpregado: integer; tDtVenda: TDateTime; cdsItens: TClientDataSet): boolean; var nCdVenda: integer; i: integer; trans: TDBXTransaction; begin try trans := DelphiTheClub. BeginTransaction(TDBXIsolations. ReadCommitted); //preenche a venda spVenda.Params[1].AsInteger := nCdCliente; spVenda.Params[2].AsInteger := nCdEmpregado; spVenda.Params[3].AsDateTime := tDtVenda; spVenda.Params[4].AsFloat := StrToFloat(cdsItens. FieldByName(‘Total’).AsString); //executa a SP spVenda.ExecProc; //pega o retorno nCdVenda := spVenda.Params[5]. AsInteger; do 26 //adicione os itens for i := 0 to cdsItens.RecordCount -1 setembro 2012 begin spItens.Params[1].AsInteger := nCdVenda; spItens.Params[2].AsInteger := cdsItens.FieldByName(‘nCdProduto’). AsInteger; spItens.Params[3].AsCurrency := cdsItens.FieldByName(‘nVlProduto’). AsCurrency; spItens.Params[4].AsFloat := cdsItens.FieldByName(‘nQtProduto’). AsFloat; spItens.ExecProc; cdsItens.Next; end; DelphiTheClub.CommitFreeAndNil(trans); Result := true; except DelphiTheClub. RollbackFreeAndNil(trans); Result := false; end; end; Como podemos notar, está usando transação. Primeiramente, preenchemos o componente da SP de venda e em uma variável adicionamos o retorno da SP, o código da venda. Após, percorremos o ClientDataSet dos itens, preenchendo seus parâmetros e chamando o ExeProc. Caso aconteça algum problema, o bloco do except é executado e nada será executado no banco. A função retornará true se não tivermos problemas e false, se ocorrer algum erro. No formulário da venda, basta chamar o método usando o código da Listagem 7. Listagem 7. Validações para salvar a venda if (nCdCliente = 0) then ShowMessage(‘É necessário escolher um cliente.’) else if (nCdEmpregado = 0) then ShowMessage(‘É necessário escolher um empregado.’) else if (not cdsItens.Active) or (cdsItens.RecordCount = 0) then ShowMessage(‘É necessário adicionar ao menos um item.’) else begin if DM.InserirVenda(nCdCliente, nCdEmpregado, tDtVenda.DateTime, cdsItens) then begin MessageDlg(‘Venda inserida com sucesso.’, mtInformation, [mbOK], 0); nCdCliente := 0; nCdEmpregado := 0; DMPesquisa.cdsPesquisaCliente.Close; DMPesquisa.cdsPesquisaEmpregado.Close; cdsItens.Close; end else ShowMessage(‘Ocorreu um erro.’); end; Isso ocorre, por que o XE2 ainda não suporta o mesmo. Como eu posso usar o SQL Server 2012 com o XE2? Simples, instale o cliente do SQL Server 2008. Acesse o link de acordo seu SO: 32 bits: http://go.microsoft.com/fwlink/?LinkId=123717&clcid=0x416 64 bits: http://go.microsoft.com/fwlink/?LinkId=123718&clcid=0x416 Conclusões No código, fizemos validações referentes aos campos obrigatórios da tela. Após, chamamos o InserirVenda do Data Module, verificando se o retorno será true para então, emitir uma mensagem e configurar as variáveis e componentes para iniciar uma nova venda. Vimos nesse artigo, como utilizar transações com dbExpress. Nesses três artigos sobre Delphi e banco de dados, acredito que pudemos entender apenas uma parte das vastas possibilidades que encontramos no Delphi para trabalhar com banco de dados. Faça testes, inserindo vendas, itens etc. Confirme que os registros foram inseridos no banco. Simule um erro e confirme que nenhum registro será adicionado no banco, pois estamos usando transação. No próximo artigo vamos conhecer o poder da programação Orientada em Objetos, com herança visual, classes e muito mais. SQL Server 2012 Quando iniciamos essa série, indicamos o uso do SQL Server 2008 Express. A Microsoft lançou a pouco tempo a versão 2012 do seu servidor de banco de dados. Rumores indicam que o XE3 terá suporte a essa nova versão do SQL Server. Instalei o mesmo e começou a ocorrer o seguinte erro: DBX Error: Driver could not be properly initialized. Client library may be missing, not installed properly, of the wrong version, or the driver may be missing from the system path. Um grande abraço a todos e até a próxima! Sobre o autor Luciano Pimenta É Técnico em Processamento de Dados, desenvolvedor Delphi/C# para aplicações Web com ASP.NET e Windows com Win32 e Windows Forms. Palestrante da 4ª edição da Borland Conference (BorCon). Autor de mais de 60 artigos e de mais de 300 vídeos aulas publicadas em revistas e sites especializados. É consultor da FP2 Tecnologia (www.fp2.com.br) onde ministra cursos de programação e banco de dados. É desenvolvedor da Paradigma Web Bussiness em Florianópolis-SC. www.lucianopimenta.net setembro 2012 27 Dicas DELPHI Arrastar e soltar abas do PageControl O componente PageControl é bastante utilizado para organizar o layout de um formulário, com ele pode-se agrupar os campos de preenchimento por seus respectivos assuntos, um exemplo bem clássico disso é um formulário de vendas, onde pode-se ter uma aba para o cabeçalho da venda, outra para os produtos daquela venda e outra destinada a forma de pagamento da venda. Ou seja, os componentes foram agrupados conforme a sua adequação ao formulário. Esta dica visa dar uma funcionalidade a mais à este componente, o drag and drop, o famoso arrastar e soltar. Porque não deixar o usuário organizar as abas ao seu modo, além de ser um atrativo a mais para o seu sistema, e é muito fácil a sua implementação. Bastam apenas três procedimentos para que este recurso seja adicionado ao PageControl do seu formulário.O primeiro procedimento é mostrado na listagem 1, no evento onMouseDown do PageControl. procedure TfrmPgCtrlDragDrop. PageControl1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // Da inicio ao drag and drop PageControl1.BeginDrag(True); end; Listagem 1 – Inicia o drag and drop No segundo passo é onde ocorre toda a programação de arrastar e soltar. É implementado no evento onDragDrop do próprio PageConrol, veja na listagem 2 a codificação. procedure TfrmPgCtrlDragDrop. PageControl1DragDrop(Sender, Source: TObject; X, Y: Integer); const TCM_GetItemRect = $130A; var TabRect : TRect; j : Integer; begin if (Sender is TPageControl) then for j := 0 to PageControl1.PageCount -1 do begin PageControl1.Perform(TCM_ GetItemRect, j, LParam(@TabRect)); if (PtInRect(TabRect, Point(x, y))) then begin if (PageControl1.ActivePage. PageIndex <> j) then PageControl1.ActivePage. PageIndex := j; Exit; 28 setembro 2012 end; end; end; Listagem 2 – Codificando o drag and drop O último processo é fazer a aceitação do drag and drop no próprio componente PageControl. No evento onDragOver implemente o código da listagem 3. procedure TfrmPgCtrlDragDrop. PageControl1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin if (Sender is TPageControl) then Accept := true; end; Listagem 3 – Finalizando o drag and drop Com este último evento finaliza-se a ação de arrastar e soltar as abas do PageControl em tempo de execução, execute o exemplo e veja esta nova funcionalidade do PageControl. Espero que tenham gostado desta dica, um abraço a todos e até mais. Função para criptografar e descriptografar Esta função faz a criptografia da string deixando-a com a mesma quantidade de caracteres que contêm a string original.A função recebe apenas um parâmetro que é do tipo string, ou seja, para criptografar, basta passar a string a ser criptografada e para descriptografar basta passar a string criptografada que a sua criptografia será revertida para a string de origem. Segue na listagem 1 o corpo da função Criptografar. function TfrmCriptografa. Criptografar(Texto: string): string; var Maiusculas, Minusculas, Cod1, Cod2 : String; I: Integer; begin Maiusculas := ‘ABCDEFGHIJLMNOPQRSTUVXZYWK ~!@#$%^&*()’; Minusculas := ‘abcdefghijlmnopqrstuvxzywk1234567890’; Cod1 := ‘ÂÀ©Øû׃çêùÿ5Üø£úñѪº¿®¬¼ëèïÙýÄ ÅÉæÆôöò»Á’; Cod2 :=’áâäàåíóÇüé¾¶§÷ÎÏ+ÌÓ߸°¨•¹³²Õµþîì¡«½’; for I := 1 to Length(Trim(Texto)) do begin if Pos(Copy(Texto, I, 1), Maiusculas) > 0 then Result := Result + Copy(Cod1, pos(copy(Texto, I, 1), Maiusculas), 1) else if Pos(copy(Texto, I, 1), Cod1) > 0 then Result := Result + copy(Maiusculas, pos(copy(Texto, I, 1), Cod1), 1) else if pos(copy(Texto, I, 1), Minusculas) > 0 then Result := Result + copy(Cod2, pos(copy(Texto, I, 1), Minusculas), 1) else if pos(copy(Texto, I, 1), Cod2) > 0 then Result := Result + copy(Minusculas, pos(copy(Texto, I, 1), Cod2), 1); end; end; Listagem 1 Pode-se ver que a criptografia funciona pela troca dos caracteres das variáveis Maiusculas e minúsculas pelos caracteres de mesma posição nas variáveis Cod1 e Cod2. Veja na listagem 2 um exemplo da utilização desta função. procedure TfrmCriptografa. Button1Click(Sender: TObject); begin if (edtTexto.Text <> ‘’) then begin // CRIPTOGRAFA O TEXTO edtResultado.Text := Criptografar(edtTexto.Text); edtTexto.Clear; end else if (edtresultado.Text <> ‘’) then begin // DESCRIPTOGRAFA O TEXTO CRIPTOGRAFADO edtTexto.Text := Criptografar(edtresultado.Text); edtResultado.Clear; end; end; Listagem 2 Bom pessoal, espero que tenham gostado desta dica e que tenha sido de algum utilidade à vocês. Até mais. Cronômetro usando o componente TTimer Nesta dica iremos utilizar 1 TTimer (Enabled = False), 1 TLabel, 1 TEdit e 1 TButton (Caption = “Ativar”). O TLabel será o responsável por exibir o tempo corrente do cronômetro, o TEdit o responsável para informar de onde o cronômetro iniciará a sua contagem, o TTimer será o responsável pela contagem do tempo e por fim o TButton será o responsável por ativar e desativar o Cro- nômetro. Além desses componentes iremos utilizar também 2 variáveis para a manipulação do cronômetro, são elas TIME_START e TIME_OLD ambas do tipo TDateTime, portanto, não se esqueça de declarar estas variáveis na sessão private do formulário. Na listagem 1 segue o código do evento onTime do Timer1. procedure TfrmCronometro. Timer1Timer(Sender: TObject); begin Label1.Caption := FormatDateTime(‘HH:MM:SS:ZZZ’, TIME_START + Now - TIME_OLD); end; Listagem 1 Reparem que será exibido na Label1 as horas, os minutos, os segundos e também os milésimos de segundos, através da função FormatDateTime e os parâmetros passados que são, o tempo de início da contagem mais o tempo atual, pela função Now, menos o tempo corrente no sistema no momento em que foi ativado o cronômetro pelo botão. Veja na listagem 2 a codificação do botão de ativar e desativar o cronômetro. procedure TfrmCronometro. Button1Click(Sender: TObject); begin TIME_START := StrToDateTime(Edit1.Text); if (Button1.Caption = ‘Ativar’) then begin TIME_OLD := Now; Timer1.Enabled := True; Button1.Caption := ‘Desativar’; end else begin Timer1.Enabled := False; Button1.Caption := ‘Ativar’; end; end; Listagem 2 Na primeira linha já é alimentada a variável TIME_START recebendo o valor na propriedade Text do Edti1. Em seguida faz a verificação se o Caption do Button1 está como Ativar, caso seja sim, adiciona o tempo atual na variável TIME_OLD, ativa o Timer1, deixando como True a sua propriedade Enabled, e altera o Caption do Button1 para “Desativar”. Se o Caption do Button1 não for “Ativar”, fará o processo de desativação do cronômetro, deixando False a Propriedade Enabled do Timer1 e mudando o Caption do Button1 para “Ativar”. Para deixar um automatizado o processo de preenchimento do tempo no Edti1, pode-se adicionar no evento onCreate do formulário o código da listagem 3. procedure TfrmCronometro.FormCreate(Sender: TObject); begin Edit1.Text := FormatDateTime(‘hh:mm:ss’, Now); end; Listagem 3 Espero que seja útil esta dica. Um grande abraço a todos e até mais. setembro 2012 29 Horizontal 30 Vertical setembro 2012 setembro 2012 setembro 2012