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
Download

setembro 2012