Mar 13 2007

Só pra vocês terem uma idéia do tamanho do problema

Autor: Marcos Dell Antonio - Categorias: Cotidiano, Delphi

Faz quatro dias que estou desenvolvendo uma rotina que gera a programação de trabalho, em forma de agenda, das máquinas cadastradas no sistema. O negócio tá tão complicado que até o Delphi concorda comigo:

Pra quem não conhece Delphi, a tela acima é usada para verificar o valor das variáveis durante o debug. Sem querer eu adicionei a expressão begin e eis o que aconteceu.

Hehe. Coisas da vida.

Até +.

Comentários Faça o seu comentário

Mar 06 2007

MySQL: formatando campos DateTime

Autor: Marcos Dell Antonio - Categorias: .NET, Cotidiano, Delphi

Alguma vez você executou um comando SQL como o apresentado abaixo e não obteve linha alguma como resultado?

SELECT * FROM teste WHERE datahora <= '2007-06-03';

Isso já aconteceu diversas vezes comigo e sempre chego à mesmoaconclusão: o campo da cláusula WHERE (datahora) é do tipo DateTime, ou seja, armazena além da data o horário, e isso gera um problema no SQL acima.

Farei os testes usando a seguinte tabela:

CREATE TABLE teste ( datahora DATETIME NOT NULL )

E os seguintes registros:

INSERT INTO teste VALUES ('2007-06-03 12:00:00'); INSERT INTO teste VALUES ('2007-06-03 13:30:00'); INSERT INTO teste VALUES ('2007-06-03 14:20:00'); INSERT INTO teste VALUES ('2007-06-03 15:10:00');

Bem simples, mas é o suficiente para demonstrar um grande problema que pode acontecer ao executar o comando SELECT apresentado no início do post.

Repare que todos os registros possuem além da data um horário. Portanto, se você tentar buscar todos os registros onde o campo datahora for menor ou igual a ‘2007-06-03′, a lista de registros encontrados estará vazia. Por que, hãm? Pois ‘2007-06-03 12:00:00′, ou ‘2007-06-03 13:30:00′ (dois exemplos aleatórios da tabela criada anteriormente) não são menores ou iguais a ‘2006-06-03′.

Em outras palavras, o MySQL está considerando o tempo ao comprar os valores, e como o valor da cláusula WHERE não especifica um horário, ele assume o padrão (00:00:00). Logo, ‘2007-06-03 13:30:00′ não é menor que ‘2007-06-03 00:00:00′, por isso não é incluído no resultado.

Para resolver isso de uma forma bem rápida, simples e not(IsWordAround), basta usar a função DATE_FORMAT do próprio MySQL.

O SELECT apresentado anteriormente fica assim:

SELECT * FROM teste WHERE DATE_FORMAT(datahora, '%Y-%m-%d') <= '2007-06-03';

Ou seja, agora o MySQL utiliza somente a data para realizar as comparações entre os valores. O resultado apresentado é este:

Para ficar mais claro como o banco está processando a query, execute o seguinte comando:

SELECT DATE_FORMAT(datahora, '%Y-%m-%d') FROM teste;

O resultado é:

Simples, fácil, rápido e legível. Nada de ficar inventando tratamentos extras, mudando os operadores das queries, etc. Basta fazer uso das funções do próprio banco.

Para obter mais informações sobre esta função, veja o artigo do Fábio que foi publicado no site Viva o Linux ou a documentação oficial do MySQL.

É isso ae. Até +.

Comentários 5 comentários

Feb 26 2007

Delphi: TScrollBox não tem eventos para tratamento de Scroll, e agora?

Autor: Marcos Dell Antonio - Categorias: Delphi

Dias atrás precisei de um componente no estilo do TPanel mas que tivesse as barras de rolagem na vertical e horizontal. A princípio achei que o próprio TPanel teria isso, porém não encontrei nada.

Foi então que ao acaso descobri o componente TScrollBox. Ele é exatamente isto: um TPanel + TScrollBar (horizontal e vertical).

Com o componente em mãos, fiz alguns testes e já parti pra produção. No meio do caminho, após enviar uma demo ao supervisor do que estava sendo feito, recebi uma solicitação para que o cabeçalho (alguns botões) deste componente ficasse fixo.

Encontrei duas soluções para o problema:

1. Remover os botões do cabeçalho e colocá-los logo acima do TScrollBox. Desta forma, quando ele rolasse para baixo ou para cima, o cabeçalho permaneceria no mesmo lugar.

2. Manter os botões no topo do TScrollBox e tratar dinamicamente o Top deles (conforme o scroll) para que ficassem sempre no topo.

A primeira solução foi descartada, pois naquela situação teria que alterar várias outras coisas. Logo, parti para a segunda.

Quando fui procurar o evento que tratava a rolagem das barras (OnScroll ou algo do tipo), simplesmente descobri que não existia. Foi então que parti pro Google

Através do quarto site encontrado na pesquisa realizada acima, cheguei novamente à fonte de milagres para qualquer programador Delphi: Torry’s Delphi.

Com base no exemplo dele, elaborei a seguinte rotina usando a mensagem WM_VSCROOL:

1 procedure TfrmMeuForm.WMVScroll(var Msg: TMessage); 2 begin 3 FOldWindowProc(Msg); 4 5 if (Msg.Msg = WM_VSCROLL) then 6 button.Top := scrollBox.VertScrollBar.Position; 7 end;

Neste exemplo tratei o Top apenas de um botão para facilitar o entendimento.

Com esse método declarado e implementado, bastou fazer o seguinte:

- Declarar uma variável private do tipo TWndMethod:

1 private 2 FOldWindowProc: TWndMethod;

- Definir a propriedade WindowProc da TScrollBox para o método de tratamento e armazenar uma cópia de onde está o método atual:

1 FOldWindowProc := scrollBox.WindowProc; 2 scrollBox.WindowProc := WMVScroll;

Pronto. Desta forma, quando o método WMVScroll for executado, ele tratará todas as mensagens que tratava antes e também a WMV_SCROLL, só que agora de uma forma específica.

É isso ae. Espero que ajude.

Até + 

Comentários 1 comentário

Feb 12 2007

Delphi: enviando componentes para frente e para trás dos demais

Autor: Marcos Dell Antonio - Categorias: Delphi

Problema: tenho quatro TPanel empilhados, ou seja, um sobre o outro. Ao clicar sobre um dos quatro, como faço para que ele apareça no topo da pilha?

Existe um método chamado BringToFront da classe TControl que faz parte da hierarquia da TPanel. Sua função é colocar o componente indicado na frente de todos os outros pertencentes ao mesmo Parent.

Exemplo:

Ao clicar no panel vermelho, o resultado é este:

O código para fazer isso é simples. Adicione no evento OnClick do panel desejado a seguinte linha:

1 Panel.BringToFront;

Para terminar, existe também o método SendToBack que faz exatamente o contrário do BringToFront: envia determinado componente para trás dos outros. A chamada é tão simples quanto a anterior.

É isso ae. Até +! :-)

Comentários 1 comentário

Feb 12 2007

Delphi: A propriedade Width dos componentes TDBText é uma farsa

Autor: Marcos Dell Antonio - Categorias: Delphi

Para que serve a propriedade Width se não para indicar qual é o tamanho do componente?

Ao utilizar um TDBText dentro de um TDBCtrlGrid, o Width do primeiro não se altera conforme o conteúdo. Por exemplo, o componente mantém o mesmo Width para os textos “Teste” e “Testeeeeeeeee”.

Isso a princípio está correto, pois não é possível habilitar o AutoSize do TDBText dentro de um TDBCtrlGrid.

Agora imagine a seguinte situação: você tem uma TDBCtrlGrid com dois TDBText na mesma linha (Top igual). O tamanho da fonte de ambos é configurável, ou seja, pode aumentar e diminuir. Conforme o usuário altera o tamanho, a posição do segundo componente deve mudar, pois se ele aumentar a fonte, o primeiro aumentará de tamanho e, logo, o segundo deverá ir mais para a esquerda.

Ok, até aqui nada demais. No entanto, a forma mais lógica para resolver este problema, que é alterar o Left do segundo componente conforme o Width do primeiro, não funciona. Veja:

1 lblComp2.Left := lblComp1.Left + lblComp1.Width + 3;

Tendo em vista que o Width do primeiro componente nunca muda, pois o AutoSize não pode ser habilitado, o Left do segundo componente permanecerá fixo também e então ele irá sobrepor o primeiro.

A solução mais prática que achei para este problema foi manipular o Width dos componentes manualmente. Esta alteração é feita com base na quantidade de caracteres que o campo tem no momento. A fórmula usada foi essa: 

1 label1.Width := 2 Round((label1.Font.Size * label1.GetTextLen * 8) / 10);

Com isso, a primeira fórmula (utilizando o Left + Width) funcionará, pois agora o tamanho dos componentes está de acordo com seu conteúdo.

É isso ae. Até +. :)

Comentários 1 comentário

Jan 16 2007

Delphi: Como detectar se a sua aplicação está rodando sobre WTS e saber qual é a ID da sessão

Autor: Marcos Dell Antonio - Categorias: Delphi

Parece ridículo, mas quem poderia imaginar que o controle de licenças de um software furaria quando os clientes fizessem o acesso via WTS (Windows Terminal Server)?

Imagine a seguinte situação: um controlador de licenças baseado no IP dos clientes e que bloqueia a execução da mesma aplicação duas vezes simultaneamente na mesma máquina. Se o acesso ao controlador for via TCP/IP “diretamente”, da forma mais comum possível (um servidor + um cliente), essa validação funcionará perfeitamente.

Agora pense no mesmo cenário acima, porém com o acesso de dois clientes usando WTS. O que acontece? O primeiro cliente acessa, o controlador faz o registro do IP. Quando o segundo cliente for acessar (lembre-se, é WTS), adivinha? O controlador bloqueia! Por que? Pois o segundo cliente possui o mesmo IP do primeiro, que por sua vez possui o mesmo IP do servidor, pois está tudo sobre WTS.

Solução? Eu vejo duas:

  1. Controlar o acesso pelo IP do cliente. Para isso, é preciso identificar qual a sessão que está acessando o WTS e através do seu ID buscar as informações (IP e outras) de quem a originou;
  2. Fazer com que o controlador de licenças conheça se o cliente está sobre WTS e considerar o ID da sessão no controle de acesso.

Para ambas as soluções duas rotinas são fundamentais:

  1. Identificar se a aplicação está sendo executada via WTS:

    function IsRemoteSession: boolean; const SM_REMOTESESSION = $1000; begin Result := GetSystemMetrics(SM_REMOTESESSION) <> 0; end;

    Adicione o uses Windows à unit.

  2. Pegar o ID da sessão que vai executar o aplicativo:

    1 function GetCurrentSessionID: Integer; 2 type 3 TProcessIdToSessionId = 4 function(dwProcessId: DWORD; 5 pSessionId: DWORD): 6 BOOL; stdcall; 7 var 8 ProcessIdToSessionId: TProcessIdToSessionId; 9 hWTSapi32dll: THandle; 10 Lib : THandle; 11 pSessionId : DWord; 12 begin 13 Result := 0; 14 Lib := GetModuleHandle('kernel32'); 15 if Lib <> 0 then 16 begin 17 ProcessIdToSessionId := 18 GetProcAddress(Lib, 'ProcessIdToSessionId'); 19 20 if Assigned(ProcessIdToSessionId) then 21 begin 22 ProcessIdToSessionId(GetCurrentProcessId(), 23 DWORD(@pSessionId)); 24 Result:= pSessionId; 25 end; 26 end; 27 end;

    As units Windows e Winsock são necessárias.

Com essas duas rotinas é possível controlar se aplicação cliente está sobre WTS ou não. O resto fica por conta da sua imaginação. ;)

Fonte: Torry’s Delphi Pages (cuidado com a rotina que está lá, ela tem um errinho no parâmetro da GetProcAddress - o número 1 está sobrando)

Até +!

Comentários Faça o seu comentário

Dec 12 2006

MySQL: verificando se um índice existe

Autor: Marcos Dell Antonio - Categorias: Cotidiano, Delphi

Observações: a versão do MySQL usada para os testes foi a 3.2.9. Provavelmente esta solução é compatível com as versões mais novas.

Índices (ou indexes) são perfeitos quando se trata de melhorar o desempenho de acesso ao banco. Hoje mesmo consegui reduzir o tempo de processamento de um SELECT de 16s para 5s. Tendo em vista que a tabela possui mais de 15.000 registros, consegui chegar a um tempo razoável.

Mas esse post não é sobre os benefícios dos índices, e sim sobre como verificar se um determinado índice já existe. Não há um comando específico para checar isso, portanto vamos a baixaria:

1 SHOW INDEX FROM tabela

O comando acima, de acordo com a documentação do MySQL, retorna os índices de uma tabela. Dentre as informações retornadas, existe uma que representa o nome do índice: Key_name. É através deste campo que podemos verificar se um índice existe. Para isso, criei a seguinte rotina (em Delphi):

1 function ExisteIndice(tabela, indice: string): boolean; 2 var 3 query: TSDQuery; 4 begin 5 try 6 query := TSDQuery.Create(nil); 7 query.DatabaseName := 8 dtmDataModule.dtbBancoDados.DatabaseName; 9 query.SQL.Add('SHOW INDEX FROM ' + tabela); 10 query.Open; 11 12 query.First; 13 while (not(query.Eof)) do 14 begin 15 if (query.FieldByName('key_name').AsString = indice) then 16 begin 17 Result := true; 18 Break; 19 end; 20 21 query.Next; 22 end; 23 finally 24 query.Close; 25 FreeAndNil(query); 26 end; 27 end;

O que ela faz? Simples:

  1. Retorna todos os índices de uma determinada tabela (parâmetro “tabela”);
  2. Verifica se entre os índices retornados existe algum igual ao passado no parâmetro “indice”;
  3. Se existir, retorna true. Caso contrário, retorna false.

Pronto! Problema resolvido. :-)

Se você cria dinamicamente as tabelas e índices do seu sistema, pode utilizar esta rotina para verificar se o índice já existe antes de criá-lo. Se existir, poderá ignorar a criação ou removê-lo para criá-lo novamente.

É isso aí. Até +.

Comentários Faça o seu comentário

Dec 05 2006

MySQL: Ordenando campos VARCHAR como se fossem INTEGER

Autor: Marcos Dell Antonio - Categorias: .NET, Cotidiano, Delphi

Ps: Este post trata da ordenação de campos VARCHAR como se fossem INTEGER utilizando o banco MySQL 3.2.9. Provavelmente a solução apresentada é compatível com as versões mais novas.

É muito comum o uso de campos VARCHAR para armazenar valores inteiros e alfanuméricos. No entanto, um grande problema é como ordenar esse tipo de campo como se fosse INTEGER.

No exemplo deste post, vou usar a seguinte tabela:

1 CREATE TABLE teste 2 ( 3 codigo VARCHAR(20) NOT NULL, 4 descricao VARCHAR(50) NOT NULL, 5 PRIMARY KEY(codigo) 6 )

Veja que a chave primária (campo “codigo”) é do tipo VARCHAR. Para fazer os testes, usarei os seguintes registros:

1 INSERT INTO teste VALUES ('1', 'Teste 1'); 2 INSERT INTO teste VALUES ('2', 'Teste 2'); 3 INSERT INTO teste VALUES ('3', 'Teste 3'); 4 INSERT INTO teste VALUES ('4', 'Teste 4'); 5 INSERT INTO teste VALUES ('5', 'Teste 5'); 6 INSERT INTO teste VALUES ('6', 'Teste 6'); 7 INSERT INTO teste VALUES ('7', 'Teste 7'); 8 INSERT INTO teste VALUES ('8', 'Teste 8'); 9 INSERT INTO teste VALUES ('9', 'Teste 9'); 10 INSERT INTO teste VALUES ('10', 'Teste 10'); 11 INSERT INTO teste VALUES ('11', 'Teste 11'); 12 INSERT INTO teste VALUES ('12', 'Teste 12');

Após populada a tabela, o desafio é fazer um SELECT que retorne os registros ordenados pelo campo “codigo”. O comando SQL mais óbvio para isso é:

1 SELECT * FROM teste ORDER BY codigo;

No entanto, ao executar este SQL o resultado apresentado é este:

Veja que a ordem retornada não é a esperada, ou seja, o registro 10, por exemplo, vem logo após o 1, quando deveria vir após o 9. Isso acontece pois o banco de dados está interpretando o campo “codigo” como VARCHAR.

Uma maneira muito comum para resolver este problema é cadastrar todos os registros prefixados com vários zeros, exemplo: 00001, 00002, 00010, etc. Desta forma, o SQL acima funciona.

No entanto, forçar o usuário a cadastrar suas informações utilizando um prefixo não é nada elegante quando existe outra solução muito mais simples. Veja o comando SQL que resolve este problema:

1 SELECT * FROM teste 2 ORDER BY 3 CONCAT(REPEAT("0", 20 - LENGTH(codigo)), codigo);

O resultado apresentado é este:

As funções CONCAT, REPEAT e LENGTH fazem um tratamento no campo código de tal forma que o banco passa a ordenar considerando um prefixo (neste caso vários zeros).

Para mais informações, nada melhor do que a documentação oficial.

É isso aí! Até +.

Comentários 6 comentários

Dec 01 2006

Tentativa de Singleton usando Delphi

Autor: Marcos Dell Antonio - Categorias: Delphi

O design pattern Singleton garante que somente uma instância de determinada classe estará disponível durante todo o ciclo de vida da aplicação.

Hoje precisei implementá-lo em uma aplicação servidora. Tenho uma estrutura assim:

  • Arquivo de configuração XML (Configuracoes.xml);
  • Classe que faz read/write neste arquivo (TXMLCfgClass);
  • Classes que utilizam a TXMLCfgClass para acessarem as suas configurações (TServidorCfgClass, TBackupCfgClass, TBancoDadosCfgClass, etc.).

O mesmo arquivo XML será compartilhado pelas três classes de acesso às configurações, ou seja, uma única instância do objeto TXMLCfgClass deverá estar disponível para as três classes. Aqui é que entra o Singleton: ele garante que somente uma instância da classe TXMLCfgClass existirá durante todo o ciclo de vida da aplicação.

Tudo muito bonito e interessante na teoria. Na prática a coisa é um pouquinho diferente.

Tentei criar uma propriedade (ou field) privado e estático na classe TXMLCfgClass, mas não consegui. Uma solução semelhante é utilizar uma variável na seção implementation, desta forma:

Declaração da variável
1 implementation 2 3 var 4 xmlCfg: TXMLCfgClass;

Implementação do Singleton (método estático e público):
class function TXMLCfgClass.GetInstance: TXMLCfgClass; begin // GetInstance if (xmlCfg = nil) then xmlCfg := TXMLCfgClass.Create; Result := xmlCfg; end;

A abordagem é muito interessante, pois se bem utilizada (chamando somente o método GetInstance) funcionará. No entanto, ainda é possível instanciar um objeto TXMLCfgClass utilizando o constructor Create.

Outra forma de implementar o Singleton é fazendo um override no método NewInstance da classe TObject. Encontrei este exemplo que é específico para o Delphi 5, no entanto, fiz os testes com o Delphi 6 e funcionou também.

Aqui também não são só flores. De acordo com o help do Delphi 6, ao fazer o override do NewInstance não devemos chamar inherited NewInstance no final, mas sim InitInstance. No exemplo que fiz, utilizei o inherited NewInstance e funcionou perfeitamente. O único detalhe é que não deve-se chamar o FreeAndNil após utilizar o objeto.

Veja o exemplo:

Declaração pública:
class function NewInstance: TObject; override;

Implementação:
class function TCliente.NewInstance: TObject; begin if (cliente = nil) then cliente := TCliente(inherited NewInstance); Result := cliente; end;

No fim das contas, acabei optando pela primeira opção. Faça os testes e escolha uma delas. ;)

É isso aí! T+

Comentários Faça o seu comentário

Nov 29 2006

Delphi: Exemplo utilizando CreateProcess

Autor: Marcos Dell Antonio - Categorias: Delphi

Já comentei há alguns dias que existem três formas para rodar uma aplicação externa em um programa escrito em Delphi. São elas: ShellExecute, WinExec e CreateProcess.

Ontem precisei executar o MySQL Dump para gerar um backup de uma base de dados. Até aqui nenhum segredo, pois o WinExec faz isso facilmente. No entanto, eu precisava manter a aplicação esperando até que o comando terminasse de executar.

A única forma de fazer isto é utilizando o CreateProcess. Veja a rotina que utilizei:

Exemplo utilizando o CreateProcess
Download do código fonte

Para realizar o dump, usei o seguinte comando:

cmd.exe /c “c:\executavel\Utils\mysqldump.exe -hHOST -uUSUARIO -pSENHA -c –add-drop-table teste > C:\teste.sql”

Algumas informações importantes sobre esta rotina:

  • O CreateProcess requer vários parâmetros. No entanto, veja que a rotina acima pede um só, portanto ela assume um comportamento padrão;
  • Enquanto a aplicação externa está em execução, o programa que criou ela fica esperando (graças ao WaitForSingleObject);
  • Como utilizei o cmd.exe para rodar o MySQL Dump, precisei passar o nome do executável e seus parâmetros dentro de ” ” (até descobrir isso passaram-se algumas horas);
  • A aplicação criada é executada em modo SW_HIDE, ou seja, escondida.

Fonte: Dicas de Delphi

Não deixe de conferir as sugestões de livros sobre Delphi que eu elaborei. Lá você encontrará informações sobre este e outros comandos.

É isso aí! T+ :P

Comentários 14 comentários

« Página anterior - Próxima página »