quinta-feira, 8 de março de 2012

Evitar que a aplicação trave ao executar um processo grande

Já devemos ter precisado executar um comando muito grande e, para ficar mais amigável ao usuário, colocar uma barrinha de progresso, para avisá-lo em que estágio de processamento está.

Porém, nos deparamos com um pequeno problema: Nossa aplicação fica travada enquanto executa o comando!! Como resolver isto?

Existe um método chamado Application.ProcessMessages;   que força a aplicação processas as mensagens do sistema operacional, como por exemplo a exibição correta do form. Com isto, não só o form é visualizado corretamente, mas também nossa barrinha de progresso funciona perfeitamente. :D

Vejamos um exemplo simples:

Digamos que tenhamos um ADOTable com vários registros abertos e vamos percorrer um a um:

ADOTable1.First;
  while not (ADOTable1.Eof) do
  begin
      lblStatus.Caption := 'Processando registro...'; // exibimos alguma mensagem
      Application.ProcessMessages; // chamados o método que força o SO a desenha a janela
      ADOTable1.Next; // pula para o próximo registro da tabela
  end;

Pronto. Agora é só você implementar uma barra de progresso e colocar mais algumas perfumarias (objetos que deixam a cara do formulário mais amigável para o usuário) caso seja necessário :)

segunda-feira, 30 de janeiro de 2012

Detectando o Numero Serial do HD

Function SerialNum(FDrive:String) :String;
Var
    Serial:   DWord;
    DirLen,Flags: DWord;
    DLabel : Array[0..11] of Char;
begin
     Try GetVolumeInformation(PChar(FDrive+':\'),dLabel,12,@Serial,DirLen,Flags,nil,0);
            Result := IntToHex(Serial,8);
       Except Result :='';
end;
end;

Recuperar a Velocidade da CPU

Esta interessante função recupera a velocidade de processamento aproximada da CPU:

const
ID_BIT=$200000; // EFLAGS ID bit
 
function GetCPUSpeed: Double;
const
DelayTime = 500;
var
TimerHi, TimerLo: DWORD;
PriorityClass, Priority: Integer;
begin
try
PriorityClass := GetPriorityClass(GetCurrentProcess);
Priority := GetThreadPriority(GetCurrentThread);
 
SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_TIME_CRITICAL);
 
Sleep(10);
asm
dw 310Fh // rdtsc
mov TimerLo, eax
mov TimerHi, edx
end;
Sleep(DelayTime);
asm
dw 310Fh // rdtsc
sub eax, TimerLo
sbb edx, TimerHi
mov TimerLo, eax
mov TimerHi, edx
end;
 
SetThreadPriority(GetCurrentThread, Priority);
SetPriorityClass(GetCurrentProcess, PriorityClass);
 
Result := TimerLo / (1000.0 * DelayTime);
except end;
end;
No evento OnClick, basta atribuir a saída da função a uma string:

procedure TForm1.Button1Click(Sender: TObject);
var cpuspeed:string;
begin
cpuspeed:=Format('%f MHz', [GetCPUSpeed]);
edit1.text := cpuspeed;
end;

Verificando o tipo de Drive

Verificando o tipo de Drive

O código abaixo implementa uma função para testar qual o tipo de drive da unidade especificada.

Para isto, é necessário utilizar uma função de API do windows chamada GetTypeDrive.

Esta função retorna valores default que indicam o tipo de drive selecionado.(Drive_Removable,Drive_Fixed,Drive_Remote,Drive_CdRom,Drive_RamDisk)

Código Completo:

Function Tipo_Drive(Drive:Char):String;
Const
Drive_Removable = 2;
Drive_Fixed = 3;
Drive_Remote = 4;
Drive_CdRom = 5;
Drive_RamDisk = 6;
var
Tipo: byte;
begin
Tipo := GetDriveType(PChar(Drive + ':\'));
Case Tipo of
0: Result := 'Indeterminado';
1: Result := 'Inexistente ';
2: Result := 'Removível';
3: Result := 'Fixo';
4: Result := 'Rede';
5: Result := 'CD-ROM';
6: Result := 'RAM Disk';
else Result := ' Erro';
End;
end;

Consultar por mês de um campo data

Problema:

Tenho um cadastro de clientes com Codigo, Nome, DataNasc, etc.

Preciso fazer uma consulta onde apareceão apenas os clientes que fazem aniversário em determinado mês. Como fazer?

Solução:

Use uma Query como abaixo:

- Coloque no form os seguintes componentes:

  * TQuery

  * TDataSource

  * TDBGrid

  * TEdit

  * TButton

- Altere as propriedades dos componentes como abaixo:

  * Query1.DatabaseName = (alias do BDE)

  * DataSource1.DataSet = Query1

  * DBGrid1.DataSource = DataSource1

 - Coloque o código abaixo no evento OnClick de Button1:

  Query1.Close;
  Query1.SQL.Clear;
  Query1.SQL.Add('select * from dCli');
  Query1.SQL.Add('where extract(month from DataNasc) = :Mes');
  Query1.ParamByName('Mes').AsInteger := StrToInt(Edit1.Text);
  Query1.Open;
- Execute. Digite um número de 1 a 12 no Edit e clique no botão.

Observações

Os números de 1 a 12 representam, respectivamente, os meses de Janeiro a Dezembro. Este exemplo foi testado com Delphi4, BDE5 e tabela Paradox7.

Criando tabelas via SQL

Inclua na seção uses: dbTables

- Coloque um TButton no form;

- Escreve no OnClick do Button como abaixo:

procedure TForm1.Button1Click(Sender: TObject);
var
  Q: TQuery;
begin
  Q := TQuery.Create(Application);
  try
  Q.DatabaseName := 'SF';
  with Q.SQL do begin
  Add('Create Table Funcionarios');
  Add('( Codigo AutoInc,');
  Add(' Nome Char(30),');
  Add(' Salario Money,');
  Add(' Depto SmallInt,');
  Add(' Primary Key (Codigo) )');
  end;
  Q.ExecSQL;
  finally
  Q.Free;
  end;
end;

ObservaçõesEste exemplo foi testado com banco de dados Paradox, porém deverá funcionar em vários outros bancos de dados com pouca ou nenhuma alteração.

Mostrando progresso de uma SQL

Algumas pessoas estavam interessadas em saber como apresentar o progresso de um TQuery enquanta ele esta sendo aberto (ou executada, no caso de um

INSERT / UPDATE / DELETE).

A tecnica que vou demostrar nao apenas serve para o proposito procurado, mas tambem serve para mostrar o progresso de diversas outras atividades que o BDE executa, como:

* Criacao de tabelas

* Criacao de indices para tabelas

* Reestruturacao de tabelas

* Execucao de queries (ja comentado)

* alguma outra coisa que no momento nao me ocorre... :))

Importante:


1) No meu exemplo, estou usando o Delphi 3.02. Caso seu Delphi seja de uma versao menor, vc devera ter um trabalho extra para repor a classe TBDECallback. Acredito que seja possivel fazer uma rotina que funcione em Delphi 1, mas que com certeza dara um certo trabalhinho, ah, isso dara...

:-/

2) Ate agora so usei esse codigo com tabelas Paradox, mas realmente acredito que ele venha a funcionar com base de dados Interbase, Oracle, etc...

3) Nao sei se com o uso do Opus, Apollo ou qualquer outro substituto do BDE a tecnica ira funcionar, uma vez que nao se estaria trabalhando com o BDE original. Talvez alguem da lista possa dar essa informacao.

Teoria

Segundo o help do Delphi, "o TBDECallback eh um wrapper para uma funcão de callback do BDE. Com ele eh possivel instruir o BDE para que o mesmo execute algumas tarefas em resposta a eventos que ocorram durante uma chamada de uma funcao do BDE. " - Fim do plagio do arquivo de help.

O tipo de callback depende de um parametro CBType que eh fornecido no momento da criacao do TBDECallback. E, entre os diversos valores que o CBType pode apresentar, existe um que muito nos interessa; o cbGENPROGRESS.

:))

Assim, vc deveria criar uma funcao de callback do tipo cbGENPROGRESS chamada AtualizaGauge e indicar que a mesma eh que devera ser executada "entre cada respiracao" do BDE. Na rotina AtualizaGauge, o BDE iria te informar o percentual de progresso da tarefa .

O que voce faria nessa rotina ? Simples... atualizar o Gauge / ProgressBar.

Tudo muito bonito, tudo muito comovente, mas agora vamos para o lado pratico...

Pratica

Para que o BDE possa informar o progresso da tarefa, ele precisa obter essa informacao da base de dados que esta sendo utilizada. Acontece que, por razoes diferentes, nem sempre ele eh capaz de saber o PERCENTUAL da tarefa. Numa copia de registros de uma tabela para outra, ele pode saber que ja foram copiados 270 registros, mas nao saber que esse esforco representa 36 % de todos os registros que serao copiados.

Assim sendo, na funcao de callback que sera criada, receberemos um parametro do tipo pCBPROGRESSDesc, que por sua vez eh um ponteiro para uma estrutura que contem duas informacoes:

iPercentDone => percentual do servico realizado

szMsg => texto descrevendo o progresso do servico.

Como usar esses parametros ? Simples: sempre que o iPercentDone for negativo, voce devera considerar o texto descrito no campo szMsg. Se for igual ou maior que zero, entao vc devera considerar o valor do proprio iPercentDone.

Uma boa noticia para quem se preocupa com as mensagens que aparecem em ingles, quando se quer na verdade mostra-las em portugues: a mensagem fornecida por szMsg devera sempre aparecer no formato

<:>

.....

Exemplo:

Records copied: 170

Assim, voce pode procurar pelos dois pontos ":" e pegar o valor que vem a seguir para montar sua propria informacao em portugues.

Pessoalmente, ate agora nunca obtive um iPercentDone positivo. Li no newsgroup da Borland que poucas bases de dados eram capazes de informar o real percentual para o BDE. Se nao me engano, o Sybase era um deles... NAO ESTOU CERTO DISSO.

Vamos para um exemplo pratico ? Crie um projeto novo, e coloque um:

TQuery, TButton, TProgressBar e TLabel.

Sua query deve ser montada para abrir uma tabela razoavelmente grande, demodo que a operacao de abertura demore um pouco.

Agora vamos aos codigos:

1) Acrescente a unit BDE no seu USES da unit.

2) Acrescente algumas declaracoes na declaracao do seu Form:

==============================

type
TForm1 = class(TForm)
... (bla bla bla)
private
{ Private declarations }
FCBPROGRESSDesc: pCBPROGRESSDesc;
FProgressCallback: TBDECallback;
function GetDataCallback(CBInfo: Pointer): CBRType;
public
{ Public declarations }
end;
==============================

No evento OnCreate do seu Form:

==============================

procedure TForm1.FormCreate(Sender: TObject);
begin
FCBPROGRESSDesc := AllocMem(SizeOf(CBPROGRESSDesc));
FProgressCallback := TBDECallback.Create(Self, Query1.Handle,
cbGENPROGRESS, FCBPROGRESSDesc, SizeOf(CBPROGRESSDesc),
GetDataCallback, True);
end;
==============================

Percebam que no segundo parametro do Create do callback, eu coloquei Query1.Handle.

Caso voce queira usar isso numa TTable, coloque Table1.Handle.

E se quiser que essa funcao de callback seja chamada para todos os "progressos" de qualquer componente DataSet, voce deixa esse parametro como

NIL.

No evento OnDestroy do Form:

==============================

procedure TForm1.FormDestroy(Sender: TObject);
begin
FProgressCallback.Free;
FreeMem(FCBPROGRESSDesc, SizeOf(CBPROGRESSDesc));
end;
==============================

E agora, a tao falada funcao de callback:

==============================

function TForm1.GetDataCallback(CBInfo: Pointer): CBRType;
begin
Result := cbrCONTINUE;
with pCBPROGRESSDesc(CBInfo)^ do
begin
if iPercentDone < 0 then
begin
Label1.Caption := szMsg;
Label1.Refresh;
ProgressBar1.StepIt; {Apenas para ficar rodando o gauge}
end
else
ProgressBar1.Position := iPercentDone;
end;
end;
==============================

Agora eh so executar a query no clicar do botao e curtir o visual... :))

IMPORTANTE !!!!!!

Caso voce receba uma mensagem de erro informando que nao foi possivel inicializar o BDE (o que provavelmente acontecera, pois voce esta criando uma funcao de callback do BDE, quando ate entao nenhuma tabela havia sido aberta), va no DPR do seu projeto (Menu View -> Project Source) e faca o seguinte:

1) Acrescente a unit BDE no uses do projeto.

2) Acrescente a instrucao

DbiInit(nil);

apos a instrucao Application.Initialize;

Isso deve resolver o problema.

Bom, nao vou me alongar mais, porque senao essa mensagem vai ficar maior do que ja esta...

Espero que tenha contribuido para a solucao desse problema de mostar progresso de uma query. Qualquer duvida mandem mensagem.

Otimizações SQL

As dicas abaixo foram testadas essencialmente com Oracle:

1) Todas as vezes que for utilizar um SQL que possua condições de OR, é mais aconselhável e mais rápido utilizar IN, como no exemplo:

AO INVÉS DE

  Select * from projint where sit_projint = ‘AI’ or sit_projint = ‘EL’

COLOQUE

  Select * from projint where sit_projint IN (‘AI’,‘EL’);

2) Quando existem duas ou mais condições AND juntas, especifique primeiro sempre a que possui o maior limite de ocorrências

AO INVÉS DE

  select count(*) from pessoa where sit_pessoa = 11 AND cod_munic > 1100155

COLOQUE

  select count(*) from pessoa where cod_munic > 1100155 AND sit_pessoa = 11

3) Quando existem duas ou mais condições OR juntas, especifique primeiro sempre a que possui o maior limite de ocorrências

AO INVÉS DE

  select count(*) from pessoa where cod_munic > 1100155 OR sit_pessoa = 11

COLOQUE

  select count(*) from pessoa where sit_pessoa = 11 OR cod_munic > 1100155

4) Tenha cuidado com o sinal de <>

AO INVÉS DE

  select count(*) from pessoawhere cod_munic < > 1100155

COLOQUE

  select count(*) from pessoawhere cod_munic < 1100155 OR cod_munic > 1100155

Procura com mais de um Banco de Dados

edit1.text:= dbcombobox1.Text;
QueryPrinc.active := false;
QueryPrinc.sql.clear;
QueryPrinc.sql.add('select * from estrucpr where portugues = "' + edit1.Text + '"');
QueryPrinc.active:= true;
 
if DBcodEl.text=DBcodEl.text then
edit2.Text:= DBcodEl.Text;
 
QuerySin.active := false;
QuerySin.sql.clear;
QuerySin.sql.add(' select * from sinonimo where sin_est_id = "' + edit2.text + ' "');
QuerySin.active:= true;
 
 if DBcodEl.text=DBcodEl.text then
edit3.Text:= DBcodEl.Text;
 
QueryPropFis.active := false;
QueryPropFis.sql.clear;
QueryPropFis.sql.add('select * from FIQU_PRO where fiqu_id = "' + edit3.Text + '"');
QueryPropFis.active:= true;

Selecionando registros de uma tabela que não existam em outra tabela

(SELECT * FROM , WHERE . = .    AND . = 'AP' )
  MINUS
(SELECT * FROM @remoto1, @remoto1   WHERE @remoto1. = @remoto1.   AND @remoto1. = 'AP' )

Obs: todos os campos retornados no SELECT secundário deverá conter os mesmos campos do SELECT primário

Sql por campo edit pesquisando pelo nome

Inclua um componente Tedit e Tquery conectado a tabela do banco de dados, e no eveento OnChange do compoente TEdit digite:

query1.active := false;
query1.sql.clear;
query1.sql.add('select * from teste where nome = "' + edit1.Text + '"');
query1.active:= true;


terça-feira, 10 de janeiro de 2012

Função para gerar senhas aleatórias

Essa é uma dica simples e que para os que trabalham com redes sem fio pode ser muito útil. O que a função faz é gerar um string com caracteres hexadecimais, mas que podem ser adaptados para qualquer outro tipo. Esta função recebe como parâmetros o comprimento da senha como um integer, e outros três parametros do tipo boolean que indicam se estarão presentes letras minúsculas, maiúsculas e números.

Aqui vai o código e as explicações seguem logo abaixo.

function GeraSenhaHex(Digitos: Integer; Min: Boolean; Mai: Boolean; Num: Boolean): string;
const
     MinC = 'abcdef';
     MaiC = 'ABCDEF';
     NumC = '1234567890';
var
     p, q : Integer;
    Char, Senha: String;
begin
Char := '';
If Min then Char := Char + MinC;
If Mai then Char := Char + MaiC;
If Num then Char := Char + NumC;
for p := 1 to Digitos do
  begin
    Randomize;
    q := Random(Length(Char)) + 1;
    Senha := Senha + Char[q];
  end;
Result := Senha;
end;

Explicações:

Primeiro criamos as constantes que trarão os caracteres referentes a letras minúsculas, maiúsculas, e números, depois, iniciamos como vazia, só por desencargo de consciência já que o delphi faz isso por padrão, a variável "Char", que conterá todos os caracteres a serem usados para a geração da senha randômica.

Após isso, testamos os parâmetros para letras maiúsculas, minúsculas e números, acrescentando à "Char" cada um dos que forem verdadeiros segundo os parâmetros passados na chamada da função.

E depois, para finalizar, um laço com o número de repetições igual aos dígitos passados também como parâmetro na chamada, que usando a função Random do delphi gera números aleatórios dentro do limite estabelecido pelo cumprimento da variável "Char", lembrando que o fato de acrescentar o "+ 1" é por que as posições dos caracteres dentro de um string iniciam em 1, e a função Random gera números de 0 até o valor estipulado como limite. Por exemplo uma String = 'teste' temos os valores a seguir:

String[1] = 't'
String[2] = 'e'
String[3] = 's'
String[4] = 't'
String[5] = 'e'

segunda-feira, 2 de janeiro de 2012

Verificando o seu endereço IP

A fonte do projeto principal ficará algo como:

implementation
 uses Winsock;
 {$R *.DFM}
 procedure TForm1.FormCreate(Sender: TObject);
var
  wVersionRequested : WORD;
  wsaData : TWSAData;
begin
  wVersionRequested := MAKEWORD(1, 1);
  WSAStartup(wVersionRequested, wsaData);
end;
 procedure TForm1.FormDestroy(Sender: TObject);
begin
  WSACleanup;
end;
 procedure TForm1.BtnIPClick(Sender: TObject);
var
  p : PHostEnt;
  s : array[0..128] of char;
  p2 : pchar;
begin
  GetHostName(@s, 128);
  p := GetHostByName(@s);
  Memo1.Lines.Add(p^.h_Name);
  p2 := iNet_ntoa(PInAddr(p^.h_addr_list^)^);
  Memo1.Lines.Add(p2);
end;
 end.

segunda-feira, 26 de dezembro de 2011

Capturar a data da BIOS ( Basic Input Output System ) do computador


Insira um objeto do tipo Button com a propriedade name definica como Button1 e um objeto do tipo Label com a propriedade definida como Label1.


procedure TForm1.Button1Click(Sender: TObject);
begin
      Label1.Caption := 'Data da Bios: '+String(PChar(Ptr($FFFF5)));
end;

segunda-feira, 12 de dezembro de 2011

Como Criar Forms Em Tempo de Execução

Para você economizar memória, pode-se criar os forms de sua aplicação somente no momento da execução. Na criação do Form você define se ele é MODAL ou NÃO MODAL. Para Isso observe os seguintes códigos:

MODAL - Mostra form em modo exclusivo
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);{Carrega form na memória}
Form2.ShowModal;{Mostra form em modo exclusivo}
Form2.Free; {Libera Memória}
end;

NÃO MODAL - Mostra form em modo não exclusivo
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);{Carrega form na memória}
Form2.ShowModal;{Mostra form em modo exclusivo}
end;
No evento OnClose do Form2 coloque o seguinte código.
procedure TForm2.FormClose (Sender: Tobject; var Action : TCloseAction);
begin
Action:= caFree;
end;

Aliado a este código, deve deve alterar no delphi, no menu Options, opção Project. Mudando os forms a serem criados dinamicamente da coluna Auto-Create Forms para Avaliable Forms.