Social Icons

Featured Posts

domingo, 23 de abril de 2017

O Lado Negro do "Application.ProcessMessages"

Aviso!
Este artigo é uma tradução do artigo: The Dark Side of Application.ProcessMessages in Delphi Applications.


Quando um evento é tratado no Delphi (como o evento OnClick de um botão), algumas vezes sua aplicação fica ocupada, por exemplo quando necessita escrever um arquivo grande ou realizar a compressão de dados.

Se você fizer isso, perceberá que sua aplicação vai ficar como se estivesse bloqueada. Seu formulário não poderá ser movido e os botões não mostrarão sinal de vida.

Da a impressão que a aplicação está travada.

A motivo é que uma aplicação Delphi é thread única (single thread). O código que você escreve representa um monte de procedimentos que são chamados pela thread principal do delphi sempre que um evento ocorre. O restante do tempo a thread principal está tratando mensagens do sistema e outras coisas como formulários e funções de manipulação de componentes. 

Então, se você não finalizar a processamento demorado que seu evento está fazendo, você irá impedir que a aplicação trate as mensagens.

Uma solução comum para este tipo de problema é chamar "Application.ProcessMessages". "Application" é um objeto global da classe TApplication.

O Application.ProcessMessages processa todas as mensagens que estão em espera, tais como os movimentos da janela, cliques de botões e assim por diante. É comumente usado como uma solução simples para manter sua aplicação em "funcionamento". 

Infelizmente o mecanismo por traz do "ProcessMessages" tem suas próprias características, as quais podem causar grande confusão!

O QUE O PROCESSMESSAGES FAZ?

"ProcessMessages" processa todas as mensagens do sistema que estão aguardando na fila de mensagens da aplicação. O windows usa mensagens para conversar com todas as aplicações que estão em execução. A interação do usuário com o formulário é realizada através de mensagens e o "ProcessMessages" processa todas elas. Se o mouse está apertando um botão, por exemplo, o ProcessMessages faz tudo que poderia acontecer nesse evento como repintar o botão com o estado de "pressionado" e, claro, a chamada do processamento do OnClick() caso você o associou.

Este é o problema: qualquer chamada ao ProcessMessages pode conter uma chamada recursiva a qualquer evento novamente. Aqui está um exemplo:

Use o seguinte código para o evento OnClick. A instrução "for" simula um processamento longo e faz uma chamada ao ProcessMessages de vez em quando.

Este foi simplificado para melhor legibilidade:

{in MyForm:}
   WorkLevel : integer;
 {OnCreate:}
   WorkLevel := 0;
 
 procedure TForm1.WorkBtnClick(Sender: TObject) ;
 var
   cycle : integer;
 begin
   inc(WorkLevel) ;
   for cycle := 1 to 5 do
   begin
     Memo1.Lines.Add('- Work ' + IntToStr(WorkLevel) + ', Cycle ' + IntToStr(cycle) ;
     Application.ProcessMessages; 
     sleep(1000) ; // or some other work
   end;
   Memo1.Lines.Add('Work ' + IntToStr(WorkLevel) + ' ended.') ;
   dec(WorkLevel) ;
 end;

SEM "ProcessesMessages" as seguintes linhas são escritas no memo, se o botão for pressionado DUAS VEZES em um curto espaço de tempo.

 - Work 1, Cycle 1
 - Work 1, Cycle 2
 - Work 1, Cycle 3
 - Work 1, Cycle 4
 - Work 1, Cycle 5
 Work 1 ended.
 - Work 1, Cycle 1
 - Work 1, Cycle 2
 - Work 1, Cycle 3
 - Work 1, Cycle 4
 - Work 1, Cycle 5
 Work 1 ended.

Enquanto o procedimento está ocupado, o formulário não mostra nenhuma reação, mas o segundo click foi colocado na fila de mensagens windows.

Logo após o "OnClick" terminar ele será chamado novamente.
INCLUINDO "ProcessMessages", a saída pode ser muito diferente:

 - Work 1, Cycle 1
 - Work 1, Cycle 2
 - Work 1, Cycle 3
 - Work 2, Cycle 1
 - Work 2, Cycle 2
 - Work 2, Cycle 3
 - Work 2, Cycle 4
 - Work 2, Cycle 5
 Work 2 ended.
 - Work 1, Cycle 4
 - Work 1, Cycle 5
 Work 1 ended.


Desta vez o formulário parecerá estar em funcionando novamente e aceita qualquer interação do usuário. Então o botão é pressionado novamente na metadade do seu primeiro processamento,o qual será tratado imediatamente. Todos os eventos de entrada serão tratados como qualquer outra chamada de função.

Em teoria, durante toda chamada ao "ProcessMessages" QUALQUER quantidade de cliques e mensagens do usuário podem acontecer "No mesmo instante"

Então tome cuidado em seu código!

Um outro exemplo (em um simples pseudo-código)

procedure OnClickFileWrite() ;
 var myfile := TFileStream;
begin
  myfile := TFileStream.create('myOutput.txt') ;
  try
    while BytesReady > 0 do
    begin
      myfile.Write(DataBlock) ;
      dec(BytesReady,sizeof(DataBlock)) ;
      DataBlock[2] := #13; {test line 1}
      Application.ProcessMessages; 
      DataBlock[2] := #13; {test line 2}
    end;
  finally
    myfile.free;
  end;
end;


Esta função escreve uma quande quantidade de dados e tenta "desbloquear" a aplicação usando "ProcessMessages" a cada vez que um bloco de dados é escrito. Se o usuário clica novamente no botão, o mesmo código será executado enquanto o arquivo ainda é escrito. Portanto, o arquivo não pode ser aberto uma segunda vez e o procedimento falha.

Talvez sua aplicação faça uma recuperação de erro como liberar os buffers.

Como um possível resultado o "bloco de dados" será liberado e o primeiro código causará "repentimanente" um "Access Violation" quando tentar acessa-lo. Nesse caso: o teste na linha 1 funcionará, o teste na linha 2 falhará.

A melhor maneira:

De uma maneira fácil, você poderia setar o formulário "enabled := false", que bloqueia todos as entradas do usuário, mas NÃO mostrará isso ao usuário (todos os botões não ficarão acinzentados).

Uma melhor forma seria setar todos os botões para "desabilitado", mas isto pode ser complexo se você quer manter um botão "Cancelar" por exemplo. Você também precisaria percorrer todos os componentes para desabilitá-los e quando eles são ativados novamente, você precisa checar se existe algum que esteja desabilitado.

Você pode desabilitar os controles filho de um contêiner quando a propriedade Enabled for alterada.

Como o nome de classe "TNotifyEvent" sugere, ele só deve ser usado para reações de curto prazo para o evento. Para o código demorado, a melhor maneira em minha honesta opinião, é colocar todo o código "lento" em uma Thread própria.

Quanto aos problemas com "ProcessMessages" e / ou a ativação e desativação de componentes, o uso de uma segunda thread parece não ser muito complicado em tudo.

Lembre-se de que mesmo as linhas simples e rápidas de código podem ficar paralisadas por segundos, por exemplo: abrir um arquivo em uma unidade de disco pode ter que esperar até que a rotação da unidade tenha terminado. Não parece muito bom se o aplicativo parece falhar porque a unidade é muito lenta.

É isso aí. Da próxima vez que você adicionar "Application.ProcessMessages", pense duas vezes ;)

sábado, 1 de abril de 2017

TStyleManager - Temas Visuais no Delphi

Olá meus amigos delpheiros! Depois de muito tempo, estou retomando as postagens no blog. Eu mostrarei muitas novidades referentes as novas versões do delphi.

Uma dessas novidades que vem desde o delphi XE é a opção que permite estilizar o sistema com temas visuais sem a necessidade de alterar os componentes. Os estilos darão uma cara moderna para sua aplicação, além de permitir que os usuários escolham o tema visual da aplicação.

Para usar os temas visuais do delphi a opção Enable Runtime Themes deve estar marcada . Crie uma nova aplicação VCL e  vá ao menu:Project > Options > Appliction e marque as opções abaixo.


Agora selecione quais estilos você deseja utilizar para aplicação, para isso vá ao menu Project > Options > Application > Appearance. Você vai perceber que já existem vários estilos disponibilizado pelo delphi.


Informação 
Na opção Default style você escolherá qual será o tema inicial que sua aplicação apresentará.
Agora vamos montar um formulário para aplicar nossos estilos e permitir que o usuário escolha qual tema deseja utilizar. Arraste para ele alguns componentes conforme a imagem abaixo.


Informação 
Adicione o uses Vcl.Themes para ter acesso a classe TStyleManager

Vamos criar uma função para criar a lista de temas em uma combobox e chame ela no onCreate do formulário.

procedure TForm1.IniciarEstilos;
var
  v :String;
  vIndice :Integer;
begin
  ComboBox1.Clear;

  //Cria uma lista com os temas habilitados para o executável
  for v in TStyleManager.StyleNames do
    ComboBox1.Items.Add(v);

  //Pega o nome do tema atual e seleciona na lista de temas
  vIndice :=  ComboBox1.Items.IndexOf(TStyleManager.ActiveStyle.Name);
  ComboBox1.ItemIndex := vIndice;
end;


Agora implemente o evento onSelect para que ao selecionar o item do ComboBox o tema seja aplicado imediatamente.

procedure TForm1.ComboBox1Select(Sender: TObject);
begin
  TStyleManager.SetStyle(ComboBox1.Text);
end;


É isso aí! Agora é só compilar e testar os resultados. Abaixo mostro alguns exemplos.




Baixe os fontes do artigo no GitHub

sábado, 16 de novembro de 2013

Usando TJvDBGrid – Parte 4

Continuando a série de artigos sobre o TJvDBGrid, hoje vou mostrar como colocar hints que mostram o conteúdo não visível de um campo, inclusive para campos blob.

Informação

A propriedade que usaremos é a ShowCellHint. Em nosso exemplo vamos usar basicamente:
1 TForm
1 TClientDataSet
1 TDataSource
1 TJvDBGrid.

No TClientDataSet criar os seguintes campos:
CODIGO :Integer
NOME :String
TELEFONE :String
Deixe a propriedade ShowCellHint com o valor true
Agora basta iniciar algumas informações na Grid. O exemplo de código abaixo fará isso.

procedure TForm2.FormCreate(Sender: TObject);
begin
   ClientDataSet1.CreateDataSet;

   ClientDataSet1.Append;
   ClientDataSet1CODIGO.AsInteger := 1;
   ClientDataSet1NOME.AsString := 'Jocimar de Souza Medeiros';
   ClientDataSet1TELEFONE.AsString := '44 9999-2233';
   ClientDataSet1.Post;

   ClientDataSet1.Append;
   ClientDataSet1CODIGO.AsInteger := 2;
   ClientDataSet1NOME.AsString := 'Silvana Rodrigues de Mello';
   ClientDataSet1TELEFONE.AsString := '44 9899-2266';
   ClientDataSet1.Post;
end;

Dica
  • Lembre-se de que a propriedade showHint deverá estar com o valor true também

É isso aí então pessoal! Agora quando uma informação na TJvDBGrid ficar oculta parcialmente e passarmos o mouse sobre essa informação, será mostrada um hint com o restante do conteúdo.


Em caso de dúvidas, sugestões ou reclamações, por favor comentem o post. E se a informação ajudou você, curta o post nas redes sociais usando os ícones abaixo.
Baixe os fontes do artigo no GitHub

sábado, 24 de agosto de 2013

Fonts do Firefox em Negrito

Imagem fonte arialAguem já teve problema onde algumas páginas no firefox ficavam com a fonte em negrito?! Bom, eu estava com esse problema até agora pouco, já havia semanas que eu estava a procura de uma solução e estava a ponto de realizar a formatação do meu notebook.

Após ler vários fóruns, consegui resolver o problema. O problema estava na minha fonte arial que apesar de estar instalada, não estava funcionando, basicamente o problema seria resolvido reinstalando a fonte, no entanto eu fiz isso e ainda continuei com problema, até que eu descobrir um update da microsoft que resolveu o problema http://www.microsoft.com/en-us/download/details.aspx?id=16083. Esse update instalou a fonte original arial do sistema, quando eu reinstalei a atualização não teve efeito porque a fonte arial que usei provavelmente não era a original do windows. Essa atualização é para windows 7.

Espero ter ajudado!