Pensei em vários tÃtulos para o artigo, mas o “melhorzinho” foi esse, rsrs.
Desenvolvedores Delphi, esse artigo é para vocês! O objetivo é compartilhar alguns cuidados com três eventos muito utilizados na programação: OnExit
, OnChange
e OnAfterScroll
. Vocês já empregaram estes eventos para alguma finalidade, certo? Pois bem, confira o artigo e verifique se vocês os utiliza adequadamente.
Esse artigo é uma homenagem ao Luis Olivetti, um grande companheiro de trabalho que possui uma visão bastante crÃtica com relação à expressividade e desempenho do código-fonte. Ele é um verdadeiro Clean Coder!
Antes de iniciar o artigo, deixo claro que as recomendações abaixo não são uma regra, até pelo fato de que cada programador geralmente utiliza técnicas particulares para implementação, então, talvez, você possa discordar de alguns pontos.
Como forma didática, para cada evento, farei uma breve explicação, apresentarei um cenário e, por fim, uma sugestão, ok?
OnExit
Como sabemos, este evento é disparado quando saÃmos de um campo. Muitos desenvolvedores usam o OnExit
para executar validações, como evitar valores nulos, valores zerados ou datas inválidas. Porém, em algumas ocasiões, o evento “prende” o usuário no campo e pode comprometer a usabilidade da tela. Como? Veja a seguir.
Suponha que uma tela tenha um componente TButton
e um componente TEdit
, como a imagem abaixo. O botão serve para fechá-la e o campo de texto é usado para digitar um nome qualquer.
No evento OnExit
do TEdit
, temos o seguinte código:
1 2 3 4 5 6 7 8 |
procedure TForm1.Edit1Exit(Sender: TObject); begin if Trim(Edit1.Text) = EmptyStr then begin ShowMessage('É necessário preencher o nome.'); Edit1.SetFocus; end; end; |
Ótimo. Nossa validação está funcional. Se o usuário tentar sair do campo sem digitar o nome, a aplicação exibe uma mensagem e foca novamente o campo para que ele possa preenchê-lo. Agora, faça um teste: deixe o campo em branco e tente clicar no botão para fechar a tela. O que acontece?
Ops, e agora? O usuário não consegue mais deixar a tela. A única saÃda é digitar o nome no campo, mas, e se não for obrigatório? E se o usuário quiser apenas cancelar a operação?
Sugestão: Procure implementar as validações em outros eventos, como o OnClose
da tela. Se for uma inserção de registro, eu recomendo implementá-la no evento OnBeforePost
do DataSet. Logo, todas as validações estarão centralizadas em um único evento, propiciando um ambiente menos “flexÃvel” para o usuário.
1 2 3 4 5 6 7 8 9 |
procedure TForm1.DataSet1BeforePost(DataSet: TDataSet); begin if Trim(Edit1.Text) = EmptyStr then begin ShowMessage('É necessário preencher o nome.'); Edit1.SetFocus; Abort; end; end; |
OnChange
Já discuti bastante sobre o evento OnChange
com outros desenvolvedores. Na maioria das vezes, o evento é utilizado para pesquisar registros em uma Grid conforme o usuário digita o texto. No entanto, lembre-se que essa pesquisa pode solicitar uma requisição ao banco de dados e afetar o desempenho da aplicação. Leia o código:
1 2 3 4 5 6 7 8 |
procedure TForm1.Edit1Change(Sender: TObject); begin Query1.Close; Query1.SQL.Clear; Query1.SQL.Add('Select * from CLIENTES where Nome like :Nome'); Query1.ParamByName('Nome').AsString := Concat(Edit1.Text, '%'); Query1.Open; end; |
Já deu para notar o problema, não é? Para cada letra digitada pelo usuário, a aplicação fecha a Query (ou um DataSet), executa uma instrução SQL informando o texto como parâmetro, abre-a novamente e atualiza os registros na Grid. Isso significa que, ao digitar “Andre” (que demora, no máximo, 2 segundos), a aplicação faz 5 requisições ao banco de dados.
Imagine, então, se a aplicação for multiusuário ou cliente/servidor e 100 usuários estiverem na mesma tela pesquisando registros?
Sugestão: Considere adicionar um botão de pesquisa logo à direita do campo de busca e mover o código de consulta para o evento de clique. Dessa forma, a requisição ao banco de dados será realizada somente quando o usuário clicar no botão.
Aproveitando o ensejo, há outra alternativa. Ao invés de solicitar uma consulta ao banco de dados, faça uso da propriedade Filter
do DataSet, que permite filtrar os registros em memória na aplicação cliente. Além de reduzir o tráfego na rede, a pesquisa não irá sobrecarregar o banco de dados com requisições simultâneas.
1 2 3 4 5 |
procedure TForm1.Edit1Change(Sender: TObject); begin DataSet1.Filter := 'Nome like ' + Concat(Edit1.Text, '%'); DataSet1.Filtered := True; end; |
OnAfterScroll
A advertência sobre esse evento é basicamente a mesma do evento OnChange
, mas, mesmo assim, vou apresentar um cenário diferente.
No artigo sobre boas práticas de desenvolvimento de software, mencionei a inconveniência da barra de rolagem horizontal em uma Grid, principalmente quando dados importantes não ficam visÃveis para o usuário. Como solução, sugeri a inserção de um painel abaixo da Grid para exibir dados adicionais conforme o usuário navega entre os registros.
Pois bem, a atualização desses dados pode ser feita no evento OnAfterScroll
do DataSet, que é disparado quando a posição do cursor do DataSet é alterada. Porém, se estes dados estiverem em tabelas diferentes, cairemos na mesma questão: a aplicação executará uma consulta no banco de dados para cada movimentação do DataSet!
E tem mais: sabemos que com o botão de scroll do mouse, podemos navegar, por exemplo, em 10 registros da Grid em poucos segundos. O que isso significa? Dez requisições ao banco de dados!
Tudo bem, o cenário acima pode ser solucionado se utilizarmos um Inner Join para trazer os dados relacionados de diferentes tabelas e armazenarmos em memória, mas o evento OnAfterScroll
também é utilizado para situações diferentes, como a exibição de dados agregados. Por exemplo, o desenvolvedor pode utilizá-lo para consultar a quantidade de compras do cliente selecionado na Grid e exibi-la em algum local da tela.
1 2 3 4 5 6 7 8 9 10 |
procedure TForm1.DataSet1AfterScroll(DataSet: TDataSet); begin QueryCompras.Close; QueryCompras.SQL.Clear; QueryCompras.SQL.Add('Select COUNT(1) as Qtde from COMPRAS where CodCliente = :CodCliente'); QueryCompras.ParamByName('CodCliente').AsInteger := {código do cliente}; QueryCompras.Open; EditTotalCompras.Text := QueryCompras.FieldByName('Qtde').AsString; end; |
Para complicar, a instrução SQL contém um COUNT
. Neste caso, para cada movimentação na Grid, o banco de dados terá que contar todas as compras do cliente e retornar para a aplicação! Eita!
Sugestão: Assim como recomendando para o evento OnChange
, considere a possibilidade de reduzir as requisições ao banco de dados. Crie uma nova tela com as informações adicionais do registro, como a quantidade de compras exemplificada no código acima. Instrua o usuário para que, se desejar visualizar mais informações do registro selecionado na Grid, basta clicar no botão para abrir uma nova tela com os dados.
Essas são as minhas recomendações, pessoal. Espero que sejam úteis.
Lembrem-se sempre de ponderar a utilização dos eventos acima, principalmente para atender requisitos não-funcionais, como a usabilidade e o desempenho da aplicação.
Até a próxima, leitores. Obrigado pela visita!
Amigo André, eu que tenho que homenageá-lo. É uma honra poder trocar conhecimentos com você, aprendi muito com sua experiência e força de vontade. Você crescerá ainda mais.
E aproveitando, uma dica no exemplo do onbeforepost, nós poderÃamos colocar esse dataset em um data module e validar o campo, em vez do edit1. E o dataset mesmo enviaria o foco. Tem um evento que não lembro, mas sei que tem hehe.
Vlw piá!
Fala, grande Luis!
Sou eu quem deve agradecê-lo pelo aprendizado! O Pair Programming que realizamos nas nossas jornadas de programação já agregou muito ao meu conhecimento!
E olhe só, comentei sobre a sua visão crÃtica e você já apontou uma sugestão sobre o evento OnBeforePost, rsrs! Bem lembrado, amigo!
Valeu, Sr. Olivetti!
Boa tarde, André! Referente a parte sobre OnChange, eu uso o seguinte na minha tela de estoque:
dm_dados.tblEstoque.Locate(‘EST_PRODUTO’,edit1.Text,[loPartialKey,loCaseInsensitive]);
Isso será um erro quando tiver vários usuários pesquisando?
Muito boa matéria, e mais uma vez muito obrigado por toda ajuda.
Olá, André, tudo bem?
A função
Locate
é executada em memória na aplicação cliente, portanto, não será um problema! A pesquisa de um usuário não irá interferir na pesquisa de outros usuários e, além disso, como executa do lado do cliente, não exige requisições ao banco de dados.Abraço!
Andre, gostei do seu comentário, muito bom vc.
Queria fazer uma pergunta, eu tenho um código para acusar erros nos campos datas, mas queria que depois do erro limpasse o campo, o meu é DBEdit. Vc poderia ajudar por favor.
Olá, Luis Carlos, tudo bem?
Para limpar um campo que está ligado a um DBEdit, basta utilizar o método Clear do TField:
Abraço!