segunda-feira, 26 de julho de 2010
Algumas dicas Uteis
{ Inclua na seção uses: MMSystem }
PlaySound('C:\NomedoArquivodeSom.wav', 1, SND_ASYNC);
--------------------------------------------------------------------------------------------------------------
Enviar Arquivo para lixeira :
Inclua na seção uses: ShellApi
{ Coloque a procedure abaixo na seção implementation }
procedure ArqParaLixeira(const NomeArq: string; var MsgErro: string);
var
Op: TSHFileOpStruct;
begin
MsgErro := '';
if not FileExists(NomeArq) then begin
MsgErro := 'Arquivo não encontrado.';
Exit;
end;
FillChar(Op, SizeOf(Op), 0);
with Op do begin
wFunc := FO_DELETE;
pFrom := PChar(NomeArq);
fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_SILENT;
end;
if ShFileOperation(Op) <> 0 then
MsgErro := 'Não foi possível enviar o arquivo para a lixeira.';
end;
{ - Coloque um botão no Form;
- Altere o evento OnClick do botão conforme abaixo: }
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
begin
ArqParaLixeira('c:\Diretorio\Teste.doc', S);
if S = '' then
ShowMessage('O arquivo foi enviado para a lixeira.')
else
ShowMessage(S);
end;
--------------------------------------------------------------------------------------------------------------
Fechar o Windows:
{ Reinicia o Windows }
ExitWindowsEx(EWX_REBOOT, 0);
{ Desliga o Windows }
ExitWindowsEx(EWX_SHUTDOWN, 0);
{ Força todos os programa a desligarem-se }
ExitWindowsEx(EWX_FORCE, 0);
{ Você já observou a caixa "Propriedades", aquela que mostra
as propriedades de um arquivo no Windows Explorer, não
aparece na lista do Alt+Tab e tampouco na barra de tarefas?
Isto ocorre porque ela funciona como uma ToolWindow, enquanto
os demais aplicativos funcionam como AppWindow. Porém podemos
mudar o comportamento de nossos programas feito em Delphi
para que se comportem como uma ToolWindow também.
Para experimentar, crie um novo projeto e altere o
Project1.dpr como abaixo (não esqueça do uses):
}
program Project1;
uses
Forms, Windows,
Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}
var
ExtendedStyle : Integer;
begin
Application.Initialize;
ExtendedStyle := GetWindowLong(Application.Handle, gwl_ExStyle);
SetWindowLong(Application.Handle, gwl_ExStyle, ExtendedStyle or
ws_Ex_ToolWindow and not ws_Ex_AppWindow);
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
--------------------------------------------------------------------------------------------------------------
Obter nome do usuário e a empresa registrada no Windows:
Inclua na seção uses: Registry
{ Coloque um botão no form e altere seu evento OnCkick
como abaixo: }
procedure TForm1.Button1Click(Sender: TObject);
var
Reg: TRegIniFile;
S: string;
begin
Reg := TRegIniFile.Create('SOFTWARE\MICROSOFT\MS SETUP (ACME)\');
try
S := Reg.ReadString('USER INFO','DefName','');
S := S + #13;
S := S + Reg.ReadString('USER INFO','DefCompany','');
ShowMessage(S);
finally
Reg.free;
end;
end;
Executar programa do DOS e fechá-lo em seguida
WinExec('command.com /c programa.exe',sw_ShowNormal);
{ Se quizer passar parâmetros pasta adicioná-los após o
nome do programa. Exemplo: }
WinExec('command.com /c programa.exe param1 param2',sw_ShowNormal);
domingo, 25 de julho de 2010
Como gravar posição do form no registro do windows e recuperá-lo
sexta-feira, 23 de julho de 2010
Pesquisa entre faixa de valores com Firebird
var
Campo1, Campo2: string;
Ok1, Ok2: Boolean;
begin
Campo1:= '';
Campo2:= '';
//recupera o código inicial na variável Campo1
InputQuery('Código Inicial',
'Digite o Código onde começará a ser salvo a tabela',
Campo1);
//recupera o código final na variável Campo2
InputQuery('Código Final',
'Digite o Código onde terminará a seleção para ser salvo a tabela',
Campo2);
//se informou os dois códigos
if (Campo1 <> '') and (Campo2 <> '') then
begin
//monta o select utilizando o operador BETWEEN, que faz a pesquisa
//entre uma faixa de valores, seja numérico, caracter ou date/hora
SimpleDataSet1.Close;
SimpleDataSet1.DataSet.CommandText := 'SELECT * FROM TABELA' +
' WHERE CAMPOTABELA BETWEEN ' + Campo1 + ' AND ' + Campo2;
SimpleDataSet1.Open;
end
else
ShowMessage('Valor(es) não digitado(s)');
end;
quinta-feira, 22 de julho de 2010
Usando funções no MySQL
Crie uma pequena tabela com a seguinte estrutura:
CREATE TABLE `clientes` (
`ID` int(11) NOT NULL auto_increment,
`NOME` varchar(45) default NULL,
`DATANASC` date default NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Inclua as linhas abaixo na tabela, ou digite suas próprias informações:
INSERT I NTO `clientes` (`ID`, `NOME`, `DATANASC`) VALUES
(1, 'SILVIO SANTOS', '1971-09-18'),
(2, 'SILLAS SANTOS', '1993-04-27');
Agora, crie a função conforme o exemplo abaixo:
CREATE FUNCTION `idade`(pDATA DATE)
RETURNS varchar(15) CHARSET latin1
NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER
COMMENT ''
Begin
DECLARE vIDADE varchar(30);
IF DATE_FORMAT(NOW( ),'00-%m-%d') >= DATE_FORMAT(pDATA,'00-%m-%d') THEN
SET @idade=DATE_FORMAT(NOW( ),'%Y')-DATE_FORMAT(pDATA,'%Y');
ELSE
SET @idade=DATE_FORMAT(NOW( ),'%Y')-DATE_FORMAT(pDATA,'%Y')-1;
END IF;
RETURN(concat(@idade,' anos'));
end;
Esta função, retornará a idade da pessoa de acordo com o campo DATANASC, da tabela ‘clientes’.
Veja que a função receberá um valor (pDATA, que no nosso exemplo será o campo ‘DATANASC’ de nossa tabela) e fará a subtração da “dat a corrente” pela data do campo DATANASC.
Dentro da função, criamos uma variável (vIDADE), onde armazenaremos o valor a ser retornado pela função. Note também que a função possui retorno do tipo String (RETURNS varchar(30)), que deverá ter o tamanho suficiente para comportar o resultado retornado. Ou seja, se o valor retornado tiver 15 caracteres, o ‘RETURNS’ da função precisa ter tamanho de 15 ou mais caracteres, senão, pode acontecer de não retornar nenhum valor.
Após ter criado a tabela, inserido as linhas na mesma e criado a função, execute a seguinte instrução:
SELECT NOME, idade(DATANASC) IDADE FROM clientes;
No SQL acima, a consulta irá retornar o Nome, e a idade assim: “XX anos”, para cada cliente.
Como você viu, utilizar funções dentro do banco de dados por ser muito mais prático do que fazê-lo dentro da aplicação (me refiro a funções para retorno de dados, claro).
Lembrando que este exemplo é apenas uma base, pois podemos criar funções para qualquer necessdiade, como: calculo de preços, juros de mora, datas, horas, concatenação de campos complexos, junções de tabelas (como nos inner, left e right joins (desde que precise retornar apenas um valor), e muito mais.
Utilizando Barras de Progresso
1 Componente ProgressBar - Paleta Win32;
1 Componente TGauge - Paleta Samples;
1 Componente TTimer – Paleta System;
1 Componente TButton – Paleta Standard;
2 Componentes TRadioButton – Paleta Standard;
procedure TForm1.Timer1Timer(Sender: TObject); begin //Se o RadioButton1 estiver marcado executa o código abaixo if RadioButton1.Checked then begin label2.Caption := inttostr(ProgressBar1.Position)+ '%'; progressbar1.position := progressbar1.position+5; if progressbar1.position = 10 then begin Label1.caption:='Carregando Telas: '; //escreva o que quiser label1.Repaint; end; if progressbar1.position = 35 then begin label1.caption:='Carregando as Tabelas'; label1.Repaint; end; if progressbar1.position = 85 then begin label1.caption:='Finalizando'; label1.Repaint; end; end; //FIM IF RADIOBUTTON1 //Se o RadioButton2 estiver marcado executa o código abaixo if RadioButton2.Checked then begin gauge1.progress := gauge1.progress+5; if gauge1.progress = 10 then begin Label3.caption:='Carregando Telas: '; //escreva o que quiser label3.Repaint; end; if gauge1.progress = 35 then begin label3.caption:='Carregando as Tabelas'; label3.Repaint; end; if gauge1.progress = 85 then begin label3.caption:='Finalizando'; label3.Repaint; end; end; //FIM IF RADIOBUTTON2 end;
ProgressBar1.Position := 0; Gauge1.Progress := 0; Timer1.Enabled := True; if RadioButton1.Checked then while ProgressBar1.Position < 100 do Application.ProcessMessages; if RadioButton2.Checked then while Gauge1.Progress < 100 do Application.ProcessMessages; Timer1.Enabled := False;
//atribuir o valor máximo /qtde de processamento Gauge1.MaxValue := DataSet.RecordCount; while not DataSet.Eof do begin //processamento dos dados DataSet.Next; //ajustar a posição da barra de progresso Gauge1.Progress := Gauge1.Progress; end;
quarta-feira, 21 de julho de 2010
Listas em Memória
Por exemplo, imagine produzir um relatório com uma query envolvendo três tabelas, na ordem de milhões de linhas cada, e dispondo-se de um tempo curto e crítico. Isso pode ser conseguido se transferirmos conscientemente algum trabalho de organização e seleção de dados, para a memória.
A opção pelo tratamento consciente de dados em memória, além de aliviar o trabalho do servidor, poderá dar um aumento significativo de velocidade ao processamento.
Ou seja, são várias as situações que admitem esse tipo de solução, pois nem sempre é conveniente ou viável que se reestruture o banco de dados para atender a uma situação específica. Além disso, existem situações em que é necessária a manipulação de massas de dados que não estão sob controle de um SGBD.
O Delphi disponibiliza um poderoso dispositivo que nos auxilia no tratamento de dados, que são as listas em memória, da classe TSTRINGLIST, as quais permitem criar e manipular listas, de modo simples e eficiente.
Um objeto STRINGLIST dispõe de dois métodos que simplificam o seu uso, Sort e Find, que, combinados, fazem grande parte do trabalho.
Este artigo trata do uso prático desse recurso, e para tanto desenvolveremos 3 casos, de complexidade crescente, dando oportunidade a que sejam apresentadas algumas técnicas interessantes. Os casos são estudados a seguir.
Caso 1. Verificando se existe.
Este primeiro caso é bem simples, meramente ilustrativo dos métodos da classe.
Suponhamos uma tabela, tab_A, que desejamos filtrar, e a condição de seleção é que o dado DADO exista em um arquivo plano.
Seria equivalente ao uso da condição EXISTS, do Sql. sendo que, no nosso caso, a lista com os critérios de seleção está em um arquivo texto.
Vejamos o código, simples, para tratar a situação.
procedure TForm1.pega_do_txt;
var lista : TStringList;
n,ind : integer;
begin
// carrega Txt para a lista, e a ordena
lista := TStringList.Create();
lista.LoadFromFile('c:\revista_delphi\dado.txt');
lista.Sorted := true;
// o SORTED é necessário para uso do FIND, mais adiante
// lê a tabela A, e verifica se existe o dado na lista - "n" é meramente ilustrativo
n := 6;
tab_A.Active := true;
tab_A.First;
while not tab_A.eof do
begin
if lista.Find(tab_A.Fields[n].Value, ind) then
begin
// trata linha selecionada
end;
tab_A.Next;
end;
tab_A.Active := false;
lista.Free;
end;
Comentários:
1. Memória ocupada: Se o arquivo texto tem 10.000 linhas, por exemplo, e o dado buscado tem 20 caracteres de comprimento, a stringlist irá ocupar algo em torno de 0,2MB apenas.
2. A propriedade SORTED foi setada por ser uma condição para o método FIND funcionar.
3. Observe que a posição do DADO na lista ficou anotada no campo ind. Isso será utilizado nos próximos exemplos.
4. Em complemento ao método loadfromfile, que foi utilizado no exemplo, existe o savetofile, que permite que os dados da lista, de uso bem simples, sejam despejados em um arquivo plano.
Caso 2. Indexando.
Suponha agora duas tabelas. Na primeira, tab_A, existe um campo, COD que servirá para buscar um DADO na segunda tabela, tab_B.
Na tab_B, existem dois campos: COD e DADO. Esse dado será recuperado de tab_B sempre que COD for encontrado.
Ou seja: toma-se COD de A e procura-se em B: se encontrado, recupera-se DADO.
O programa fica assim: os dois campos da tabela B, COD e DADO, são carregados em listas na memória; em seguida, para cada linha da tabela A, pesquisa-se COD na lista B, recuperando-se o dado DADO.
(Vamos modificar o exemplo anterior, acrescentando mais uma lista, ficando uma para COD, e outra para DADO. Também, as tabelas serão lidas do banco de dados, e não mais de um arquivo texto.)
Mas, agora surge um problema: se carregarmos as duas listas distintas, e em seguida fizermos a ordenação, perdem-se as relações entre as posições dos itens das duas. Para evitar isso, usaremos duas técnicas diferentes, a seguir:
- alternativa 1: ordenando as duas listas juntas
Uma alternativa é agregar COD + DADO numa só lista, ordenar, e em seguida, desmembrar em duas listas, que manterão a posição relativa entre os itens. O desmembramento é necessário porque, se mantivéssemos os dados agregados, o FIND não iria encontrá-los.
Figura 1 - Ordenação das duas listas como um todo (juntas em uma única lista)
No passo I, são agregados COD e DADO numa só lista; No passo II, COD + DADO é ordenado, e;
No passo III, COD e DADO são segregados ficando, porém nas mesmas posições nas respectivas listas.
O código fica:
procedure TForm1.indexando_ordenando_as_duas;
var lst_COD,lst_DADO : TStringList;
n,ind : integer;
dado_recuperado : string;
begin
lst_COD := TStringList.Create();
lst_DADO := TStringList.Create();
// carrega COD e DADO da tabela B, usando uma query
query.SQL.Text := 'select COD, DADO from tabela_B';
query.Active := true;
query.First;
while not query.eof do
begin
lst_COD.Add(query.Fields[0].value + query.Fields[1].value);
query.Next;
end;
query.Active := false;
lst_COD.Sort; // ordena os pares de dados
lst_COD.Sorted := false;
// agora, desmembra a lista em duas, com os itens emparelhados
for ind := 0 to lst_COD.Count - 1 do
begin
lst_DADO.add(copy(lst_COD[ind], 4, 99));
// no caso, o comprimento de COD seria 3
lst_COD[ind] := copy(lst_COD[ind], 1, 3);
end;
lst_COD.Sorted := true;
// lê a tabela A, e verifica se existe o dado na lista -
//"n" é meramente ilustrativo
n := 6;
tab_A.Active := true;
tab_A.First;
while not tab_A.eof do
begin
if lst_COD.Find(tab_A.Fields[n].Value, ind) then
//dado_recuperado := lst_DADO[ind]
else
dado_recuperado := '';
tab_A.Next;
end;
tab_A.Active := false;
lst_COD.Free;
lst_DADO.Free;
end;
Comentário:
No exemplo acima, confiamos que o comprimento de COD fosse fixo. Caso seja necessário trabalhar com formatos que não resultem em strings fixos, como inteiros, por exemplo, sugerimos que a “chave” carregada na lista seja formatada pela função FORMAT. Assim também pode ser feito quando a chave de busca é formada por múltiplos campos.
- alternativa 2: indexação relativa
A segunda alternativa é construir um vetor (uma lista), carregada apenas com os índices relativos, apontando para a outra lista donde os dados serão recuperados.
Isso é útil, por exemplo, quando DADO, do exemplo anterior, é uma estrutura ou registro, com vários campos, ou ponteiros, ou marcadores, etc. Enfim, quando não se for conveniente que DADO participe da ordenação.
Nesse caso, ao carregarmos a lista COD, carregamos a lista DADO ao mesmo tempo, e a posição em que o dado foi inserido (ind) ficará agregada junto a COD. Depois da ordenação, COD e o índice de DADO são desmembrados em duas listas. Agora são três listas.
Esquematicamente, fica assim:
Figura 2 - Utilizando uma lista/vetor auxiliar
No passo I, são carregadas as listas, e a posição relativa é anotada junto a COD;
No passo II, COD e seu antigo índice são ordenados, e;
No passo III, COD e o índice são desmembrados, ficando o índice como “ponte” para DADO
Esse exemplo ilustra uma técnica forte, que pode ser útil em várias situações.
procedure TForm1.indexando_relativo;
var lst_COD,
lst_DADO,
lst_IND : TStringList;
n,ind : integer;
dado_recuperado : string;
begin
lst_COD := TStringList.Create();
lst_DADO := TStringList.Create();
lst_IND := TStringList.Create();
// carrega COD e DADO da tabela B, usando uma query
query.SQL.Text := 'select COD, DADO from tabela_B';
query.Active := true;
query.First;
ind := -1;
while not query.eof do
begin
inc(ind);
lst_COD.Add(query.Fields[0].value + inttostr(ind));
lst_DADO.Add(query.Fields[1].value);
query.Next;
end;
query.Active := false;
lst_COD.Sort; // ordena os codigos, mais os índices para dados
lst_COD.Sorted := false;
// agora, desmembra a lista em duas
for ind := 0 to lst_COD.Count - 1 do
begin
lst_IND.add(copy(lst_COD[ind], 4, 99));
// no caso, o comprimento de COD seria 3
lst_COD[ind] := copy(lst_COD[ind], 1, 3);
end;
lst_COD.Sorted := true;
// lê a tabela A, e verifica se existe o dado na lista
//"n" é meramente ilustrativo
n := 6;
tab_A.Active := true;
tab_A.First;
while not tab_A.eof do
begin
if lst_COD.Find(tab_A.Fields[n].Value, ind) then
dado_recuperado := lst_DADO[ strtoint(lst_IND[ ind ]) ]
else
dado_recuperado := '';
tab_A.Next;
end;
tab_A.Active := false;
lst_COD.Free;
lst_DADO.Free;
lst_IND.Free;
end;
Caso 3. Uma aplicação.
Imaginemos agora duas tabelas, uma de ORÇAMENTOS, e outra de VENDAS realizadas, e desejamos saber o percentual de rejeição dos orçamentos, por produto.
Na tabela VENDAS está registrado o número do orçamento que deu origem à venda, mais o código do produto.
Porém, na tabela ORÇAMENTOS, não há referência se o orçamento foi aceito ou rejeitado. Essa tabela contém apenas o número do orçamento e o código do produto,
Ambas as tabelas têm em torno de 1.500.000 linhas. O número do orçamento é um código numérico, de 8 posições. Sabemos também que a tabela de PRODUTOS tem em torno de 5.000 linhas.
Então, se carregarmos todos os números de orçamentos, que estão na tabela VENDAS, para uma lista, tal lista ocupará em torno de 12MB, o que é bastante viável.
Trabalharemos então com a seguinte estrutura:
- duas listas, a primeira com os códigos de todos os produtos, ordenados, e, a segunda, com os códigos dos orçamentos aceitos, ou seja, que se transformaram em vendas.
- e mais um vetor de duas posições inteiras, com uma entrada para cada produto, onde serão armazenados contadores dos orçamentos emitidos, e dos orçamentos aceitos.
A indexação do vetor será relativa à lista de produtos.
Esquematicamente, fica assim:
Figura 3 - Aplicação das ordenações
O programa, então, fica:
- carregam-se todos os códigos de orçamentos que estão na tabela VENDAS para uma lista, que também é ordenada. (Orçamentos de “Vendas”)
- carregam-se os códigos dos produtos para outra lista, que é depois ordenada. (COD)
- tomam-se todos os orçamentos (código do orçamento e código do produto), da tabela ORÇAMENTOS, e:
soma-se 1 ao contador ORÇADO do produto, e:
se o orçamento existe na tabela de VENDAS (ou seja, na lista que foi carregada), soma-se 1 ao contador VENDIDO do produto.
Vamos ao código:
procedure TForm1.contando_os_orcamentos;
var lst_PROD,
lst_ORCS : TStringList;
conta : array[1..10000,1..2] of integer; // 1-orcado; 2-vendido
ind,aux : integer;
orcado, vendido : integer;
begin
orcado := 1;
vendido:= 2;
lst_PROD := TStringList.Create();
lst_ORCS := TStringList.Create();
// carrega os códigos dos produtos, usando uma query
query.SQL.Text := 'select PROD from PRODUTOS';
query.Active := true;
query.First;
ind := 0;
while not query.eof do
begin
lst_PROD.Add(query.Fields[0].Value);
inc(ind);
conta[ind,orcado] := 0;
conta[ind,vendido] := 0;
query.Next;
end;
query.Active := false;
// carrega os códigos dos orçamentos que vingaram
query.SQL.Text := 'select COD_ORC from VENDAS';
query.Active := true;
query.First;
ind := -1;
while not query.eof do
begin
lst_ORCS.Add(query.Fields[0].value);
query.Next;
end;
query.Active := false;
lst_PROD.Sorted := true;
lst_ORCS.Sorted := true;
// carrega os orçamentos, e verifica se foram aceitos ou não
query.SQL.Text := 'select COD_ORC,PROD from ORCAMENTOS';
query.Active := true;
query.First;
while not query.eof do
begin
lst_PROD.Find(query.Fields[1].value,ind); //pega o índice do produto
inc(conta[ind, orcado]); //conta orçamento
if lst_ORCS.Find(query.Fields[0].value, aux) then // conta se foi aceito
inc(conta[ind, vendido]);
query.Next;
end;
query.Active := false;
lst_PROD.Free;
lst_ORCS.Free;
end;
Comentário:
A lista de produtos poderia ser já uma lista de um componente STRINGBAND, por exemplo.
Esse caso poderia ser modificado para incluir as tuplas (COD, Descrição e contadores, etc) descritas em registros, e o vetor passar a ser uma estrutura com tais registros.
Conclusão.
Utilizando principalmente uma classe e dois métodos da classe, demonstramos como é simples tratar dados, de modo consciente e eficiente, em memória. Ou seja, pode ser simples implementarmos estratégias personalizadas para o tratamento dos dados, o que quase sempre representa um ganho significativo de desempenho para a aplicação.
Então, se juntarmos o conhecimento formal de estruturas de dados, com os poderosos recursos que o Delphi disponibiliza, poderemos primar pela eficiência da programação, sem comprometer os prazos de desenvolvimento. E mais, podemos avançar no tratamento de estruturas complexas, como grafos, por exemplo, lançando mão apenas da nossa ferramenta básica do dia-a-dia.
Por: Pedro Jales - Graduado em Engenharia Química, e pós-graduado em Engenharia de Sistemas e em Administração de Empresas. Começou a programar em 1975, inicialmente em Fortran e Cobol. Trabalhou como Analista de Sistemas de 1978 a 2002, desenvolvendo programação, Projetos de Sistemas e Análise Organizacional. Programa em Delphi desde 1998. Contato: pedrojales@ig.com.br
Dica do Editor
Listas em memória podem ajudam a obter melhor performance em vários casos, porém, os exemplos de pesquisa e ordenação mostrados nesta artigo do amigo Pedro Jales não devem ser considerados como a melhor forma para lidar com banco de dados, seja um SGBD ou um com dados somente em memória.
Nestes casos é melhor utilizar um componente já pronto, como um ClientDataSet, um RxMemoryData ou um JvMemoryData para realizar as operações de ordenação (por índice virtual em memória), filtro (propriedade Filter), busca (Locate, FindKey, FindNearest), etc, para não ter que reinventar a roda e desfrutar de códigos já testados e aprovados por tantas pessoas.
Utilizar listas em memória são úteis em cruzamento de dados, em aplicações críticas ou com certa limitação de hardware.
Além disso, há outros tipos de listas (que não a StringList) que podem ser utilizadas em processamentos críticos. Um exemplo disto é trabalhar com ponteiros e montar os apontamentos entre os endereços diretamente no registro armazenado neles.
Veja abaixo alguns materiais aqui do site, sobre listas e dados em memória que podem ser úteis:
Usando Record, Ponteiro e TList Criando tabelas temporárias Criando arquivos XML com ClientDataSet com Pesquisa e Ordenação de Colunas
Criando Arrays dinâmicos de objetos no Delphi
Primeiro criaremos a classe, a classe é criada após o uses e antes da implementation, neste caso a classe criada de exemplo sera a Tcheque.
//-------------------------------------------------------------------// // DESCRIÇÃO : CLASSE CHEQUE // //------------------------------------------------------------------// type TCheque = class protected //----- Get e Set procedure SetBanco(const Value: String); function GetBanco:String; procedure SetNumero(const Value: String); function GetNumero:String; procedure SetValor(const Value: String); function GetValor:String; procedure SetFavorecido(const Value: String); function GetFavorecido:String; procedure SetCidade(const Value: String); function GetCidade:String; procedure SetData(const Value: String); function GetData:String; procedure SetVerso(const Value: String); function GetVerso:String;
private //----- Atributos FBanco : String; FNumero : String; FValor : String; FFavorecido : String; FCidade : String; FData : String; FVerso : String;
public property Banco : String read GetBanco write SetBanco; property Numero : String read GetNumero write SetNumero; property Valor : String read GetValor write SetValor; property Favorecido : String read GetFavorecido write SetFavorecido; property Cidade : String read GetCidade write SetCidade; property Data : String read GetData write SetData; property Verso : String read GetVerso write SetVerso;
//----- Metódos Criar e destruir objeto constructor Create; destructor Destroy; //---- end;
Está é a estrutura da classe que irei usar neste exemplo, não irei colocar as funções delas por que não é objetivo deste tutorial mostrar orientação ao objetos, caso haja um interesse futuramente posso postar um artigo mais completo mostrando como trabalhar com POO no Delphi.
Após a criação da estrutura da classe que iremos utilizar, agora vou mostrar como pegar os dados de um DataSet e jogá-los dentro de um array.
Procedure GeraCheques; var ACheques : array of TCheque; // Array de Cheques dinâmico InInd : Integer; // Indice do array begin //----------------------------------------------------// // Cria o Array de Cheques // //----------------------------------------------------// //Primeiro registro DataSet.First; while not DataSet.Eof do begin //Aloca uma posição no array SetLength(ACheques,Length(ACheques)+1); //Pega o indice do ultimo registro que foi alocado InInd := Length(ACheques) - 1; // Crio o objeto ACheques[InInd] := TCheque.Create;
//Insere os valores no objeto ACheques[InInd].Banco := DataSetBAN_IN_NUMERO.AsString; ACheques[InInd].Numero := DataSetATO_ST_CHEQUE.AsString; ACheques[InInd].Valor := FormatFloat('###########0.00',DataSetATO_RE_VALORPAGO.AsFloat); ACheques[InInd].Favorecido := DataSetATO_ST_NOMINAL.AsString; ACheques[InInd].Cidade := Ed_Local.Text; ACheques[InInd].Data := FormataData('dd/mm/yy',DataSetATO_DT_VALIDOPARA.AsDateTime); ACheques[InInd].Verso := Ed_Memo.Lines.Text;
// Pula para o próximo registro DataSet.Next; end; end;
Em muitas situações trabalhar com obejetos diretos na memória se torna viável em ganho de performance, cabe a cada umverificar a utilidade do seu uso.
Como deletar arquivos temporários da Internet
Uses WinInet; procedure deletar; var info: PInternetCacheEntryInfo; diretorio: LongWord; tamanho: LongWord; begin tamanho := 0; FindFirstUrlCacheEntry(nil, TInternetCacheEntryInfo(nil^), tamanho) ; GetMem(info, tamanho) ; if tamanho > 0 then info^.dwStructSize := tamanho; diretorio := FindFirstUrlCacheEntry(nil, info^, tamanho) ; if diretorio <> 0 then begin repeat DeleteUrlCacheEntry(info^.lpszSourceUrlName) ; FreeMem(info, tamanho) ; tamanho := 0; FindNextUrlCacheEntry(diretorio, TInternetCacheEntryInfo(nil^), tamanho) ; GetMem(info, tamanho) ; if tamanho > 0 then info^.dwStructSize := tamanho; until not FindNextUrlCacheEntry(diretorio, info^, tamanho); end; FreeMem(info, tamanho) ; FindCloseUrlCache(diretorio) ; end;
Add To Repository: O repositório de objetos do Delphi
domingo, 11 de julho de 2010
Campo Auto-incremento Firebird/Interbase
Sempre que visito fóruns, salas de bate papo e outras relacionadas a programação em Delphi, vejo que uma das dúvidas mais comuns é como projetar um bom sistema, e automático, de chave auto-incremento no Firebird/Interbase. Já vi muita coisa e acho que uma das soluções que eu adotei é bem produtiva e muito eficiente. O conceito não é novo mas eu nunca vi assim como vou propor.
Bom um dos primeiros passos para um bom sistema é uma boa padronização do BD. Toda a nomenclatura dos meus BD eu padronizo. Uma delas, é que mais nos interessa: o generator. O meu é formado da seguinte maneira (sempre) "GEN_[tabela]_ID". Seguindo esse mesmo conceito eu também padronizo a nomenclatura dos meus objetos no Delphi. SQLQuery = qryTABELA, DataSetProvider = dspTABELA, ClientDataSet = cdsTABELA. Eu também acho muito mais produtivo separar os DMs dessa forma: um RDM onde vai ficar as Queries e os DataSetProviders, assim facilmente poderemos "transformar" nossa aplicação CS em Multi-Tier, e um DM onde ficam os CDSs. Como a propriedade ProviderName do CDS é uma string ele não vai "ver" os DSP do RDM, assim colocamos o componente TLocalConnection no RDM em um TConnectionBroker no DM e apontamos sua propriedade Connection para o RDM->TLocalConnection. De posse dessas informações podemos prosseguir.
No RDM onde estão todos os DSP, aponte BeforeUpdateRecord de todos os DSP para o mesmo evento.
Essa é a assinatura do evento:
BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;
DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: Boolean);
E no evento codifique assim:
Crie uma variável Campo do tipo TField e uma variável SQLStmt do tipo String e tbm uma variável Tabela do tipo String e por fim uma variável CustomSQLDataSet do tipo TCustomSQLDataSet. Não se esqueça de declarar e unit DB.
case UpdateKind of
ukInsert: // Inserindo um registro
begin
Campo := DeltaDS.Fields[0];
if (Campo.DataType = ftInteger) and Campo.IsNull then
begin
Tabela := UpperCase(IProviderSupport(SourceDS).PSGetTableName);
SQLStmt := Format(SELECT GEN_ID(GEN_%s_ID, 1) FROM RDB$DATABASE, [Tabela]);
SQLCon.Execute(SQLStmt, nil, @CustomSQLDataSet);
if Assigned(CustomSQLDataSet) then
begin
DeltaDS.Edit;
Campo.NewValue := CustomSQLDataSet.Fields[0].AsInteger;
DeltaDS.Post;
SysUtils.FreeAndNil(CustomSQLDataSet);
end;
end;
end;
Veja que em SQLStmt existe GEN_%s_ID, por isso eu uso um padrão de nomenclatura para os meus Generators no banco. Primeiro ele verifica se o campo[0], que eu sempre deixo a primary key, é do tipo Inteiro e se está nulo, porque eu posso ter atribuído o valor da chave antes, num caso em que vc tem Pedido e Ítens de Pedido, o valor da chave tem que ser atribuido antes, depois executo o SQLStmt apontando o ResultSet, que é um ponteiro, para o CustomSQLDataSet.
Se a pesquisa retornar dados o CustomSQLDataSet vai ter o valor do generator incrementado, aí é só atribuir o valor para o Delta.
Pronto, sua aplicação ja tem um campo auto-incremento totalmente automatizado !
Um abraço e até a próxima