FastReport

Propaganda
julho
2013
julho
2013
04
Editorial
05
FastReport no Delphi
Autor: Luciano Pimenta
11
TConexao:
Gerenciando seu projeto em ambientes Multi-Plataforma
– parte 1
Autor: Hamden Vogel
19
Android
Trabalhando com o TouchScreen
Índice
Autor: Thiago C. Montebugnoli
27
As principais mudanças Delphi XE4
Autor: José Antonio P. M. de Paula
julho
2013
03
Editorial
Caro Leitor!
É sempre com muita alegria e satisfação que finalizamos mais uma revista aos senhores. Aproveito também para agradecer às pessoas que vem
acessando o conteúdo de nosso site, portanto a cada dia que passa estamos
adquirindo mais acessos e cadastros. Que continuemos assim!
Para este mês, nosso consultor técnico José Antonio nos trouxe direto
do forno as principais novidades do Delphi XE4 com o intuito de sempre
mantê-los bem informados com as últimas tecnologias lançadas no mercado. Nosso colunista mensal Luciano Pimenta aborda o uso do gerador de
relatórios “FastReport”, uma boa opção para quem trabalha com o Delphi.
Podemos considerar esta ferramenta como a opção oficial para quem
utiliza as novas versões do Delphi. Já nosso colaborador Hamden Voguel
desenvolveu um componente próprio para gerenciar projetos em ambientes
Multi-Plataforma, o “TConexao”. Nesta primeira parte ele irá nos auxiliar
na gerência de Projetos utilizando diversos tipos de Banco de Dados. Importante salientar que este componente está disponível gratuitamente em
nosso site. Eu continuo apresentando recursos do Sistema Android, sendo
que neste mês ensino a trabalhar com o “TouchScreen”, descrevendo suas
principais classes e métodos para realizar esta tarefa. Recomendo a leitura
deste artigo para quem está começando a desenvolver aplicativos e para
quem deseja aprimorar seus conceitos em relação a este artifício.
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 2013
Diretor Técnico
Marcos César Silva
Diagramação
Vitor M. Rodrigues
Design
Vitor M. Rodrigues
Revisão
Drielly Cristini Patrinhani
Colunistas
João Marcos Sakalauska
José Antonio P. M. de Paula
Lucas de Oliveira
Luciano Pimenta
Thiago Cavalheiro Montebugnoli
Juninho
Jeferson Silva de Lima
Impressão e acabamento:
GRIL - Gráfica e Editora
Taquarituba-SP - Tel. (14) 3762-1345
Um forte abraço e até o mês que vem!
Reprodução
Thiago Montebugnoli - Editor Chefe
[email protected]
04
julho
2013
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 Borland International,
as demais marcas citadas são registradas
pelos seus respectivos proprietários.
FastReport no
Delphi
F
astReport é um gerador de relatórios presente no Delphi desde
a sua versão 4. A partir da versão XE2, passou a ser a ferramenta
de relatórios “oficial” do Delphi. Com o FastReport podemos criar
poderosos relatórios pra nossas aplicações Win32 e FireMonkey
com Delphi.
Trabalharemos para criar relatórios simples, usando dois componentes:
o frxReport e o frxDBDataSet. O primeiro é o editor de relatórios, onde basta
dar um duplo clique que teremos o ambiente de criação dos relatórios (Figura
2). O segundo faz a ponte, entre os dados e o relatório.
A ferramenta ainda possui suporte (adquirido separadamente) para .NET,
Mono e Lazarus. Possui ainda uma ferramenta chamada FastCube para análise
de dados e construção de relatórios e gráficos (OLAP), uma ferramenta de
scripts (FastScript) e o FastQueryBuilder, construtor visual de consultas SQL.
Para ver exemplos e outras informações sobre essas ferramentas, acesse:
www.fast-report.com/pt/. Nesse artigo, vamos conhecer a versão do FastReport
que acompanha o Delphi XE4, aprendendo a criar relatórios simples, agrupados,
formatações e muitos outros exemplos.
Conhecendo a ferramenta
Ao abrir o Delphi e criar um projeto VCL Foms, podemos visualizar a aba
FastReport 4.0 (Figura 1).
Figura 2. Ambiente de desenvolvimento de relatórios (fonte: FastReport User’s
Manual)
Baseado na Figura 2, destacamos a seguir as principais janelas e editores.
Figura 1. Aba do Fast Report no Delphi
1. Report designer: local onde vamos adicionar as bandas e os objetos
para o relatório;
2. Barra de menus;
3. Barra de ferramentas: botões com as opções de carregar relatórios,
criar novos, salvar etc;
4. Barra de ferramentas do objeto: objetos que podem ser adicionados
ao relatório;
5. Report page tabs: abas para configuração da página do relatório;
6. Report tree: janela onde podemos visualizar as bandas e objetos
inseridos no relatório;
7. Object Inspector: janela para definição das propriedades dos objetos
do relatório;
8. Data tree: janela com os objetos de dados do relatório. Podemos
julho
2013
05
arrastar facilmente um campo para o relatório;
9.Régua;
10. Status line: semelhante a uma barra de status, com informações
sobre o objeto selecionado no relatório.
Primeiro exemplo
Volte ao Delphi e adicione um frxDBDataSet (um frxReport também deve
estar no formulário). Crie uma conexão com o banco de dados de sua preferencia (usarei no artigo, o Firebird). Usarei no artigo um Data Module para conter
os componentes de conexão com o banco, pois usaremos vários exemplos.
Adicione os tipos: Page Header, Page Footer e Master Data. O Master Data,
abre um editor, solicitando o respectivo DataSet da aba. Selecione frxDBDataSet1. Existem duas maneiras de adicionar os campos na banda Master Data
para exibir os dados.
Arraste os campos da janela Data tree para o relatório. Note que após
inserir os campos, ao passar o mouse, é mostrado uma seta, onde podemos
clicar e será exibido um menu com os campos do DataSet, assim fica fácil
modificar o objeto para outro campo da consulta (Figura 5).
Vincule o frxDBDataSet com o DataSet que retorna os dados da sua consulta. Dê um duplo clique no frxReport para abrir o editor. Primeiramente, vamos
vincular o frxDBDataSet com o nosso relatório, acessando o menu Report>Data.
No editor que abrir, escolha o respectivo controle de dados (Figura 3).
Figura 5. Mudando o campo do objeto facilmente
Figura 3. Vinculando o componente de dados com o relatório
Vamos agora, adicionar as bandas do relatório. Clique no botão Insert Band
na barra de ferramentas de objetos. Conforme vemos na Figura 4 é mostrado
um menu suspenso que todas as opções de banda.
Outra maneira de adicionar os campos no relatório é escolhendo um Text
object na barra de ferramentas e adicionando o mesmo no relatório. Será
aberto um editor com alguns botões (Figura 6).
Figura 6. Editor do Text Object
No editor, temos abas onde vamos configurar o formato do campo,
formatações de fonte etc. o primeiro botão da aba Text, acessa outro editor,
onde podemos escolher os campos da consulta, variáveis do relatório, funções,
etc (Figura 7).
Figura 4. Adicionando bandas ao relatório
06
julho
2013
Formatação de objetos
Como podemos ver no relatório que criamos, o campo Salary, esta como
texto, sem a devida formatação. Para ajustar isso, abra o relatório e dê um
duplo clique no campo. No editor, acesse a aba Format e configure, conforme
a Figura 10, onde indicamos o tipo de formatação e o separador decimal
(vírgula) do formato.
Figura 7. Editor de expressões do relatório
Podemos usar o editor do Text Object para apenas adicionar rótulos (textos) no relatório, onde basta digitar o texto desejado na aba Text. Na barra
de ferramentas temos as funcionalidades necessárias para formatar os textos
digitados nesse objeto.
Note que ao adicionarmos um Text Object ou mesmo adicionar um campo
usando a janela Data tree, existem linhas que nos auxiliam para que os controles fiquem alinhados tanto horizontalmente, como verticalmente (Figura 8).
Figura 8. Alinhando os componentes do relatório
Feche o relatório. No Delphi, adicione um botão e adicione o seguinte
código em seu evento OnClick:
frxReport1.ShowReport();
Figura 10. Relatório de listagem em execução
Nem precisamos executa o projeto novamente, basta acessar o botão
de preview no próprio ambiente do relatório para visualizar as modificações.
E se precisássemos modificar a formatação de um objeto (fonte, cor etc) de
acordo com algum parâmetro que existisse, como faríamos? É bastante simples.
Acesse o objeto que deseja modificar a formatação. Dê um duplo
clique para acessar o editor e a aba Highlight. Na opção Condition, digite:
“<frxDBDataset1.”SALARY”> > 50000”. Marque em Font a opção Bold. O botão Color, indica a cor do texto quando a condição do objeto for verdadeira.
Você também pode usar o botão para acessar o editor de expressões para
montar a mesma manualmente. De acordo com o configurado antes, o campo
Salary do relatório deve aparecer em vermelho quando o valor desde for maior
que 50.000,00. Veja na Figura 11 nosso exemplo em execução.
Execute a aplicação e visualize o relatório em funcionamento (Figura 9).
Figura 11. Formatação condicional do relatório
Caso você deseje mudar a formatação de todo “a linha”, para que não
precise fazer manualmente a configuração de cada objeto, basta selecionar
todos os objetos e acessar a propriedade HighLight>Condition e fazer a mesma
configuração anterior.
Figura 9. Relatório de listagem em execução
Outra formatação bastante usada é a de indicar a quantidade de páginas e
julho
2013
07
a página atual do relatório. O FastReport possui variáveis que retornam essas
informações e que são fáceis de serem utilizadas.
Acesse a aba Variables do Data tree e arraste para o formulário um Date
e um TotalPages. Para customizar esses variáveis, basta dar um duplo clique e
digitar o texto que deseja. Veja na Figura 12 o rodapé do relatório (modifiquei
o mesmo para mostrar mais de uma página).
Figura 12. Formatação do rodapé do relatório
Nota: Para adicionar um texto, basta digitá-lo no editor, tomando
o cuidado de não remover a variável.
Arquivos de relatórios
Para você que estava acostumado com o Rave Reports, vai notar uma
diferença interessante no FastReport. Cada relatório é um arquivo FR3. No
Rave, o arquivo RAV era de projeto e dentro poderíamos ter vários relatórios.
No FastReport, cada FR3, é apenas um relatório. Salve o arquivo e modifique o código do botão para o seguinte código:
Código 02
select EMPLOYEE.first_name,
EMPLOYEE.last_name,
employee.salary, DEPARTMENT.
department
from EMPLOYEE
inner join DEPARTMENT on
DEPARTMENT.dept_no = EMPLOYEE.
dept_no
order by DEPARTMENT.department
Crie um novo frxReport (se desejar, em outro formulário). Caso seja carregado o relatório anterior, crie um novo. Não esqueça de modificar o Data
do relatório. Caso tenha criado um novo formulário, atende para o nome do
formulário no editor de seleção de DataSets (Figura 3).
De um duplo clique no Master Data e vincule com o DbDataSet. Adicione
uma banda Group Header. Um editor é aberto para configurarmos a banda do
grupo. Vamos indicar que o agrupamento se dará pelo campo Department.
Podemos ainda indicar uma expressão para o agrupamento.
Nas opções de agrupamento, podemos configurar para que a cada grupo,
seja mostrado em uma nova página ou drill-down, onde clicamos sobre o grupo
para mostrar os dados.
Código 01
frxReport1.
LoadFromFile(‘Listagem.fr3’);
frxReport1.ShowReport();
Nota: caso o FastReport não encontre o arquivo, nenhum erro é
mostrado, apenas o relatório não exibe nenhum registro.
Dica: caso você escolha a opção drill-down ao executar o relatório,
apenas os grupos serão mostrados. É necessário clicar em cima do mesmo
para exibir os registros.
Arraste a banda para que fique acima da Master Data. Veja na Figura 13
o relatório agrupado em execução.
Usamos o método LoadFromFile para carregar o arquivo de relatório no
frxReport. Isso nos dá a facilidade de termos apenas um controle e vários
relatórios, sendo carregados de acordo com a nossa necessidade.
Agrupamento
Outro exemplo muito usado em relatórios é o de agrupamento de dados.
Neste exemplo, vamos fazer a mesma listagem anterior, com a diferença que
vamos retornar todos os empregados, agrupados pelo seu departamento.
Veja o SQL da consulta:
08
julho
2013
Figura 13. Relatório agrupado
Somatório
Master/detail
Sempre que usamos agrupamento de dados em um relatório, precisamos
somar um determinado campo ou contar a quantidade de registros. Vamos
aproveitar esse relatório para fazer esse exemplo. Adicione uma banda GroupFooter no relatório.
Outro exemplo muito comum em relatório e de mostrar os dados master e os details. Exemplo clássico: nota fiscal, os dados referente ao cliente
(comprador) podemos considerar o master, e as informações dos produtos
da nota, são o detail.
Adicione um Text Object e acesse o botão Insert Aggregate da aba Text.
No editor, vamos configurar um campo que terá seu valor somado, que no
caso é Salary. Veja na Figura 14 como ficou a configuração.
Essas duas fontes de dados, precisam ter um relacionamento. Primeiro,
teremos duas consultas separadas, uma para a master e outra para detail.
Nesse exemplo, estou pesquisando os empregados (Employee) e o histórico
de salários (Salary_History). Precisaremos, portanto de dois frxDBDataSet (um
para cada consulta).
O relacionamento, faremos nos componentes de consulta, o que é bastante simples. Adicione um DataSource e faça a ligação com a consulta master.
No componente (ClientDataSet, Query, Table etc) com os dados da consulta
detail, acesse a propriedade MasterSource e escolha o DataSet anterior. Em
MasterFields, acesse o editor e configure o campo de relacionamento (nesse
caso Emp_no).
Crie um novo relatório e adicione as seguintes bandas: Page Header,
Master Data, Detail Data e Page Footer. Na Master Data você deve configurar o frxDBDataSet que tem os dados do máster (no exemplo, Employee) e
consequentemente, para o Detail Data, os dados do detail (Salary_History).
Veja na Figura 16 a disposição dos campos nas duas bandas.
Figura 14. Configurando o somatório do grupo
Faça a formatação no campo, semelhante ao campo Salary. Rode o relatório
e note que temos o somatório por grupo. Mas e se quisermos o valor total
no final do relatório? Basta adicionar uma banda ReportSummary e fazer a
mesma configuração anterior.
Veja na Figura 15 o relatório com somatório do grupo e somatório total.
Figura 16. Relatório master/detail
Execute o relatório e veja que os dados estão agrupados de acordo com
o nome do empregado.
Exportação de relatórios
Uma característica interessante que observei no FastReport é a quantidade
de opções de tipos de exportações para os relatórios. Veja na Figura 17 a aba
de exportações do FastReport.
Figura 15. Somatórios no relatório
Dica: temos uma opção bem interessante na configuração do somatório.
Caso precisássemos mostrar um somatório acumulado por grupos, basta
marcar a opção Running total na janela Insert Agreggate (Figura 14). Assim, a
cada final de grupo, teríamos um somatório acumulado.
Figura 17. Tipos de exportações do FastReport
julho
2013
09
Para exportar, por exemplo, um relatório para HMTL, basta adicionar no
formulário um frxHTMLExport. Acesse a IDE de desenvolvimento e execute
o preview do relatório. Note que um botão foi adicionado para exportar o
relatório.
Para cada tipo de exportação, um item de menu será adicionado no preview
do relatório (Figura 18).
Figura 19. Editor para envio por e-mail do relatório
Figura 18. Opções de exportação no preview do relatório
Na versão comercial do FastReport, diferente da versão que acompanha
o Delphi, existe a opção de envio do relatório por e-mail. Ao clicar no componente, um editor será mostrado. Nele, na aba E-mail configuramos para quem
o relatório será enviado, assunto, corpo do e-mail e escolhendo o formato
desejado (Figura 19).
Na aba Account, configuramos o e-mail de envio, ou seja, as nossas configurações de e-mail para o envio do mesmo.
Conclusões
Vimos nesse artigo como trabalhar com o FastReport, a “nova” ferramenta
para gerar relatórios no Delphi. Existem muitas outras possibilidades para
criarmos relatórios profissionais para suas aplicações Delphi, assim, no próximo artigo veremos como criar gráficos, cross-tab, trabalhar com templates
(semelhante à herança) etc.
Um grande abraço a todos e até a próxima!
Sobre o autor
Luciano Pimenta
Luciano Pimenta (NOVO DOMINIO: www.lucianopimenta.com) é desenvolvedor Delphi/C#
para aplicações Web com ASP.NET, Windows com Win32 e Windows Forms com .NET. Palestrante
da 4ª edição da Borland Conference (BorCon) e da 1ª Delphi Conference.
É MVP Embarcadero, grupo de profissionais que ajudam a divulgar o Delphi no mundo.
Atualmente é desenvolvedor da SoftDesign fábrica de softwares em Porto Alegre-RS.
Autor de mais de 90 artigos e de mais de 600 vídeos aulas publicadas em revistas e sites
especializados, além de treinamentos presenciais e multimídias. É consultor da FP2 Tecnologia
(www.fp2.com.br) onde ministra cursos de programação e banco de dados.
www.lucianopimenta.net
10
julho
2013
TConexao: Gerenciando
seu projeto em ambientes MultiPlataforma parte 1
I
magino que muitos desenvolvedores já experimentaram a situação de avaliar um protótipo em mais de um ambiente, talvez ao
mesmo tempo (homologação em um ambiente em uma máquina
local e entrega em outro ambiente através de uma “build”). Nestes
casos, pode um mesmo Banco de Dados satisfazer tais requisitos
de performance e regras negociais exigidas, executando comumente um
“CRUD” sem comprometer a “rapidez” do tráfego de dados atualizando de
forma imperceptível os controles projetados na camada de apresentação do
seu aplicativo – até aí tudo bem – mas e se houver a necessidade negocial
de evolutivamente abstrair a camada de dados ? O seu aplicativo “decide”
qual base ele irá rodar – e sem prejuízo de instabilidade – este processo será
transparente para o usuário final.
Porque não implantar uma forma otimizada de se obter eficientemente
esta característica de multiplataforma? O Framework Itil já dizia: “As Empresas
buscam inovar seus processos para se tornarem mais eficientes e competitivas. Inovações que dão certo transformam-se em melhores práticas”. Esta
implementação já foi devidamente testada, é livre, tem uma boa resposta de
execução, é acessível a todos e por isso justifica-se em uma boa prática de
serviço. Ambiciosamente podemos entregar este valor em potencial levando
ao aumento dos resultados desejados dos clientes. Em suma, mais do que
funciona.
O cliente tem uma exigência que é crescente; nós devemos estar sempre
preparados para prontamente atendê-las; podemos no caso de um entrega
rodá-la localmente em um pen-drive ou em seu próprio HD com um baixo volume de dados utilizando um arquivo XML (apenas para verificar basicamente
seu funcionamento e sem muitos requisitos de software), ou executá-la em um
ambiente de desenvolvimento cliente-servidor exigindo todos os requisitos implementados até o momento, simulando realmente sua performance e demais
quesitos (disponibilidade, confiabilidade, sustentabilidade, etc) – escalando
qual dataset ele irá acessar – e em tempo de execução! Portanto, imagine um
software ser independente do seu banco de dados - e aqui é referido de forma
abstrata – valendo-se tanto de um mero arquivo XML (construí um parser para
isso), tanto para um Sistema baseado em Arquivos ou um Sistema Gerenciador
de Banco de Dados: tem para estas três finalidades - e finalmente o sistema
está independente de tudo: nasce a classe Tconexao.
Nós temos a seguinte missão de fazer todo o “meio-de-campo” entre a
camada de apresentação e a de negócio: enquanto as telas do cliente fazem as
chamadas para as classes de negócio, elas por sua vez vão se comunicar com a
nossa classe de conexão para chamar o banco de dados desejado, processando a chamada (select, update, etc) e finalmente a retornando (um dataset,
stringlist, etc) - conforme pode ser entendido pela figura abaixo:
Figura 01 - Como as camadas se comunicam com a classe TConexao. Enquanto
que uma camada chama, outra sempre a escuta e resolve.
Esta funcionalidade é interessante pela praticidade, como dito anteriormente, pois o aplicativo fica livre de implementações distintas de acesso aos
dados; apenas a nossa classe de conexões fará este trabalho de carregamento e alteração deles – toda visualização é produzida pelo carregamento do
componente TClientDataSet – ele sempre será alimentado pela nossa classe
– portanto eles sempre andarão juntos – um sempre se comunicando com o
outro – e consequentemente a base em que ele acessará será sempre desligada, esperando algum evento do usuário ou sistema, como por exemplo de
“comitar” um “update” ou carregar uma lista (para inicialização de uma tela
ou um “refresh”).
Abaixo pode ser visualizado o diagrama de sequência do componente, a
fim de exemplificar o seu funcionamento na prática; não é complicado perceber
sua idéia básica de funcionamento.
Figura 02 - Diagrama de Sequência do componente TConexao.
julho
2013
11
Agora, finalmente, o processo completo de funcionamento de acordo com
a orientação a objetos juntamente com a nossa classe:
nem tampouco as camadas de negócio (nosso exemplo o TUsuario) acessem
quaisquer parâmetros de banco de dados ou sequer se comuniquem com ele:
tudo isso é dever EXCLUSIVO da classe TConexao.
Portanto, basicamente o processo de criação para um novo banco finaliza-se aqui; não é difícil estendê-lo.
Figura 03 - Explicação da operacionalização do funcionamento das camadas
juntamente com as regras negociais e com a classe TConexao.
Primeiros Passos com a classe TConexao:
O primeiro passo a seguir é criar um data-módulo contendo a conexão
com o banco; pelo menos nesta versão o nosso projeto não tem suporte a
metadados; pretendo em uma próxima versão automatizar a criação e detecção
de banco de dados em tempo de execução, onde os bancos são reconhecidos
e listados mas apenas com um TDataModule – ele será criado e destruído a
cada alteração de solicitação de bases – mas isso é tema para outra release
deste componente.
Por agora, devemos estar atentos de que nenhuma conexão deverá estar
pré-configurada porque isso é o dever da classe de conexão realizar – e tampouco este data-módulo recém-criado deverá estar associado ao projeto - ele
deve ser removido dele. Lembre-se sempre de que a aplicação (referindo-se
às telas da camada de apresentação e às classes negociais) “não conhece” o
banco de dados a ser chamado - a classe cria o data-módulo dinamicamente,
executa as operações desejadas retornando ou não o seu processamento
e após sua utilização ela é destruída: e como ficam os acessos data-aware,
que eram utilizados para acessar diretamente um banco de dados qualquer,
editando informações até mesmo de dentro de uma célula de um TDBGrid e
consequentemente gravando e “comitando” a transação tudo “de uma vez
só”? A resposta agora é o TClientDataSet - ele vai representar em memória
nosso DataSet e exibir com qualquer componente data-aware desejado sem impactar diretamente uma transação com o banco de dados - qualquer
transação será feita agora pela classe TConexao, pois através dela solicitará as
transações com o banco de dados, cuidando totalmente de sua persistência.
Continuando, o segundo passo é acrescentar neste data-módulo um
ClientDataSet para cada classe que será utilizada e persistida na aplicação:
como a classe de conexão e as demais utilizam a abordagem orientada a
objetos, cada ator no sistema será realmente um objeto negocial; no nosso
exemplo existe um ator usuário, portanto foi criada uma classe TUsuario para
ele, e assim por diante.
O terceiro e último passo são os métodos (personalizados) que deverão
ser criados para manipulação total de seu novo acesso ao banco de dados:
deverão ser implementadas chamadas de configuração, dll´s necessárias (que
deverão ser levadas juntas ao executável e mais o client do banco, se for o caso)
e demais arquivos necessários para o seu gerenciamento, agora encapsulado
pela nossa classe TConexao.
Algumas recomendações:

Não utilizar arquivos arquivos de conexão. Fazer diretamente de
dentro da classe;

Não utilizar ODBC: Fazer diretamente de dentro da classe;

Não permitir que a aplicação (telas da camada de apresentação) e
12
julho
2013
O importante sempre é criar um banco de dados primeiro e depois
suas implementações, sempre estendendo a classe TConexao, criando uma
nova subclasse que irá sobreescrever os métodos BancoExiste, FecharBanco,
CarregarBanco, RecordCount, RetornaDataSet e ObterDataSet – cruciais para
operações básicas de funcionamento – consulta, inclusão, alteração e exclusão
– além de outros métodos descendentes de TDataSet (recordcount).
A seguir, um trecho do código-fonte do método “CarregarBanco” de uma
conexão para o Firebird:
Código 01
function TConexaoFirebird.
CarregarBanco(const cDiretorio:
string): Boolean;
begin
Result := True;
if (cDiretorio = EmptyStr)
then
begin
fVendorLib
:=
FMyGDSVendorLib;
fLibraryName
:=
FMyDBXExpress
FirebirdLibraryName;
fPathDataBase
:=
FUsuariosFDB;
end
else if (cDiretorio =
‘default’) then
begin
fVendorLib
:=
FMyGDSVendorLib;
fLibraryName
:=
ExtractFilePath(Application.
ExeName) +
IncludeTrailingPathDelimiter
(‘BD\FIREBIRD’) +
FMyDBXExpressFire
birdLibraryName;
fPathDataBase
:=
ExtractFilePath
(Application.ExeName) +
IncludeTrailingPathDelimiter
(‘BD\FIREBIRD’) + fPathDataBase;
end
else
begin
fVendorLib
:=
cDiretorio + FMyGDSVendorLib;
fLibraryName
:=
cDiretorio + FMyDBXExpress
FirebirdLibraryName;
fPathDataBase
:=
cDiretorio + fPathDataBase;
end;
Result := ( (DirectoryExists
(fPathDataBase)) and
(FileExists (fLibraryName)) );
if not Result then Exit;
dmFirebird := TdmFirebird.
Create(nil);
try
try
with dmFirebird.
SQLConnection do
begin
Connected
:= False;
VendorLib
:=
dbVendorLib;
LibraryName :=
dbLibraryName;
Params.
Values[‘Database’] :=
fPathDataBase + FUsuariosFDB;
end;
finally
dmFirebird.SQLConnection.
Connected := True;
dmFirebird.SQLQuery.Open;
end;
except
on E: Exception do
begin
Raise EInvalidDataBase.
Create (EErroGeralBancoDeDados
+ #13#10 + E.Message);
Result := False;
end;
end;
end;
Nossa classe tem 3 bancos pré-definidos e configurados que já podem
ser prontamente utilizados em qualquer aplicação, dando início a um desenvolvimento de código mais “facilitado” graças a esta abstração de conexões,
porque não será mais necessário criar outras conexões para o mesmo banco
em diferentes forms ou um criar um data-módulo e associá-lo à aplicação, pois
como isso tudo já foi feito, basta apenas chamar a classe de negócio (no nosso
caso de exemplo, a TUsuario).
São os Bancos:



XML
DBISAM
FIREBIRD
Segue também para distribuição o DDL da tabela:
CREATE TABLE USUARIOS (
CD_USUARIO
INTEGER,
NOME
VARCHAR(200),
DATANASCIMENTO DATE,
ENDERECO
VARCHAR(200),
CIDADE
VARCHAR(10),
ESTADO
VARCHAR(3),
CEP
VARCHAR(20),
CARGO
VARCHAR(200),
SETOR
VARCHAR(200),
TELEFONE
VARCHAR(20),
FOTO
BLOB SUB_
TYPE 0 SEGMENT SIZE 500,
VLR_SALARIO
FLOAT,
NR_MATRICULA
INTEGER
);
No nosso projeto foram criados campos dos tipos String, Integer, Float e
Blob. Todos os tipos serão validados de acordo com o formato suportado pelo
banco – isto é – nem todos possuem as mesmas validações para data, ponto
flutuante, etc. Um ponto importante a ser mencionado é que todos os setters
são do tipo String – isso facilita os testes de validação de tipo de dados antes
de sua conversão para o seu formato original.
Alguns campos são obrigatórios, requeridos nos setters e retornando um
campos de mensagens de retorno (FCodigoMensagem e FMensagem).
Uma amostra do fonte da propriedade da data de nascimento (método
write):
Código 02
procedure TUsuario.
setDataNascimento(const Value:
String); //obrigatório
var
AuxDia, AuxMes, AuxAno,
fValue: string;
begin
fValue := Value;
fValue := Conexao.
GetDateFormat(Value);
if not (Biblioteca.
EmptyDate(fValue)) then
julho
2013
13
begin
if not Biblioteca.
IsValidDate(fValue) then
begin
FCodigoMensagem
:=
CodMensagemCampoInvalido;
FMensagem
:=
‘Campo Data Nascimento
Inválido!’;
FDataNascimento
:=
‘’;
end
else
begin
FCodigoMensagem
:=
CodMensagemNormal;
FMensagem
:=
EmptyStr;
FDataNascimento
:= Conexao.
FormatDefaultDate(fValue); //
Trim(Value);
end;
end
else
begin
FDataNascimento
:=
fValue;
FCodigoMensagem
:=
CodMensagemCampoEmBranco;
FMensagem
:=
‘Campo Data Nascimento em
Branco!’;
Alterou
:=
False;
end;
end;
‘, CIDADE = ‘ +
QuotedStr(Cidade) + ‘, ESTADO =
‘ + QuotedStr(Estado) +
‘, CEP = ‘ + QuotedStr(CEP)
+ ‘, CARGO = ‘ +
QuotedStr(Cargo) + ‘, SETOR = ‘
+ QuotedStr(Setor) +
‘, TELEFONE = ‘ +
QuotedStr(Telefone) + ‘,
VLR_SALARIO = ‘ + (Conexao.
FormatFloat(Salario)) +
‘, NR_MATRICULA
= ‘ + (Conexao.
FormatInteger(Matricula)) +
‘ WHERE NOME = ‘ +
QuotedStr(Nome);
Conexao.Executar(Query);
end
Explicação: A classe TConexao, através de GetConexao, e utilizando o
type tBDConexao (type tBDConexao = (bdConexaoXML, bdConexaoDBISAM,
bdConexaoFirebird) – fará a instanciação com a subclasse desejada de acesso
ao banco, e a melhor de todas as coisas é que em runtime os bancos podem
ser trocados – selecionando, alterando, etc – com a maior versatilidade!
Em suma, toda vez que uma classe quiser acessar um banco, ela na verdade
acessa uma instância da classe de conexão (TConexao), onde esta por sua vez
referenciará-lo em memória. Toda a interface de comunicação e operacionalização será realizada somente através dela, estando disponível até ser destruída.
Abaixo, uma seleção de alguns métodos importantes utilizados na classe
TConexao - detalhe para os métodos ObterDataSet, CarregarFoto e GetConexao (sintaxe: Conexao := Conexao.GetConexao(fbdConexao)) - que são os
métodos mais importantes do processo de comunicação entre os objetos de
negócio e dados.
Código 04
Agora, um exemplo do código-fonte padrão para verificar se existe um
registro e em caso positivo atualizá-lo:
Código 03
Query := ‘SELECT * FROM
USUARIOS WHERE NOME = ‘ +
QuotedStr(Nome);
if Conexao.RecordCount(Query)
> 0 then //atualiza
begin
Query := ‘UPDATE
USUARIOS SET ENDERECO =
‘ + QuotedStr(Endereco)
+ ‘, DATANASCIMENTO =
‘ + QuotedStr(Conexao.
FormatDate(DataNascimento)) +
14
julho
2013
procedure TConexao.
ObterDataSet(const myDataSet:
TDataSet; var myClientDataSet:
TClientDataSet);
var
fDataSet: TDataSet;
i {, k}: integer;
FName: string;
begin
if not Assigned(myDataSet)
then Exit;
if not Self.BancoInstanciado
then
begin
Application.MessageBox(PCha
r(Format(EBancoNaoInstanciado,
[FUsuariosFDB])), ETituloErro,
MB_OK + MB_ICONERROR);
Abort;
end;
fDataSet :=
TClientDataSet(myDataSet);
fDataSet.First;
if not myClientDataSet.Active
then
myClientDataSet.
CreateDataSet
else
myClientDataSet.
EmptyDataSet;
myClientDataSet.
DisableControls;
try
while not fDataSet.Eof do
begin
myClientDataSet.Insert;
for i := 0 to fDataSet.
FieldCount - 1 do
begin
// myClientDataSet.
Fields[i].Assign(fDataSet.
Fields[i]);
myClientDataSet.
FieldDefs.Items[i].DataType :=
(fDataSet.FieldDefs.Items[i].
DataType);
myClientDataSet.
FieldDefs.Items[i].DisplayName
:= (fDataSet.FieldDefs.
Items[i].DisplayName);
case myClientDataSet.
FieldDefs.Items[i].DataType of
ftString:
myClientDataSet.Fields[i].
AsString := Self.
DefaultDateFormat2(fDataSet.
Fields[i].AsString);
ftDate:
myClientDataSet.Fields[i].
AsString := Self.
DefaultDateFormat2(fDataSet.
Fields[i].AsString);
ftFloat:
myClientDataSet.Fields[i].
AsFloat := Biblioteca.
ArredondaComDecimais( fDataSet.
Fields[i].AsFloat, 2);
else
myClientDataSet.
Fields[i].AsString :=
fDataSet.Fields[i].AsString;
end;
end;
myClientDataSet.Post;
fDataSet.Next;
end;
finally
myClientDataSet.
EnableControls;
end;
end;
procedure TConexao.
ObterDataSet(const myDataSet:
TDataSet; var myStringList:
TStringList);
var
fDataSet: TDataSet;
i: integer;
begin
if not Assigned(myDataSet)
then Exit;
fDataSet :=
TClientDataSet(myDataSet);
fDataSet.First;
if not Assigned(myStringList)
then myStringList :=
TStringList.Create;
while not fDataSet.Eof do
begin
myStringList.Add(fDataSet.
Fields[0].AsString);
fDataSet.Next;
end;
end;
function TConexao.
RecordCount(Query: String):
integer;
begin
if Self is TConexaoDBISAM
then
Result :=
TConexaoDBISAM(Self).
RecordCount(Query)
else if Self is TConexaoXML
then
Result := TConexaoXML(Self).
RecordCount(Query)
else if Self is
TConexaoFirebird then
Result :=
TConexaoFirebird(Self).
RecordCount(Query);
end;
function TConexao.
julho
2013
15
RecordCount(const myDataSet:
TDataSet): integer;
begin
Result :=
(TDataSet(myDataSet).
RecordCount);
end;
function TConexao.
RetornaDataSet(Query: String):
TDataSet;
begin
if Self is TConexaoDBISAM
then
Result :=
TConexaoDBISAM(Self).
RetornaDataSet(Query)
else if Self is TConexaoXML
then
Result := TConexaoXML(Self).
ExecutaSelect(Query)
else if Self is
TConexaoFirebird then
Result :=
TConexaoFirebird(Self).
RetornaDataSet(Query);
end;
function TConexao.
CarregarFoto(const MyFile:
string; const myDataSet:
TDataSet; const MyFieldName:
string = ‘FOTO’): Boolean;
function
GetTableNameFromXMLSyntax(const
mySQLXML: string): string;
begin
if System.
Pos(AnsiUpperCase(‘where’),
AnsiUpperCase(mySQLXML)) > 0
then
Result := trim(Before(af
ter(AnsiUpperCase(mySQLXML),
‘FROM’), ‘WHERE’));
end;
var
S: TFileStream;
fClientDataSet:
TClientDataSet;
auxTableName: string;
auxFilterFields: string;
begin
if Self is TConexaoXML then
begin
if (Self.FiltroXML = ‘’)
then Exit;
16
julho
2013
auxTableName :=
GetTableNameFromXMLSyntax(Self.
FiltroXML);
auxTableName :=
auxTableName + ‘.xml’;
fClientDataSet
:= TConexaoXML(Self).
CarregarBanco(auxTableName);
auxFilterFields :=
Before(after(AnsiUpperCase(Self.
FiltroXML),’WHERE’), ‘ORDER’);
fClientDataSet.Filter :=
auxFilterFields;
fClientDataSet.Filtered
:= True;
try
fClientDataSet.Close;
fClientDataSet.Open;
if not (fClientDataSet.
state in dsEditModes) then
fClientDataSet.Edit;
TBlobField(fClientDataSet.
FieldByName(MyFieldName)).
LoadFromFile(MyFile);
fClientDataSet.Post;
fClientDataSet.
SaveToFile(TConexaoXML(Self).
CaminhoXMLAtual, dfXML);
except
on E: Exception do
begin
Raise EInvalidDataBase.
Create(EErroGeralBancoDeDados +
#13#10 + E.Message);
Result := False;
end;
end;
end
else
if Self is TConexaoDBISAM
then
begin
S := TFileStream.
Create(MyFile ,fmOpenRead);
with (myDataSet) do
begin
try
try
Edit;
TBlobField(Fie
ldByName(MyFieldName)).
LoadFromStream(S);
Post;
except
on E: Exception do
begin
Raise
EInvalidDataBase.
Create(EErroGeralBancoDeDados +
#13#10 + E.Message);
Result := False;
end;
end;
finally
S.Free;
end;
end;
end
else if Self is
TConexaoFirebird then
begin
fClientDataSet :=
dmFirebird.ClUsuario;
try
fClientDataSet.Close;
fClientDataSet.Open;
fClientDataSet.Refresh;
if not (fClientDataSet.
state in dsEditModes) then
fClientDataSet.Edit;
TBlobField(fClientDataSet.
FieldByName(MyFieldName)).
LoadFromFile(MyFile);
fClientDataSet.Post;
fClientDataSet.
ApplyUpdates(0);
except
on E: Exception do
begin
Raise EInvalidDataBase.
Create(EErroGeralBancoDeDados +
#13#10 + E.Message);
Result := False;
end;
end;
end;
end;
function TConexao.
GetConexao(const typeConexao:
tBDConexao): TConexao;
begin
if Assigned(Result) then
closeConexao;
case typeConexao of
bdConexaoDBISAM:
Result
:= TConexaoDBISAM.create;
bdConexaoXML:
Result
:= TConexaoXML.create;
bdConexaoFirebird: Result
:= TConexaoFirebird.create;
end;
end;
Nós só precisamos também criar uma classe negocial – no nosso caso
a classe de usuários TUsuario – que fará a “ponte” entre a camada de apresentação com ela, provendo por meio do encapsulamento funcionalidades
básicas negociais disponíveis para os módulos de apresentação requeridos
– não é interessante modularizar? Alterações em apenas um lugar – e todas
as chamadas automaticamente atualizadas – abstração gera produtividade se
bem empregada, como demonstra esta nossa construção.
Abrir um conjunto de dados (select) agora ficou mais simples de escrever - somente uma chamada de método (no caso, “AbreTabelaUsuarios”) é
necessária. Evoluindo o processo, temos inúmeras vantagens:

Implementação da Orientação a Objetos: como citado acima, é a
facilidade em reutilização de código em módulos, outros sistemas, etc;

Abstração realizada pelas classes de conexão: não é preciso (e nem
deve) executar quaisquer comandos SQL ou acessar quaisquer base de dados
diretamente na camada de apresentação;

Encapsulamento de DataSets: essa nossa infraestrutura padronizou a nomenclatura básica de execução de SQL´s, onde cada sentença é uma
constante passada como parâmetro em um método de obter dataset do objeto
TConexao. Logo abaixo segue um exemplo destacado deste item:
Código 05
function TUsuario.
AbreTabelaUsuarios: TDataSet;
begin
Result := Conexao.RetornaData
Set(sqlSelectAllFieldsOrderByNo
me);
end;
Abaixo é mostrado o método
SetDataBase, onde é solicitada
a troca de um banco. Note que
o nome dos bancos fazem parte
julho
2013
17
de um tipo chamado tBDConexao,
como citado acima, que é
referenciado como um parâmetro
constante:
type tBDConexao =
(bdConexaoXML, bdConexaoDBISAM,
bdConexaoFirebird);
procedure TForm1.
cbSelecionaBDChange(Sender:
TObject);
begin
case cbSelecionaBD.ItemIndex
of
0: Usuario.
SetDataBase(bdConexaoXML);
1: Usuario.
SetDataBase(bdConexaoDBISAM);
2: Usuario.
SetDataBase(bdConexaoFirebird);
end;
Usuario.
CarregaUsuarios(ClUsuario);
mskCarregaNomes.Text :=
ClUsuarioNome.AsString;
mskCarregaNomesExit(Self);
end;
Nossa classe TConexao também possui métodos de formatação específicos – nossos próprios FormatDate, FormatFloat, dentre outros. Isso porque o
Firebird aceita o formato “yyyy-mm-dd” enquanto que o XML, por exemplo,
aceita somente o formato “dd/mm/yyyy”. Já o FormatInteger formata o XML
para o formato (com aspas simples) ‘meu número’ – enquanto que o Firebird
aceita tanto o número puro (sem aspas) quanto com aspas simples. Já o banco
DBISAM aceita o formato numérico somente sem aspas. Portanto, a classe fará
o intercâmbio de formatos distintos de acordo com a instância apropriada.
Uma coisa interessante é o método GetNextId: ele tem a finalidade de
recuperar o próximo valor de um código, por exemplo, para inseri-lo como uma
chave-primária, ou apenas um valor normal, etc; são várias as possibilidades
de utilização. Ele faz o select de um “Max(coluna)+1”, para retornar sempre o
próximo item sequencial de um registro. Um detalhe especial é como o processo
é feito para o nosso banco XML – ele faz um “Max” em um campo agregado
de um TClientDataSet em criado em tempo de execução!
Código 06
function TConexaoFirebird.
GetNextId(const fFieldName:
string; const fTable: string):
string;
18
julho
2013
var
fId: string;
begin
fId :=
TCustomSQLDataSet(Self.Retorna
DataSet(Format(sqlMaxIndexFrom
FieldNameFormat1, [fFieldName,
fTable]))).Fields[0].AsString;
if (fId = ‘’) then fId :=
‘1’;
Result := fId;
end;
Finalmente, o método CarregaUsuarios alimenta um objeto TClientDataSet de retorno, onde como já mencionado anteriormente armazenará dados
apenas “read-only”: a aplicação jamais sensibilizará-los diretamente na base
de dados. Todo este acesso somente a classe TConexao realizará e de forma
abstrata. E isso é um dos pontos mais interessantes deste projeto: imagina
trocar um banco de dados com sua aplicação rodando, ou enviá-la ao cliente
em uma demonstração do produto com algumas funcionalidades disponíveis
e na versão paga alterar o banco de dados para uma nova base de dados mais
robusta e profissional; esta funcionalidade tornará sem dúvida sua aplicação
isenta de implementações amarradas de dentro da camada de apresentação
(método tradicional de programação), trazendo mais produtividade em termos
de manutenção e desenvolvimento, e consequentemente mais agilidade e
praticidade durante todo o ciclo de vida da aplicação.
Com isso, finaliza-se a primeira parte deste artigo. Na próxima revista
falaremos sobre a implementação de um parser SQL para XML, utilização
do componente de minha autoria para visualização e validação de dados,
interagindo por “debaixo dos panos” – o TMaskeditHV (explicado em edições
anteriores), explicação sobre importantes métodos da nossa classe e finalmente o desenvolvimento de nosso programa de exemplo utilizando esta nossa
classe, em uma simulação real de sua implementação, obtendo e alternando
bases de dados distintas em tempo de execução. Até a próxima, e boa leitura!
Sobre o autor
Hamden Vogel
Analista de Sistemas pós-graduado em Engenharia de Software pela
UPIS e Programador Delphi com larga experiência desde 2000, tem desenvolvido e vendido softwares em Delphi para a África e Estados Unidos,
além do mercado nacional. Colaborou com dicas e componentes para sites
especializados em Delphi. Também desenvolve em outras linguagens como
C/C++, ASP, PHP e .NET.
[email protected]
Android
Trabalhando com o TouchScreen
U
m dos recursos mais poderosos e fascinantes no desenvolvimento utilizando o sistema android é a possibilidade de
poder interceptar eventos na tela com o toque do dedo, o
“TouchScreen”. Temos classes específicas para este trabalho,
a minha idéia neste mês é descrever as principais funcionalidades destas classes junto com seus métodos criando exemplos didáticos e
de grande utilidade no dia-a-dia.
Eventos de Entrada (“Input Events”)
Existem várias maneiras de interceptar os eventos de interação do usuário
com o aplicativo. Ao considerar eventos dentro de sua interface de usuário,
a abordagem é capturar os eventos dentro da região denominada “View”,
fornecendo os meios para fazê-lo. Quando compomos um layout, fazemos
o uso de quantas “Views” forem necessárias, neste caso, podemos observar
vários métodos de retorno de chamada pública que parecem úteis para os
denominados eventos de InPut(entrada) e OutPut(saída). Esses métodos são
chamados pelo Android quando a respectiva ação ocorre no objeto em questão. Por exemplo, quando um componente (um botão) é tocado, o método
“onTouchEvent()” é acionado no objeto. No entanto, a fim de interceptar isso,
você deve estender a classe e substituir o método. No entanto, ampliando
cada objeto “View”, a fim de lidar com um evento como esse não seria prático.
É por isso que a classe “View” também contém uma coleção de interfaces
aninhadas com retornos de chamada que poderemos utilizar com uma maior
facilidade. Estas interfaces, chamadas de “Listeners” (“ouvintes”), são o seu
“bilhete” para capturar a interação do usuário com a interface do aplicativo.
Temos a possibilidade de estender uma classe “View”, a fim de construir por
exemplo um componente personalizado. Outra situação é estender a classe
“Button” para fazer algo mais sofisticado. Neste caso, poderemos definir os
comportamentos de eventos padrão para sua classe usando os manipuladores
de eventos, os (“Event Handlers”)
para serem implementados, sendo:
onKeyDown (int, KeyEvent) - Chamado quando apertamos alguma tecla.
onKeyUp (int, KeyEvent) - Chamado quando soltamos alguma tecla.
onTrackballEvent (MotionEvent) - Chamado quando ocorre um evento
de movimento.
onTouchEvent (MotionEvent) - Chamado quando ocorre um evento de
movimento da tela com o toque do dedo.
onFocusChanged (boolean, int, Rect) - Chamado quando recebemos ou
perdemos o foco.
Demonstrei os métodos acima a fim de apresentar os manipuladores
existentes. Daremos importância ao evento “OnTouchEvent”.
Entendendo a classe “MotionEvent”
A classe base para suporte TouchScreen é a classe “MotionEvent” onde
é passado para as “Views” através do método onTouchEvent () (abordado
anteriormente) podendo também ser sobrecarregado . Vejamos na Imagem
01 a hierarquia desta classe para maiores informações.
Manipuladores de Eventos (“Event Handlers”)
De acordo com a própria documentação do Android, quando estamos
construindo um componente a partir da classe “View” teremos vários métodos
Figura 01: Estrutura da classe “MotionEvent”.
A classe “MotionEvent” contém inúmeros métodos e constantes como
julho
2013
19
por exemplo: informações das coordenadas X, Y e capturas de movimentos.
Como foi discutido anteriormente, devemos utilizar a classe “View” para
implementarmos o método OnTouchEvent().
Principais métodos e constantes
- GetAction(): Este método recebe a ação que foi executada, fornecendo
as seguintes constantes para determinar a ação.
ACTION_DOWN: Ação que ocorre quando um toque foi iniciado contendo
o local de partida inicial do movimento.
ACTION_UP: Esta constante é acionada quando um gesto pressionado
tenha terminado, contendo a localização final, bem como todos os pontos
intermediários desde o último movimento.
ACTION_MOVE: Ação que ocorre entre as constantes ACTION_DOWN e
ACTION_UP. (A movimentação em si)
Todas as constantes abordadas acima retornam um valor inteiro.
- GetY(): Este método retorna um valor do tipo “Float” das coordenadas
“Y” (linha vertical). Este valor pode ser transformado para inteiro.
- GetX():Este método retorna um valor do tipo “Float” das coordenadas
“X” (Linha horizontal). Este valor pode ser transformado para inteiro.
Classe “CriarImagem.java”
Antes de iniciarmos a codificação desta classe será necessário adicionar
uma imagem qualquer em nosso aplicativo. Basta copiar e colar no diretório
“res/drawable-hdpi”. Ver Imagem 03.
Exemplo 1 - Movimentando uma Imagem com o toque do
dedo
Nos exemplos deste artigo iremos envolver todos os conceitos aprendidos
anteriormente sendo que neste primeiro criaremos uma imagem dinamicamente. Esta imagem será movimentada com o dedo e usando o “TouchScreen”
na tela do dispositivo. Para isto descreverei todos os passos daqui pra frente.
Inicie uma aplicação do Zero clicando em “File/New/Android Project...”
criando uma classe para estender (extends) para a interface “View”. Clique
sobre o pacote e com o botão direito escolha “New/Class”. É nesta classe onde
criaremos todos os métodos responsáveis pela movimentação da Imagem em
questão. Ver Figura 02.
Figura 03: Imagem adicionada no projeto.
Observação Importante:
Esta imagem deverá ser do tipo “.png” ou “jpg” possuindo o nome escrito
especificamente em caixa baixa para assim evitar problemas de compilação.
Logo em seguida importaremos as bibliotecas que serão utilizadas ao
decorrer do desenvolvimento.
Código 01
package pct.Android_
OnTouchEvent;
Figura 02: Criando a classe “CriarImagem.java”.
20
julho
2013
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.
drawable.Drawable;
import android.util.
AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class CriarImagem
extends View
{
private Drawable imagem;
private int x, y, largura,
altura;
private boolean Clicou;
public int movimento = 12;
A classe principal “CriarImagem” herdará da classe “View”. Utilizaremos
algumas variáveis privadas, sendo uma do tipo “Drawable” responsável por
trabalhar com a imagem, outras do tipo “int” onde definimos a largura, altura,
as coordenadas X, Y e uma booleana indicando se clicamos ou não na imagem.
Código 02
public CriarImagem(Context
context, AttributeSet attrs)
{
super(context, attrs);
imagem =
context.
getResources().getDrawable(R.
drawable.theclub);
largura = imagem.
getIntrinsicWidth();
altura = imagem.
getIntrinsicHeight();
x = 150;
y = 300;
setFocusable(true);
}
No método Construtor passamos dois parâmetros: o “Context” o Contexto
da aplicação e o “AttributeSet” que significa uma coleção de atributos que podem ser utilizados dentro deste método. O método “context.getResources().
getDrawable()” irá recuperar a imagem, os métodos “getIntrinsicWidth()” e
“getIntrinsicHeight()” irão retornar a largura e altura respectivamente, x e y
serão as coordenadas para posicionamento da imagem em relação a tela do
dispositivo. Já o método “setFocusable()” definirá se a “view” irá ou não receber
o foco enquanto estiver em modo “TouchScreen”. Deixaremos como “true”
Código 03
@Override
protected void onDraw(Canvas
canvas)
{
super.onDraw(canvas);
imagem.setBounds(x,y,x+largura
,y+altura);
imagem.draw(canvas);
}
Para desenhar a imagem foi necessário reescrever o método “OnDraw()”
tendo como parâmetro a classe “Canvas”. Passamos as coordenadas X e Y e
a largura e altura utilizando o método “setBounds()” e redesenhamos com o
“draw()” usando a classe “Canvas”. Este método é disparado sempre quando
necessitamos redesenhar a imagem.
Código 04
public boolean
onTouchEvent(MotionEvent
motionEvent)
{
this.x = (int)motionEvent.
getX();
this.y = (int) motionEvent.
getY();
switch(motionEvent.
getAction())
{
case MotionEvent.ACTION_
DOWN:
{
Clicou = imagem.
copyBounds().contains(x,y);
break;
}
case MotionEvent.ACTION_
MOVE:
if (Clicou)
{
this.x = x (largura/2);
this.y = y (altura/2);
break;
}
case MotionEvent.ACTION_
UP :
{
Clicou = false;
break;
}
}
invalidate();
return true;
}
Este é o principal evento que iremos trabalhar. Ele tem como parâmetro
de entrada a classe “MotionEvent” e retorna um valor booleano. No primeiro
momento iremos atribuir às variáveis globais X e Y as respectivas coordenadas com os métodos “getX()” e “getY()”. Faremos um “Case” para comparar
julho
2013
21
as Constantes do método “GetAction()”, o mesmo que recebe a ação que
foi executada, fornecendo constantes para determinar a ação. Na constante
“ACTION_DOWN” com o método “copyBounds()” indicamos que clicamos na
imagem informando suas coordenadas. A constante “ACTION_MOVE”, faz o ato
de mover em si, movimentamos a figura fazendo o cálculo das coordenadas
menos o tamanho dividido por dois, dando uma impressão de movimento. Por
final retiramos o foco com a constante “ACTION_UP”. Resumindo, este método
irá movimentar a Imagem conforme o toque do dedo.
Configurações no “AndroidManifest.xml”
Nas configurações do arquivo “AndroidManifest.xml” não faremos
praticamente nada de mirabolante, apenas setamos o uso da “Activity”
“Android_OnTouchEventActivity” que será comentado logo a seguir. Ver em
seguida a listagem completa.
por exemplo: a cor cinza
que preencherá o fundo onde
movimentaremos a imagem. Ver
código abaixo:.
<?xml version=”1.0”
encoding=”utf-8”?>
<resources>
<string
name=”hello”>Trabalhando com o
evento OnTouchEvent!</string>
<string name=”app_name”>The
Club - Android</string>
<color
name=”cinza”>#D0D0D0</color>
</resources>
Código 04
<?xml version=”1.0”
encoding=”utf-8”?>
<manifest xmlns:android=”http://
schemas.android.com/apk/res/
android”
package=”pct.Android_
OnTouchEvent”
android:versionCode=”1”
android:versionName=”1.0”>
<uses-sdk
android:minSdkVersion=”8” />
<application android:icon=”@
drawable/icon” android:label=”@
string/app_name”>
<activity
android:name=”.Android_
OnTouchEventActivity”
android:label=”@string/app_
name”>
<intent-filter>
<action
android:name=”android.intent.
action.MAIN” />
<category
android:name=”android.intent.
category.LAUNCHER” />
</intent-filter>
</activity>
Criando o “Lay-Out”
O aplicativo possuirá um “LinearLayout” onde conterá um “TextView” e
um componente chamado desenho que faz referência à classe “CriarImagem”.
Ver Imagem 04.
Figura 04: Componentes utilizados.
A Imagem 05 nos dá uma noção do Lay-out.
</application>
</manifest>
Configurações no “Strings.xml”
Este arquivo irá definir algumas
constantes que serão abordadas
ao decorrer do projeto, como
22
Figura 05: Lay-Out do exemplo 1.
julho
2013
O XML correspondente:
Código 05
<?xml version=”1.0”
encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.
android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_
parent”
android:layout_height=”fill_
parent”
>
<TextView
android:layout_width=”fill_
parent”
android:layout_height=”wrap_
content”
android:text=”@string/
hello”
/>
<pct.Android_OnTouchEvent.
CriarImagem
android:id=”@+id/desenho”
android:layout_width=”fill_
parent”
android:layout_height=”fill_
parent”
android:background=”@
color/cinza”
/>
</LinearLayout>
Código 06
package pct.Android_
OnTouchEvent;
import android.app.Activity;
import android.os.Bundle;
public class Android_
OnTouchEventActivity extends
Activity
{
@Override
public void onCreate(Bundle
savedInstanceState)
{
super.
onCreate(savedInstanceState);
setContentView(R.
layout.criarimagem);
}
}
O “SetContentView()” será o método que irá chamar a tela de nosso
aplicativo. Ver Imagem 06.
Exemplo em “Run-Time”
Devemos dar uma atenção especial na linha de código abaixo:
<pct.Android_OnTouchEvent.
CriarImagem />
Percebam que interessante, conseguimos chamar diretamente uma classe
(no caso uma “View”) de dentro do XML, é fantástico!
Codificando o exemplo 1
Seguindo a lógica, a atividade “Android_OnTouchEventActivity” que
codificaremos a seguir, possuirá uma chamada para o lay-out “criarimagem.
xml” que dentro do mesmo teremos a classe “CriarImagem.java”, na qual está
localizada toda a codificação de nosso exemplo.
Figura 06: Movimentando a Imagem “The Club”.
Exemplo 2 – Pintando e escrevendo na Tela com o toque
do dedo
Este segundo exemplo irá pintar e desenhar na tela de nosso aplicativo
com o toque do dedo. Aprenderemos também a trabalhar com a classe “Paint”,
julho
2013
23
“Path” entre outras. Os passos são idênticos ao do exemplo anterior, por isto
detalharei um pouco menos para ficar menos cansativo abordando apenas
os conceitos inéditos.
Chega de conversa e vamos ao trabalho!
Classe “PintarTela.java”
Utilizaremos algumas bibliotecas adicionais para desenvolver este exemplo, veja abaixo:
Código 07
package pct.Android_
OnTouchEvent;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.
AttributeSet;
import android.view.MotionEvent;
import android.view.View;
Temos a classe “android.graphics.Paint” sendo específica para pintura,
estilo, cor. Ela também oferece base para desenhar geometrias, texto e bitmaps. Já a classe “android.graphics.Path” permite realizar contornos e formas
geométricas como: segmentos de linhas retas, curvas quadráticas e cúbicas.
Código 08
public class PintarTela extends
View
{
private Paint paint = new
Paint();
private Path path = new
Path();
Esta classe também herdará da classe “View” e utilizará dois objetos, do
tipo “Paint” e “Path”.
Código 09
public PintarTela(Context
context, AttributeSet attrs)
{
super(context, attrs);
24
paint.setAntiAlias(true);
julho
2013
paint.setStrokeWidth(6);
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.
STROKE);
paint.setStrokeJoin(Paint.
Join.ROUND);
}
Temos o método “setAntiAlias()” que serve para suavizar as bordas de
nosso toque, o “setStrokeWidh()” é o tamanho da linha, o “setColor()” para
definir a cor, o “setStyle()” o estilo e o “setStrokeJoin()” é um tratamento
específico para quando as linhas se juntarem.
Código 10
@Override
protected void onDraw(Canvas
canvas)
{
canvas.drawPath(path,
paint);
}
Método que irá desenhar usando as configurações definidas anteriormente
na classe “Paint”.
Código 11
@Override
public boolean
onTouchEvent(MotionEvent
motionEvent)
{
float eventX =
motionEvent.getX();
float eventY =
motionEvent.getY();
switch (motionEvent.
getAction())
{
case MotionEvent.
ACTION_DOWN:
path.
moveTo(eventX, eventY);
return true;
case MotionEvent.
ACTION_MOVE:
path.
lineTo(eventX, eventY);
break;
default:
return false;
}
}
invalidate();
return true;
Usaremos apenas as constantes “ACTON_DOWN” para dar início do movimento de acordo com as coordenadas definidas fazendo o uso do método
“MoveTo()” e a “ACTION_MOVE”para adicionar uma linha a partir do último
ponto especificado pelas coordenadas (x, y).
Configurações no “AndroidManifest.xml”
Adicionaremos algumas configurações básicas neste arquivo já comentado
mais acima, veja o trecho:
Código 12
...
<activity android:name=”.
Android_OnTouchEventActivity_2”
android:label=”@string/app_
name”>
<intent-filter>
<action
android:name=”android.intent.
action.MAIN” />
<category
android:name=”android.intent.
category.LAUNCHER” />
</intent-filter>
</activity>
...
Criando o “Lay-Out”
Teremos um “LinearLayout” onde conterá um “TextView” e um componente chamado desenho que faz referência à classe “Pintartela”. Ver Imagem 07.
O XML correspondente:
Código 13
<?xml version=”1.0”
encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.
android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_
parent”
android:layout_height=”fill_
parent”
>
<TextView
android:layout_width=”fill_
parent”
android:layout_height=”wrap_
content”
android:text=”@string/
hello”
/>
<pct.Android_OnTouchEvent.
PintarTela
android:id=”@+id/desenho”
android:layout_width=”fill_
parent”
android:layout_height=”fill_
parent”
/>
</LinearLayout>
Codificando o Exemplo 2
Seguindo os passos que já foram descritos acima foi necessário criar
uma classe estendendo de uma “Activity” para invocarmos o XML contendo a
classe “PintarTela”. O método “SetContentView()” irá chamar a tela de nosso
aplicativo. Ver Imagem 08.
Abaixo código completo.
Código 14
package pct.Android_
OnTouchEvent;
import android.app.Activity;
import android.os.Bundle;
public class Android_
OnTouchEventActivity_2 extends
Activity
{
@Override
public void onCreate(Bundle
savedInstanceState)
Figura 07: Lay-Out do exemplo 2.
julho
2013
25
{
super.
onCreate(savedInstanceState);
setContentView(R.
layout.pintartela);
}
}
Exemplo em “Run-Time”
Referências
http://developer.android.com/reference/android/view/MotionEvent.html
http://developer.android.com/reference/android/view/View.html
Conclusões
Uma das formas de interceptar os eventos de interação do usuário com o
aplicativo é capturar estes eventos dentro de uma região chamada “View”. É
a partir desta classe que discorremos o artigo deste mês. Trabalhamos com o
evento “OnTouchEvent()”, que é o evento que capta o toque do dedo na tela do
dispositivo e a classe “MotionEvent”, classe base para suporte para esta tarefa
junto com métodos e constantes. No primeiro exemplo procurei demonstrar
como movimentar uma imagem com o “TouchScreen”, já no segundo como
pintar e escrever na tela do aplicativo. Esta é uma das inúmeras formas de usar
os recursos “TouchScreen” na programação Android.
Espero que tenham gostado! Deixo aqui um abraço e nos vemos no mês
que vem!
Sobre o autor
Thiago Cavalheiro Montebugnoli
adora aprender novas tecnologias. Formado pela Faculdade de Tecnologia de Botucatu
– SP (FATEC), 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 é colunista mensal da Revista The Club Megazine e é consultor
Técnico do The Club. 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]
Figura 08: Desenhando e Pintando em “Run-Time”.
26
julho
2013
As principais mudanças
Delphi XE4
O
Delphi XE4 já está disponível para download, e conta com
várias novidades que lhes apresentarei neste artigo.
A primeira delas, está na exclusão da RAD Studio
que na versão anterior trazia quatro IDEs que podíamos
utilizar para desenvolver o Delphi, C++, PHP e .NET. No XE4
o Delphi XE4 Prism. Com isso a RAD Studio irá ficar com Delphi, C++Builder
e HTML5 Builder.
Aplicações para iPhone e iPad e os controles de interface
do iOS.
Agora podemos desenvolver aplicações usando um componente de calendário para escolhermos uma data em um aplicativo iOS como mostra a figura 01.
Entre as principais mudanças no Delphi XE4, estão:
•
Desenvolvimento de aplicações multidispositivos para iOS, Windows
e Mac.
•
Desenvolvimento de aplicações para iPhone e iPad.
•
Controles de interface do iOS.
•
Rápida criação de protótipos.
•
Suporte aos bancos de dados IBLite e SQLite iOS.
•
Suporte à cliente multicamadas para DataSnap, web services e
bancos de dados corporativos.
•
Acesso a mais bancos de dados em mais dispositivos e com maior
facilidade na FireDAC.
•
Plataforma de Aplicações FireMonkey FM3.
Figura 01: Adicionando o componente TcalendarEdit
Selecionando o componente TcalendarEdit, no Object Inspector, abriremos a aba Events clicando duas vezes no espaço vazio ao lado de onchange.
Escreveremos o código da seguinte forma:
Explicarei então, rapidamente cada um destes novos recursos.
Código 01
Desenvolvimento de aplicações multidispositivos para iOS,
Windows e Mac.
As vantagens de desenvolver aplicações multidispositivos, significa que não
precisaremos manter projetos de desenvolvimento separados para criar uma
aplicação de modo nativo em múltiplas plataformas (iOS, Windows e Mac).
O desenvolvimento de aplicações verdadeiramente nativas permite que
criemos aplicações nativas livres de scripts, que tiram o máximo proveito das
capacidades e desempenho dos dispositivos subjacentes. Resultando em
aplicações mais rápidas e ricas que agradarão ainda mais seus usuários finais.
procedure TForm25.
CalendarEdit1Change(Sender:
TObject);
begin
ShowMessage(FormatDateTime(‘d
ddddd’, CalendarEdit1.Date));
end;
Podemos também usar um componente de botão com estilos diferentes
em um aplicativo iOS como mostra a figura 02.
julho
2013
27
Figura 04: Adicionando itens no ComboBox.
Essa ferramenta conta com diversos recursos, porém, não entraremos
em detalhes neste artigo.
Agora veremos como usar o componente TWebBrowser em um aplicativo
iOS.
Utilizaremos os seguintes componentes:
TToolBar
TButton
TEdit
A aplicação vai ficar como a figura 05:
Figura 02: Estilos do botão.
Usando componentes combobox para escolher itens de uma lista em um
aplicativo iOS como mostra a figura 03.
Figura 03: Adicionando ComboBox na aplicação
Depois de adicioná-lo, podemos observar o componente ComboBox no
form, clicando com o botão direito do mouse no componente ComboBox e
selecionando itens do editor, abri-rá uma janela como mostra a figura 04.
28
julho
2013
Figura 05: Componentes adicionados.
Antes de implementar manipuladores de eventos, primeiramente implantaremos um método comum para abrir uma página web com base na
propriedade Text do Tedit.
Código 02
procedure OpenURL; next to {
Private declarations }
type
Tform34 = class(TForm)
ToolBar1: TToolBar;
Button1: TButton;
Edit1: TEdit;
WebBrowser1:
TwebBrowser;
private
Procedure OpenURL;
Public
end;
Aperte CTRL + SHIFT + C para criar o procedimento.
Rápida criação de protótipos
O Delphi XE4 fornece um fluxo de trabalho aperfeiçoado e otimizado,
que automatiza a distribuição e a depuração tanto no simulador quanto no
dispositivo. Crie com rapidez protótipos de suas aplicações com layouts para
cada tipo de dispositivo, passando com agilidade da prototipagem à produção.
Integre o feedback rapidamente, distribuindo seu protótipo diretamente no
dispositivo de destino, usando objetos e códigos reais do framework.
Suporte aos bancos de dados IBLite e SQLite iOS
O Delphi XE4 inclui suporte a banco de dados iOS local para SQLite e InterBase embutido via dbExpress, FireDAC e IBX. Você ainda recebe uma licença
de distribuição gratuita e ilimitada do IBLite para iOS
Suporte a cliente multicamadas para DataSnap, web services
e bancos de dados corporativos
O Delphi XE4 inclui conectividade integrada a banco de dados de classe
corporativa ou ISV, computação nas nuvens e middleware. Você conta com
suporte integrado a SQL Server, Oracle, Sybase, DB2, InterBase, SQL Anywhere,
SQLite, MySQL e muitos outros bancos de dados embutidos e de servidor, assim
como serviços nas nuvens, incluindo Windows Azure e Amazon.
Código 03
Procedure TForm34.OpenURL;
Begin
WebBrowser1.Navigate(Edit1.
Text);
end;
A linha “WebBrowser1.Navigate(Edit1.text); “ iremos programar conforme
o nome componente.
No evento OnChange do edit digite OpenURL;
E no evento KeyDown do edit digite o seguinte código.
Código 04
Procedure Tform34.
Edit1KeyDown(Sender; bar Key:
Word;
var KeyChar: char; Shift:
TshiftState);
Begin
If (key = vkReturn) then
Begin
OpenURL;
Button1.SetFocus;
end;
end;
Pronto agora é só testar e usar a criatividade para melhorar a interface
da aplicação.
Acesse mais bancos de dados em mais dispositivos e com
maior facilidade com a FireDAC
A FireDAC dá a você acesso direto, nativo e de alta velocidade do Delphi a
InterBase, SQLite, MySQL, SQL Server, Oracle, PostgreSQL, DB2, SQL Anywhere,
Advantage DB, Firebird, Access, Informix, DataSnap e muitos outros bancos
de dados mobile e corporativos, locais ou embutidos. Migre com facilidade
do BDE e de outras tecnologias.
Plataforma de Aplicações FireMonkey FM3
O Delphi XE4 tira máximo proveito do FireMonkey® FM3, a plataforma
de última geração para construção de aplicações multidispositivos verdadeiramente nativas a partir de uma única base de código. Você codifica uma
única vez e o framework aperfeiçoa sua aplicação para múltiplas plataformas
de destino. Componentes visuais dão a você blocos de construção extensíveis
e reutilizáveis para que se obtenha um desenvolvimento mais rápido e altamente manutenível.
Novo Compilador
Para oferecer suporte aos dispositivos móveis foram criados dois novos
compiladores, um para o simulador do iOS e outro para o dispositivo físico
(ARM), já que as arquiteturas são distintas.Desta forma agora temos:
•
•
•
•
•
Win32 compiler (DCC32)
Win64 compiler (DCC64)
Mac compiler (DCCOSX)
iOS Simulator compiler (DCCIOS32)
iOS ARM compiler (DCCIOSARM)
A novidade está nos dois últimos, os quais seguem um desenho totalmente
novo, baseados em um padrão chamado LLVM (http://llvm.org). Podemos
julho
2013
29
entender o LLVM como um conjunto de módulos e ferramentas reutilizáveis
para compiladores, utilizados amplamente por diversos compiladores nativos,
entre eles o próprio Xcode, nativo da Apple.
faça uso de construções como TStringBuilder e TStringHelper para a manipulação de strings. Um trecho de código utilizando estas classes para ilustrar:
Mudanças no Delphi Language
Para suportar dispositivos móveis, o novo compilador impõe algumas
mudanças de linguagem. Na verdade, mais do que mudanças, trata-se de uma
grande evolução da linguagem Delphi. No futuro, estes e outros avanços serão
portados também para os compiladores Win32, Win64 e OSX.
Automatic Reference Counting (ARC)
O ARC traz o melhor dos dois mundos: gerenciamento automático de
memória sem a necessidade de um garbage collection, tido como um grande
vilão de performance nas linguagem que o utilizam. Em resumo o ARC gerencia
o ciclo de vida de objetos sem que você tenha que se preocupar com o “Free”.
Código 05
Class procedure TMySimpleClass.
CreateOnly;
Var
MyObj: TMySimpleClass;
Begin
MyObj := TMySimpleClass.Create;
MyObj.DoSomething;
End;
No exemplo acima, o objeto MyObj será removido da memória assim
que sair do escopo. Importante ressaltar que a sintaxe tradicional (try/finally
fazendo o Free manual do objeto) continua suportada, portanto as migrações
de código estão garantidas.
Também está disponível o pattern Dispose e suporte a [Weak] References.
Você pode encontrar mais sobre isso aqui: http://edn.embarcadero.com/
article/43073.
Tipo String
Todos os tipos string existentes (AnsiString, UTF8String, RawByteString,
WideString, AnsiChar, PAnsiChar, PWideChar, OpenString, ShortString) agora
estão simplificados em um único: String. Além disso, as strings agora são
0-based ao invés de 1-based. Isso significa que alguns códigos podem precisar
de revisão, mas as funções de manipulação de strings já estão preparadas
para esta mudança.
Outra importante mudança é que as strings passarão a ser “imutáveis”,
devido ao gerenciamento de memória dos dispositivos móveis. Nesta versão
do compilador tudo continua conforme anteriormente, você receberá apenas
uma warning alertando que, futuramente, construções como a exibida abaixo
não será mais suportada:
Outras mudanças
1. Ponteiros não são suportados no mundo mobile
2. Não há suporte para Inline assemby
3. Evite chamadas a APIs diretamente, esteja pronto para as novas
plataformas que virão.
4. Prefira utilizar as units cross-plataform, especialmente ao manipular
arquivos (unit IOUtils)
Informações e recursos para pesquisa
5. Páginas principais dos produtos: RAD Studio, Delphi, C++ Builder,
HTML5 Builder
6. Documentação on line: http://docwiki.embarcadero.com/RADStudio/XE4/en
7. Tutorial Delphi para iOS: Delphi iOS Application Development
8. Link para download do trial: https://downloads.embarcadero.com/
free/rad-studio
9. Trial via Instant Trial (AppWave): http://windowsapps.com/rad-studio-trial.html?trial=1
10. Tudo o que há de novo no Delphi e C++ Builder XE4
11. O que há de novo especificamente no C++ Builder 64 bit
12. Lista de correções do Delphi e C++ Builder XE4: http://edn.embarcadero.com/article/43068
13. White Paper do Marco Cantu sobre todas as mudanças na linguagem: http://www.embarcadero.com/resources/white-papers/application-development
Conclusão
Espero que tenham gostado do artigo, resumi as principais alterações que
foram feita no Delphi XE4 que considero importante.
Até a próxima.
Sobre o autor
José Antônio P. M. de Paula
Consultor Técnico no The Club. Cursa o último período da FATEC
(Botucatu) no curso de Informática para Negócios.
str1[3]:=’w’;
[email protected]
Portanto é altamente recomendado que, para seus novos códigos, você
30
julho
2013
julho
2013
05
julho
2013
Download