30 de agosto de 2009

Usando Interfaces em Delphi

Outro dia, estava comentando o uso do Design Pattern Prototype aqui no blog e para implementar o exemplo utilizei uma interface em Delphi. Por causa disso, algumas pessoas me perguntaram o que vem a ser esta tal de interface. Como vou utilizar o conceito para avançar no assunto "Delphi interagindo com scripts", achei melhor postar antes a respeito das interfaces.

Neste contexto, interface não tem nada a ver com telas de um programa ou partes do hardware mas sim com um recurso que algumas linguagens de programação - como Java e, mais recentemente, o Delphi - disponibilizam para tornar mais limpo o código do programa, que de outra forma teria que ser escrito usando heranças múltiplas (uma classe herdando de mais de uma classe base). Além do mais, Delphi e Java não suportam herança múltipla.

Uma interface pode ser encarada como um contrato que tem que ser respeitado. Esse contrato é descrito em termos de métodos e/ou funções que devem ser esperados. Então, ao criar uma nova Classe você pode declará-la como implementadora de uma (ou mais) interface. Isto obriga a nova classe a respeitar fielmente o contrato, ou seja, ela terá obrigatoriamente que providenciar codificação para todas as funções e métodos declarados na interface, inclusive respeitando suas assinaturas - isto é, a quantidade e os tipo dos parâmetros e, no caso de funções, o tipo de dado retornado. Em outras palavras, as Interfaces definem comportamento e as Classes implementam esse comportamento.

E, qual a utilidade disso ? Você poderá criar classes genéricas que façam uso das funções declaradas por uma interface e, então, qualquer classe que implementar a tal interface poderá se beneficiar da classe que a manipula. Um exemplo clássico desse conceito é a criação de uma classe genérica para ordenação. Tudo que é preciso fazer é declarar uma interface com uma função para comparar 2 valores; a classe de ordenação deve manter uma lista com os objetos a serem ordenados e usar a função de comparação para determinar a ordem correta de todos os elementos. Com isso, é possível ordenar listas de objetos de qualquer classe que implemente a interface de ordenação.

Em termos da linguagem de programação, a declaração de uma interface é muito parecida com a declaração de classes. Basicamente, é composto por um nome, a palavra chave interface, uma lista de métodos e/ou funções e end para encerrar. Segue como exemplo a declaração de uma interface para ordenação:
IWSortable = interface
function CompareTo (Elemen: TInterfacedObject): Integer;
end;
Para uma classe dizer que implementa uma determinada interface, usa-se sintaxe semelhante à da herança. No exemplo abaixo, a classe TWPessoa declara que faz uma implementação da interface IWSortable:
TWPessoa = class(TInterfacedObject, IWSortable)
public
Nome : String;
Idade : Integer;
function CompareTo (AElemen: TInterfacedObject) : Integer;
end;
Em Delphi, todas as interfaces são herança de uma mesma interface base chamada IUnknown, o que exige a implementação de alguns métodos. A classe TInterfacedObject do Delphi já faz essa implementação e é por isso que a utilizo como base para TWPessoa. Se fosse necessário incluir outras interfaces na classe TWPessoa, elas deveriam vir separadas por vírgula após o nome da classe pai.

Alguns fatos sobre as interfaces:
Todos os métodos e funções declarados numa interface tem visibilidade public. Por isso, não é permitido incluir nenhuma das declarações de visibilidade e informá-las ocasionará um erro de compilação.
Os métodos e funções de uma interface são sempre abstract e virtual. Portanto, ambas as diretivas são dispensáveis na declaração da interface. Informá-las ocasionará um erro de compilação.
Não é permitido incluir campos numa interface. Propriedadades, no entanto, são admitidas desde que as cláusulas read e write façam referência a funções e métodos.
Uma classe pode implementar quantas interfaces forem necessárias.
É permitido criar heranças de interfaces.


26 de agosto de 2009

Acrescentando suporte a Scripts em aplicações Delphi - parte 3

No último post, montei uma aplicação Delphi mostrando como usar o Script Control para avaliar uma expressão VBScript em tempo de execução. Lá, usei algumas funções básicas do componente e uma técnica simples para interagir com o usuário. Agora, vou avançar um pouco, modificando o programa do post anterior para dar a ele suporte à criação de rotinas completas que poderão também ser usadas para avaliar expressões. Ou seja, o usuário poderá avaliar expressões mais complexas, tendo também a liberdade de criar funções que encapsulam seus cálculos mais comuns.

Acrescentei ao programa uma nova guia com título "Métodos e Funções" na qual inclui um TRichEdit (onde o usuário poderá criar suas funções), um TListBox (apenas para listar as funções disponíveis após a criação) e um botão. A resposta ao clique do botão adiciona ao engine do VBScript as funções criadas pelo usuário:
_ScriptControl.Reset;
InitScriptControl;
try
_ScriptControl.AddCode(reFuncao.Text);
except
end;
A primeira instrução, Reset, faz com que a instância do componente Script Control descarte todo a configuração feita nele anteriormente, incluindo funções e métodos que já tenham sido adicionados. Isto evita que se tente criar uma função com um nome que já existe - todas elas serão recriadas e poderão até mesmo executar cálculos diferentes da versão que havia antes. Como toda a configuração foi descartada, a segunda linha é uma função que agrupa os comandos de configuração que haviam no OnCreate do Form, mostrado no outro post.

O que está dentro do Try/Except adiciona ao engine do VBScript no componente as funções digitadas pelo usuário no Rich Text. Erros de sintaxe embutidos no código das funções só serão reportados quando for avaliada uma expressão que use a função problemática.

Após termos adicionado as funções, podemos recuperar no programa informações a respeito delas na propriedade Procedures do componente. O código abaixo põe no ListBox o nome das funções criadas com o AddCode:
lbFuncoes.Clear;
for i := 1 to _ScriptControl.Procedures.Count do
lbFuncoes.AddItem(_ScriptControl.Procedures.Item[i].Name, Nil);
A propriedade Procedures é uma lista com as funções e métodos que adicionamos ao engine e cada elemento recuperado em Item traz informações sobre uma dessas funções/métodos, dizendo se aceitam parâmetros e em qual quantidade, se têm valor de retorno, etc.

Algo importante a observar aqui é que todo código solto que seja adicionado através do método AddCode é executado imediatamente. Por código solto quero dizer as linhas de script que não façam parte da declaração de uma função ou método. Esta técnica permite executar um script inteiro de uma vez, bastando adicionar todo o código numa única chamada ao AddCode. Isto também dá abertura a uma solução rudimentar para o problema de fazer o script enxergar variáveis do programa. Crie no script uma variável - digamos que se chame y - e a adicione ao componente com AddCode:
Dim y
No programa Delphi, antes de avaliar uma expressão que vá precisar do valor de y, adicione uma atribuição como a seguinte:
_ScriptControl.AddCode('y = ' + IntToStr (Y));
Ao fazer isso, o valor da variável y do seu programa Delphi é imediatamente transferido para a variável y que existe no script. A partir desse momento, qualquer avaliação de expressão que use y enxergará o mesmo valor do programa Delphi.

Há uma forma mais elegante de se fazer isso e a mostrarei em outro post.Para baixar o exemplo completo - incluindo código e executável - clique aqui. O programa foi feito com Delphi 2005 mas deve funcionar com pouca ou nenhuma alteração em outras versões.


Mais Informações
Acrescentando suporte a Scripts em aplicações Delphi - parte 1 e parte 2, Documentação do ScriptControl no MSDN, Programando com VBScript

25 de agosto de 2009

Acrescentando suporte a Scripts em aplicações Delphi - parte 2

Dando continuidade ao post anterior sobre o MS Script Control, vou mostrar aqui um exemplo básico do uso do componente, montando uma aplicação onde o usuário poderá informar uma expressão qualquer em VBScript numa caixa de texto. Pressionar um botão na tela fará com que o programa avalie essa expressão, lançando numa outra caixa de texto o resultado obtido. Estou considerando que a expressão é numérica mas essa é uma questão de desenho da aplicação; se for necessário, pode-se usar qualquer dos outros tipos de dado da linguagem de script - no caso, VBScript.

Considerando que você já tem o fonte com as interfaces do Script Control, o primeiro passo é criar e configurar uma instância do componente. Coloquei esse código no evento OnCreate do Form:
_ScriptControl := TScriptControl.Create (Self);

_ScriptControl.SitehWnd := Handle;
_ScriptControl.Language := 'VBScript';
_ScriptControl.Timeout := -1;
_ScriptControl.AllowUI := true;
_ScriptControl.UseSafeSubset := false;
_ScriptControl.OnError := OnScriptError;
De acordo com o post anterior, este código configura o componente para que ele use 'VBScript'. Também permite interação via interface gráfica usando o próprio Handle do meu Form. O Timeout foi ajustado com valor -1, significando que o componente não gerará eventos relativos à expiraração de tempo enquanto executa um script demorado. Não falei da propriedade UseSafeSubset: ela determina se o componente deve restringir ou não os comandos aceitos de forma que somente os considerados seguros possam ser executados. Ajustei-a aqui para false, garantindo que todo o conjunto de funções do VBScript estará disponível. Ajuste o valor dela para true se segurança for uma preocupação pois assim o componente não permitirá a execução de comandos que possam burlar as permissões de acesso.

O evento OnError é configurado para executar a função OnScriptError sempre que houver um erro durante a execução de scripts - mesmo erros de sintaxe são reportados por este método:
procedure TfTesteScript.OnScriptError(Sender : TObject);
var lMsg : String;
lControl : TScriptControl;
begin
lControl := Sender As TScriptControl;
lMsg := lControl.Error.Source + #13#10;
lMsg := lMsg + 'Erro ' + IntToStr (lControl.Error.Number) + #13#10;
lMsg := lMsg + lControl.Error.Description + #13#10;
lMsg := lMsg + 'Linha ' + IntToStr (lControl.Error.Line);
lMsg := lMsg + ' ,Coluna ' + IntToStr (lControl.Error.Column) + #13#10;
MostraErro (lMsg);
end;
O código reproduzido acima mostra que há uma propriedade do Script Control chamado Error que descreve toda a condição do erro que ocorreu, incluindo a linha e coluna dentro do código, um número de erro e uma descrição do erro. A documentação do objeto Error pode ser encontrada aqui.

Para concluir o exemplo, faltou mostrar como é feita a avaliação da expressão. O código é a resposta ao evento de clique no botão de avaliação:
procedure TfTesteScript.btAvaliarClick(Sender: TObject);
var res : Variant;
begin
try
res := _ScriptControl.Eval(edExpressao.Text);
if VarType (res) <> varEmpty then
edResult.Text := FloatToStrF(res, ffFixed, 15, 4);
except
edResult.Text := '## erro ##';
end;
end;
Veja que o processo é bastante simples: basta chamar o método Eval do Script Control passando como parâmetro a expressão digitada pelo usuário. O valor retornado é um Variant mas como o valor que estou esperando na expressão é de ponto flutuante, posso fazer a conversão de float para string sem problema. Em todo caso, todo o trecho é protegido por um par Try/Except.

Neste exemplo, as expressões que podem ser avaliadas são fixas, isto é, não consigo informar na expressão uma variável que eu tenho criado e alimentado no meu programa. Apenas constantes, funções e operações matemáticas são válidas. Há uma lista aqui com as funções definidas no VBScript e que podem ser utilizadas na caixa com o texto para avaliação de expressões.

No próximo post, mostro como incluir métodos e funções para executar scripts mais complexos. Em outra oportunidade, mostro também como permitir que o script interaja mais efetivamente com o programa através da adição de objetos do programa ao engine de script.

Para baixar o programa de exemplo - incluindo código e executável - clique aqui. O programa foi feito com Delphi 2005 mas deve funcionar com pouca ou nenhuma alteração em outras versões.

24 de agosto de 2009

Acrescentando suporte a Scripts em aplicações Delphi - parte 1

Quando se desenvolve aplicações destinadas a empresas com perfis operacionais e ramos de negócio muito diferentes entre si é inevitável que surjam situações em que um Cliente precise que determinado cálculo seja feito de um jeito enquanto para Clientes de outros ramos esse cálculo tem que ser de outra forma. Há situações que envolvem a criação de fórmulas e cada Cliente pode querer aplicar uma diferente. Qual a melhor abordagem para tratar essas discrepâncias ? Criar telas diferentes resolvem parcialmente o problema já que isso implica em que as opções de cálculo têm que estar disponíveis dentro do programa para serem utilizadas.

Essas situações apareceram em diversos pontos no ERP da ABC71 e, para solucioná-los, sugeri o uso de um componente COM distribuído pela Microsoft chamado MS Script Control. O objetivo desse componente é permitir que programas de terceiros tirem proveito da infraestrutura de Scripts do Windows de uma forma direta e simples, sem ter que se preocupar em conhecer os pormenores dessa infraestrutura nem ter que implementar as interfaces relacionadas a ela. Basta entrar com expressões que se queira avaliar ou incluir um código script para execução e o componente se encarregará do resto.

Listo abaixo as propriedades e métodos disponibilizados pelo Script Control.
Propriedades
Language determina qual a linguagem de script será usada pelo componente. Atualmente, apenas "VBScript" e "JScript" são aceitos por padrão.
Timeout é o tempo (em milisegundos) para execução do script. Se esse tempo decorrer mas o script ainda não terminou, o evento OnTimeout é disparado. Use o valor (-1) para que o timeout seja desconsiderado.
AllowUI deve ser ajustado para true se o código do script puder interagir com o usuário através da interface gráfica, como por exemplo usando a função MsgBox.
SitehWnd é o handle para a janela que exibirá interações com a interface gráfica.

Métodos
AddCode é usado para adicionar código script ao componente. É permitido incluir funções, métodos (sub rotinas), criação de variáveis, etc. Código que estiver fora de uma definição de funções ou métodos será executado imediatamente.
Eval avalia a expressão passada como parâmetro, retornando o valor obtido dessa avaliação. A expressão terá que ser escrita na linguagem estipulada na propriedade Language, estando sujeita à sintaxe e operadorades dessa linguagem. É permitido chamar funções ou métodos adicionados com o AddCode.
Run permite executar uma função ou método adicionado anteriormente ao componente, independendo se eles possuem parâmetros ou não. No caso das funções, o valor de retorno pode ser recuperado.
AddObject mapeia um objeto do seu programa de modo que o script pode utilizá-lo como se fosse nativo da linguagem em que o script foi escrito.
Reset põe o componente em seu estado inicial, isto é, sem qualquer código ou objeto adicionado. As propriedades são ajustadas para o valor padrão e devem ser modificadas conforme a necessidade antes de poder usar de novo o componente.

Eventos
OnError ocorre sempre que um erro é encontrado no código do script.
OnTimeout é disparado quando o tempo para execução terminou mas o script ainda está em execução. Esse tempo é configurado pelo valor da propriedade Timeout.

É recomendável que a execução de scripts com esse componente seja feita na Thread principal da aplicação pois certos comandos não são thread-safe, abrindo a possibilidade de pipocar erros estranhos em pontos aleatórios do código.

O Script Control é distribuído como uma biblioteca COM e, portanto, o primeiro passo é pedir ao Delphi que crie um fonte pascal baseado nessa biblioteca para que possamos utilizá-lo. Há um post aqui mostrando como fazer isso - basta substituir a biblioteca indicada por Microsoft Script Control.

No próximo post, coloco um exemplo prático do uso dos Script Control interagindo com um programa Delphi.

21 de agosto de 2009

Design Patterns com Delphi : Prototype

O último dos Design Patterns criacionais estabelecidos pela GoF é o Prototype, cujo nome vem do fato de que objetos são instanciados com base num protótipo - ou modelo - existente. O Prototype, portanto, consiste na criação de objetos através da cópia do estado (valores das propriedades) de um objeto pré existente, gerando um clone desse objeto. Este tipo de cópia é mais útil quando há objetos aninhados, isto é, quando uma classe possui membros que são instâncias de outras classes, resultando numa hierarquia complexa de objetos cujo estado deve estar fielmente reproduzido na cópia feita.

Imagine, por exemplo, um sistema onde o usuário, ao se logar, é associado a um objeto contendo as configurações de sua sessão. Ele pode manter abertas várias telas ao mesmo tempo e aplicar temporariamente novas configurações em cada uma delas - o tamanho ou tipo do fonte (letras) ou o contraste das cores apresentadas para melhor visualização. Ao entrar na nova tela, é feita uma cópia da sessão, incluindo seus objetos internos, de modo que alterações introduzidas nas configurações da tela não afetam a configuração global existente na sessão original.

Para implementar o padrão Prototype trabalha-se basicamente com 3 classes, representadas no diagrama UML abaixo.

Diagrama UML para Prototype
A classe TWPrototype é abstrata e apenas introduz o comportamento esperado para um protótipo, que é poder gerar um clone de si mesmo. As classes TWSessao, TWFonte e TWColors são heranças de TWPrototype, o que siginifica que elas podem criar cópias idênticas de si mesmas. Veja que a classe TWSessao possui propriedades internas dos tipos TWFonte e TWColors, o quê permite à sessão repassar a mensagem de clonagem para garantir que todas as propriedades têm realmente os mesmos valores da sessão original, não importando que essas propriedades também são objetos. E TWTela, referenciada na documentação do padrão como classe Client, é quem chama o método de clonagem, utilizando uma cópia idêntica da sessão para aplicar alterações temporárias - quem até podem se tornar permanentes, se for o desejo do usuário.

O Delphi suporta o conceito de interface (como o Java), o que facilita a implementação do Prototype. As classes TWSessao, TWColors e TWFonte não mantem entre si uma relação conceitual que justique colocá-las como herança de uma mesma classe base. Por isso, crio o TWPrototype como uma interface e digo que as outras classe implementam esta interface. Isso deixa o código mais fácil de manter já que a hierarquia das classes fica mais clara. Em Delphi, a declaração das classes seria algo assim:
TWPrototype=interface
function Clone : TInterfacedObject;
end;

TWColors = class(TInterfacedObject,TWPrototype)
{ outros membros aqui}
public
function Clone : TInterfacedObject;
end;

TWFonte = class(TInterfacedObject,TWPrototype)
{ outros membros aqui}
public
_Familia: String;
_Tamanho: String;
_Bold : Boolean;
function Clone : TInterfacedObject;
end;

TWSessao = class(TInterfacedObject,TWPrototype)
{ outros membros aqui}
public
_Id : String;
_Fonte: TWFonte;
_Colors: TWColors;
function Clone : TInterfacedObject;
end;

TWTela = class
{ outros membros aqui}
public
_Sessao : TWSessao;
constructor Create (ASessao: TWSessao);
end;

Para obter sua cópia da configuração da sessão, o construtor da tela apenas chama o método Clone da interface:
constructor TWTela.Create (ASessao: TWSessao);
begin
_Sessao := ASessao.Clone As TWSessao;
end;

Na TWSessao, o método Clone introduzido pela interface copia os dados atômicos "na mão" e clona os dados que são complexos:
function TWSessao.Clone : TInterfacedObject;
var nova : TWSessao;
begin
nova := TWSessao.Create;
nova._Id := _Id;
nova._Fonte := _Fonte.Clone As TWFonte;
nova._Colors := _Colors.Clone As TWColors;

Result := nova;
end;

Para o TWColors e TWFonte, o método Clone apenas copia as propriedades internas, que são todas atômicas (não há instâncias de classes entre eles).

O exemplo é simples para ser didático mas esse Pattern pode ter uma hierarquia bem mais complexa. Pode, por exemplo, estar em associação com o Pattern Abstract Factory para criar objetos "clonáveis" sem que a classe Client saiba exatamente o tipo real do objeto a clonar. Um exemplo disso seria ter um objeto com o perfil atual do usuário e esse perfil pudesse ser um dentre diversas especializações (Administrador, usuário do Financeiro, do marketing, etc.) e parte do perfil pudesse ser temporariamente modificado.

18 de agosto de 2009

PowerBuilder pronto para a plataforma .Net

A Sybase está despendendo um bom esforço para implementar a plataforma .Net como um dos alvos no IDE do PowerBuilder. Um dos argumentos de venda da ferramenta é justamente permitir que se evolua a plataforma em que seus programas podem executar com pouco ou nenhum esforço. Para os desenvolvedores de software que usam PowerBuilder, é otimo - certamente vai poupar muito trabalho de migração. Reproduzo abaixo alguns trechos traduzidos a partir da matéria publicada na InfoWorld. A página com a matéria original pode ser acessada aqui.

Após vários anos de trabalho, a Sybase está pronta para entregar o último passo no plano de mover o PowerBuilder na direção do modelo de programação da plataforma .Net.

A empresa disponibilizará na terça-feira (18/08) uma versão beta do PowerBuilder 12, que incluirá 2 IDE's e uma ferramenta para migrar aplicações Win32 para a plataforma .Net. O PowerBuilder 12 representa a última de 4 etapas de um plano iniciado em 2002 para trazer os recursos do .Net para os que desenvolvem com a ferramenta. O programa de beta é aberto a qualquer um que queira participar; a versão final está prometida para a primeira metade de 2010.

A tecnologia DataWindow foi reescrita para oferecer suporte nativo à biblioteca gráfica da Microsoft WPF (Windows Presentation Foundation) e permitir a construção de aplicações .NET com código gerenciado. Também suportará Windows 7 quando este sistema for liberado, ainda este ano.

"[O PowerBuilder] simplifica o desenvolvimento e a joia da coroa é o componente chamado DataWindow, que permite acesso a dados, filtragem, ordenação e outros recursos afins", diz Sue Dunnell, Gerente de Produtos da Sybase. "Com o DataWindow, os programadores precisam escrever apenas 5 linhas de código para executar uma tarefa que pode requerer 300 linhas em C++ ou C#, diz ela".

Os IDE's incluídos na ferramenta são o chamado "Classic" para desenvolvimento de aplicações Win32 e o IDE.NET para desenvolvimento de aplicações WPF, este último montado sobre o shell do Microsoft Visual Studio. O WPF é um componente chave no .NET e ter uma ferramenta de conversão para levar código Win32 para o .NET Framework elimina a dor de uma migração cara, de acordo com um representante da Sybase.

Os programadores poderão construir aplicações Win32 e migrar o código para as tecnologias Windows Forms, WebForms, ASP.Net e WPF. Ou poderão criar aplicações WPF baseadas em código gerenciado .NET, o que trará benefícios como o uso do "sandbox" para implementar segurança. Além disso, poderá utilizar plug-ins do ecossistema do Visual Studio e componentes de terceiros. O acesso a dados é possível com virtualmente qualquer sistema de gerenciamento de dados, diz a Sybase.

"O que estamos entregando é o que desenvolvedores PowerBuilder já estão acostumados e, pararelamente, montamos um ambiente compreensivo para construção de aplicações .NET," diz Dunnell. A versão 12 permitirá aos desenvolvedores misturar os programas que eles já têm com .NET e evoluir para uma aplicação .NET pura.

Outros recursos incluem herança visual para janelas WPF e objetos do usuário, permitindo a criação de heranças de janelas ou controles. A ferramenta também oferece o recurso de arrastar-e-soltar para componentes visuais e acesso a propriedades, métodos e componentes nas janelas WPF.

A Sybase teve que alterar os planos com o .NET para poder incorporar as tecnologias WPF, Windows Communication Foundation e o Silverlight (para aplicações ricas via internet). "A Fase 4 era apenas para tornar a construção de aplicações .NET natural no PowerBuilder mas a Microsoft foi adiante e realmente mudou o significado do .NET," diz Dunnell.

"Para acomodar as mudanças do .NET, a Sybase liberou o PowerBuilder 11.5 no ano passado, oferecendo desenvolvimento cliente-servidor. Precisamos de um ano a mais para incorporar o WPF", acrescenta ela.



Trabalhando com Threads em Delphi - exemplo básico

Neste post, vou dar sequência ao post anterior sobre Threads e mostrar um exemplo bastante básico de como criar uma aplicação usando Thread em Delphi.

A ideia da aplicação é permitir que o usuário inicie um processo demorado mas ainda possa continuar interagindo com o programa, aí incluindo opção para cancelar a execução. Para isso, vou montar um Form com um botão para iniciar o processo, um outro botão para cancelar sua execução e uma barra de progresso para mostrar a evolução da execução:
Form para testar Thread
Completa a tela um temporizador (Timer1) que será ativado junto com a thread e terá a responsabilidade de monitorar o progresso da execução.

O primeiro passo será criar uma classe para representar a Thread. Em Delphi, acesse o menu File -> New -> Other -> Thread Object para criar um esqueleto vazio para a Thread; este caminho pode diferir um pouco, dependendo da versão do Delphi. O esqueleto gerado será basicamente o reproduzido abaixo:
TWMinhaThread = class(TThread)
protected
procedure Execute; override;
end;

O código gerado não sobrescreve o construtor padrão. Como teremos uma interação com o Form principal da aplicação, será preciso montar um construtor especial que aceite o nosso Form como parâmetro:
constructor TWMinhaThread.Create(CreateSuspended: Boolean; AForm: TForm);
begin
inherited Create (CreateSuspended);
_Form := AForm;
FreeOnTerminate := false;
end;

A primeira linha do construtor chama o construtor Create da classe base (inherited) passando-lhe o parâmetro para indicar se a thread deve iniciar imediatamente ou permanecer suspensa. FreeOnTerminate é ajustado como false de modo que nós teremos que destruir instâncias da classe manualmente.

A procedure Execute é onde deve ficar o código executado pela thread. Para simular um processo demorado, incluí nesta procedure um laço para contar até 100, aguardando alguns milissegundos em cada passo para nos dar a oportunidade de poder cancelar a execução.
procedure TWMinhaThread.Execute;
begin
_Posicao := 0;
while (not Terminated)
and (_Posicao < 100) do
begin
Inc (_Posicao);
Synchronize (AtualizaTela);
Sleep (10);
end;
end;

Chamo atenção para a linha Synchronize (AtualizaTela);. A função AtualizaTela faz a atualização da barra de progresso no Form, ajustando a propriedade Position com o valor atual de _Posicao:
procedure TWMinhaThread.AtualizaTela;
var lForm1 : TForm1;
begin
lForm1 := _Form As TForm1;
lForm1.pbProgresso.Position := _Posicao;
end;

O problema é que objetos da VCL não podem ser diretamente atualizados numa Thread que não seja a principal pois a VCL não é thread-safe. Assim, o método Synchronize tem que ser chamado para fazer o serviço. É passado a ele uma procedure sem parâmetros - no caso, o AtualizaTela - e a execução dessa procedure é sincronizada com a execução da Thread principal, garantindo a segurança da atualização no ambiente multi-threaded.

No Form, respondo ao clique do botão Iniciar criando uma instância da Thread para execução imediata e ligando o Timer que checa o status da execução dela:
_MinhaThread := TWMinhaThread.Create (false, Self);
Timer1.Enabled := true;

No evento do Timer, verificamos se a Thread chegou ao fim da execução para fazer a limpeza e avisar o usuário:
if (_MinhaThread._Posicao >= 100) then begin
Timer1.Enabled := false;
_MinhaThread.Free;
_MinhaThread := Nil;
end;

Não está representado neste trecho mas deve ser testado também se o usuário não pressionou o botão Cancelar, cuja resposta chama o método Terminate da Thread, ajustando sua propriedade Terminated. Veja que o laço while existente no método Execute da thread testa também o valor do Terminated para saber se o usuário solicitou o encerramento. Em caso afirmativo, a thread é interrompida imediatamente, mesmo que o processamento implementado nela ainda não tenha chegado ao fim.

Certamente há outras formas mais elaboradas de se monitorar o progresso da execução de uma Thread numa aplicação gráfica Windows. A que prefiro envolve a criação de uma mensagem no Windows, de modo que a própria Thread vai notificando sua evolução. Há outros posts aqui no blog mostrando também outras técnicas de sincronização (aqui e aqui).

Para fazer o download do projeto com o exemplo, clique aqui. Este exemplo foi construído com Delphi 2005 mas pode ser compilado em outras versões com poucas alterações.

17 de agosto de 2009

Trabalhando com Threads

Uma Thread - ou Linha de Execução - é a unidade básica de execução de programas dos Sistemas Operacionais modernos, tais como Windows e Linux. Isso significa que tais Sistemas Operacionais controlam o uso da CPU por Thread, alocando um determinado tempo para cada uma delas ter a oportunidade de executar seu código. Essa forma de trabalhar dá ao usuário a sensação de que todos os programas estão executando ao mesmo tempo quando, na verdade, cada um deles executa apenas por uma pequena fatia de tempo (time slice).

Quando executamos um programa, o Sistema Operacional se encarrega de criar a Thread principal. Se for necessário ou desejado, temos que criar as demais Threads manualmente. Todas as Threads criadas por um processo (programa) compartilham uma mesma área de memória chamada de address space e, por isso, elas conseguem acessar as mesmas variáveis. É fácil perceber a bagunça que vai virar se uma Thread gravar um valor numa variável e a Thread seguinte fizer a mesma coisa quando tiver seu tempo de CPU. O exemplo foi com uma variável mas podia ser um arquivo, uma impressora compartilhada ou outro recurso qualquer do Sistema Operacional.

Então, por que criar uma outra Thread no meu programa e ter que arcar também com a manutenção de um controle para acessar os recursos compartilhados ? Primeiro, o controle de acesso não é tão complexo. Segundo, há várias situações que justificam ter outras linhas de execução num programa. Explico abaixo três dessas situações:
Tempo de Resposta. Imagine que você está construindo uma aplicação para a internet que poderá receber milhares de acessos simultâneos. Há um ponto nesse seu programa que recebe todas as solicitações enviadas pela internet, processa a solicitação e devolve um documento HTML com a resposta. Se você tiver apenas uma linha de execução, apenas uma solicitação da internet será tratada por vez; todas as demais terão que aguardar, não importa o tempo que a solicitação atual demore. Criar novas Threads para tratar solicitações distintas melhorará o tempo de resposta já que cada um terá que aguardar apenas o processamento de sua própria solicitação.

Divisão de Tarefas.Se você tem um problema grande cuja solução computacional pode ser dividida em passos menores e parte desses passos pode ser calculado de forma independente, então criar Threads melhorará a performance de seu algorítmo. Um exemplo seria você ter que mesclar duas massas de dados vindas de lugares diferentes para produzir uma nova massa ordenada. Ordenar os dois conjuntos são passos que poderiam ser executados simultaneamente (em Threads separadas) para agilizar o resultado.

Monitoramento de Eventos.Há situações em que um programa precisa se manter responsivo mesmo quando está aguardando a ocorrência de algum evento. Por exemplo, um programa que tenha que aguardar a criação de certos arquivos mas que nesse meio tempo possa também continuar interagindo com o usuário. No Windows, as funções que monitoram a criação de arquivos travam a linha de execução até que haja a mudança monitorada ou que ocorra um timeout. Se o monitoramento estiver numa Thread separada, o programa poderá continuar interagindo normalmente com o usuário.
Para criar uma nova Thread em Delphi ou C++ Builder, basta criar uma herança da classe TThread - acessando o menu File -> New -> Other -> Thread Object, o próprio IDE prepara uma unit com o esqueleto para a nova classe. Cada instância dessa classe produz uma nova linha de execução para o programa.

Basicamente, é preciso sobrescrever o método Execute - ele é abstrato na classe base TThread - e colocar nele os comandos a serem executados pela nova linha de execução. Em Delphi:
procedure TWMinhaThread.Execute();
begin
b>while not Terminated do
begin
{ Código a ser executado vai aqui...}
end;
end;
É bastante comum que o código colocado no Execute tenha um laço while testando o valor da propriedade Terminated, mas não é uma obrigação. No entanto, essa propriedade registra se alguma outra parte do programa solicitou o encerramento da Thread - por exemplo, se o usuário cancelou uma operação demorada. É, portanto, uma boa prática de programação verificá-la em pontos estratégicos do código da Thread.

Outras propriedades e métodos interessantes da classe TThread:
Propriedade Priority. Determina a prioridade de execução de uma Thread em relação às outras threads do mesmo processo (programa). Quanto maior a prioridade, maior é o tempo de CPU destinado à thread. Alterações nesta propriedade devem ser feitas com critério pois aumentar a prioridade de uma thread que tenha processamento pesado pode prejudicar a responsividade das demais, enquanto diminuir a prioridade de uma Thread atrasará sua própria execução. Uma boa regra é manter a prioridade com seu valor padrão e só aumentá-lo em threads que sejam de fato mais críticas, como por exemplo uma que esteja aguardando um evento externo importante para o processo ou que seja classificada como "missão crítica". Diminua a prioridade em threads que realizem tarefas cujos resultados não estejam sendo aguardados pelo usuário, como por exemplo, aquela que arquiva emails antigos no Outlook.

Propriedade FreeOnTerminate. Determina se a VCL destruirá de forma automática a instância da classe de Thread. Se estiver ajustada com true, a VCL chamará automaticamente o destrutor da classe para a instância assim que o método Execute terminar. Neste caso, evite manter uma variável que contenha a instância da classe a menos que você possa garantir com certeza absoluta que a instância é válida nos momentos em que você precisar acessá-la. Se estiver ajustada com false, você será responsável por chamar o destrutor no momento que for mais apropriado para a aplicação.

Método Resume. Há um parâmetro no construtor da classe TThread que indica se a thread em questão será criada em estado suspenso ou se iniciará a execução imediatamente. Se solicitar que a thread esteja com a execução suspensa, você pode comandar o início da execução chamando o método Resume. Este método dará início à nova thread e executará o código que você escreveu no Execute.

Método Terminate. Use esse método para notificar a thread que ela deve encerrar sua execução. Isso é feito ajustando o valor da propriedade Terminated para true; daí a importância de testar o Terminated em pontos estratégicos da execução da thread.
No próximo post, mostro um exemplo concreto com o básico sobre o uso de Threads em Delphi.

13 de agosto de 2009

Usando arquivos INF para manutenções no Registry

Outro dia eu vi no site da Info Online uma dica interessante ensinando a incluir o desfragmentador de disco no menu de contexto do Windows Explorer. Com isso, basta clicar com o botão direito do mouse numa unidade de disco e a opção para desfragmentá-lo aparecerá no menu suspenso.

A solução mostrada naquele post é bastante simples: um arquivo com extensão INF especifica a criação de entradas no Registry para configurar o menu suspenso. Foi o uso dos arquivos INF que me chamou a atenção. Eles constituem uma forma prática de dar manutenção no Registry, criando ou removendo chaves e valores específicos. Para executá-lo, basta clicar com o botão direito do mouse no arquivo e escolher Instalar no menu suspenso. Também é possível usar a API do Windows para automatizar essa execução.

Qual a diferença entre usar um arquivo INF ou um REG, já que ambos podem dar manutenção no Registry ? Ambos são arquivos texto e podem ser facilmente executados a partir do Windows Explorer mas os INF são mais flexíveis. Eles permitem criar sequências complexas para instalação de softwares. Permitem também remover valores específicos dentro do Registry, enquanto os REG só admitem a eliminação de chaves inteiras. Outras vantagens: os INF têm sintaxe para interagir com o menu de contexto do Explorer, aceitam a inclusão de comandos para desfazer o que quer que tenha sido feito com o INF e é mais fácil de ler pois sua sintaxe é mais simples.

Veja um exemplo simples, a própria instalação do desfragmentador de disco no menu suspenso do Explorer:
[version]
signature="$CHICAGO$"

[DefaultInstall]
AddReg=AddMe

[AddMe]
HKCR,"Drive\Shell\Desfragmentar\command",,,"DEFRAG.EXE %1"

O conteúdo do arquivo INF é organizado em seções, como nos arquivos INI. Cada seção tem uma função específica e contém uma ou mais instruções.

A seção [version] é obrigatória e deve ser a primeira do arquivo para sinalizar que se trata de um arquivo INF. Esta seção deve conter uma linha obrigatória com a chave signature assumindo o valor "$CHICAGO$" se o arquivo tiver comandos compatívels com versões iguais ou superiores ao Windows 95 ou "$Windows NT$" para o caso dos comandos serem específicos de Windows 32 bits, como o NT e as versões mais recentes.

A seção [DefaultInstall] contém as instruções a serem executadas quando o usuário seleciona a opção Install no menu suspenso do Windows Explorer. No exemplo, a chave AddReg instrui a instalação a acrescentar novas chaves e valores no Registry ou modificá-los, caso tais chaves já existam. Para remover uma chave inteira do Registry ou apenas um valor específico, crie na seção [DefaultInstall] uma chave com valor DelReg. Nos dois casos, o valor que vem depois do nome indica uma outra seção (cujo nome é você quem escolhe) onde estão efetivamente uma ou mais entradas a serem acrescidas (ou deletadas) do Registry. No exemplo, o nome escolhido foi AddMe e na seção com esse nome há apenas uma nova entrada para o Registry, que configura a opção de Desfragmentar um drive (unidade de disco). Para especificar uma entrada no Registry, use a sintaxe:
RAIZ,[chave],[valor],[flags],[dados]

onde:
RAIZ é um valor obrigatório que representa uma das chaves de nível mais alto do Registry, podendo assumir uma das seguintes opções: HKCR (para a raiz ser HKEY_CLASSES_ROOT), HKCU (para HKEY_CURRENT_USER), HKLM (para HKEY_LOCAL_MACHINE) ou HKU (para HKEY_USERS). É recomendável ler a documentação dessas chaves e também dos softwares com os quais pretende interagir para saber o quê e onde gravar valores para não desestabilizar o funcionamento do Windows.

chave é um valor opcional contendo o caminho completo da chave a ser criada/alterada dentro da Raiz informada. No exemplo, foi adicionada a chave Drive\Shell\Desfragmentar\command. A barra (\) indica uma subchave, da mesma forma que uma barra no caminho no Windows Explorer indica uma subpasta.

Valor é o nome de um valor a ser criado dentro da chave especificada. O tipo de dado para esse valor é estipulado pelo campo seguinte, flags, cujos valores mais comuns são 0x00000000 (para textos) e 0x00010001 (para números inteiros).

Em Dados você coloca o valor real associado ao nome de Valor estipulado acima. Se esse dado for um texto, escreva-o entre aspas. No caso de se omitir os parâmetros Valor e Flags (como no exemplo), a informação fornecida em Dados será associada como valor padrão (default) da chave.

Este post mostra apenas tarefas simples - apenas criação e remoção de entradas no Registry - mas as possibilidades permitidas pelos arquivos INF vão muito além disso, dispondo de métodos até mesmo para instalações complexas de softwares e drivers. Volto a falar nesse assunto em outro post, mostrando exemplo de como executar tarefas mais complexas.

10 de agosto de 2009

Design Patterns com Delphi : Builder - parte 2

Neste post, vou mostrar um exemplo de como mapear classes em Delphi para implementar o Design Pattern Builder. No post anterior, dei um overview a respeito do funcionamento desse padrão e as situações onde ele se aplica. Citei como exemplo a geração do documento para o SPED Fiscal, cuja criação deve ser feita em etapas bem conhecidas, representadas por tipos de Registros, o que torna tal criação apropriada para uso do padrão Builder.

A criação dos registros neste caso consiste, de uma forma simplificada, em incluir um cabeçalho, uma série de registros comuns, alguns registros específicos para o ramo de atividade da empresa emissora do documento de SPED e, por fim, um registro de encerramento. Veja uma parte do diagrama de classes para essa situação:

Diagrama UML para Builder - SPED
Neste diagrama, a classe TWAbstractBuilderSPED é o Builder abstrato, isto é, ele apenas diz quais são os métodos que um Builder para registros do SEPD deve ter. Em Delphi, este tipo de classe é codificada usando a palavra chave abstract depois do nome de cada função tida como abstrata.
type TWAbstractBuilderSPED=class
public
function BuildHeader: TWRegistro;virtual;abstract;
function BuildRegistrosComuns: TWRegistro;virtual;abstract;
function BuildRegistrosExtras: TWRegistro;virtual;abstract;
function BuildTail: TWRegistro;virtual;abstract;
end;
Por ser abstrata, esta classe não deve ser instanciada. É preciso criar as heranças concretas que implementarão as funções introduzidas acima. Essas classes que herdam do AbstractBuilder são denominadas Concrete Builder. No diagrama eu só representei 2: uma para empresas que não tenham registros especiais (TWBuilderSPEDComum) e outra para indústrias Farmacêuticas (TWBuilderSPEDMedicam) mas deve haver uma dessas classes para cada ramo que tiver registros do SPED específicos. Exemplificando com um trecho do TWBuilderSPEDMedicam:
TWBuilderSPEDMedicam=class (TWAbstractBuilderSPED)
public
function BuildHeader: TWRegistro;override;
function BuildRegistrosExtras: TWRegistro;override;
end;

implementation

function TWAbstractBuilderSPED.BuildHeader: TWRegistro;
begin
Result := TWRecHeader.Create;
end;

function TWAbstractBuilderSPED.BuildRegistrosExtras: TWRegistro;
begin
Result := TWRecMedicamentos.Create;
end;
Cada classe concreta dessas é responsável por criar as "etapas" corretas para construir o objeto ao qual dizem respeito. Como poderão existir diversas dessas classes, um sistema normalmente usa o Design Pattern Factory Method ou Abstract Factory para decidir qual delas instanciar.

Depois de ter o Builder correto criado, é responsabilidade do TWDiretorSPED montar o objeto final - o Product, segundo a nomenclatura do Design Pattern - na ordem correta.
function TWDiretorSPED.Construct : TWDoctoSPED;
var builder : TWAbstractBuilderSPED;
Registro : TWRegistro;
begin
builder := GetBuilderSPEDFromFactory (_RamoAtividade);

Result := TWDoctoSPED.Create;

Registro := builder.BuildHeader;
Result.Add (Registro);
Registro := builder.BuildRegistrosComuns;
Result.Add (Registro);
Registro := builder.BuildRegistrosExtras;
Result.Add (Registro);
Registro := builder.BuildTail;
Result.Add (Registro);
end;
A função Construct é o resumo do padrão Builder. Nela podemos ver que uma instância concreta do Builder é criada por uma Factory e lançada numa variável do tipo do Abstract Builder de modo que podemos criar as partes separadamente através de polimorfismo. Montar uma Factory para obtenção da instância correta do Builder não é obrigatório mas é bastante comum. Por fim, o Builder obtido é usado para instanciar cada uma das etapas (os registros) que compõem o produto final. O produto final aqui é uma instância da classe TWDoctoSPED, que é montada registro a registro e retornada pela função Construct. O sistema, então, pode usar o documento SPED do jeito que for necessário.

7 de agosto de 2009

Design Patterns com Delphi : Builder - parte 1

O padrão de projeto Builder foi estabelecido para situações em que um objeto tem que ser construído em etapas ou passos de forma que esses passos podem ser abstraídos. Isto é, não importa os tipos reais (ou concretos) dos passos - sabe-se apenas que o objeto construído é composto por esses passos. Esse Design Pattern criacional difere do Abstract Factory no fato de que o Builder se propõe a criar objetos complexos passo a passo a partir de classes que não precisam estar relacionadas entre si, enquanto no Abstract Factory sempre é instanciada uma família correlata de classes. Na prática, é muito comum que uma situação seja mapeada como Abstract Factory no início de um projeto mas evolua para o Builder conforme a complexidade dos objetos construídos vai aumentando.

Exemplos de situações práticas nas quais utilizar este padrão incluem a geração do SPED Fiscal (Sistema Público de Escrituração Digital) e a criação do XML para a Nota Fiscal Eletrônica. No caso do SPED Fiscal, por exemplo, há uma série de documentos que devem ser criados para construir o pacote exigido pela Receita Federal. Alguns desses documentos variam de acordo com o ramo de atividade e com o Estado do Emissor, entre outros fatores. Os passos seriam a criação de cada um dos documentos isolados e o objeto final construido representaria o pacote a ser entregue à Receita. O mesmo vale para o XML da Nota Fiscal Eletrônica.

Conforme descrito no livro Design Patterns (que originou o conceito atual de Padrões aplicados à engenharia de Software), o padrão Builder é composto de 4 tipos de classes. O diagrama UML abaixo, que está publicado no Wikipedia, mostra estas classes e suas relações:

Diagrama UML para Builder
A classe Builder representada no diagrama acima é apenas uma interface abstrata, isto é, ela introduz uma forma padrão para se criar cada um dos passos necessários para a construção do objeto final. Essa interface tem necessariamente que ser implementada por uma ou mais classes do tipo ConcreteBuilder, que são as responsáveis por criar instâncias de passos isolados.

No caso do SPED, os passos da classe Builder devem incluir um método para construir os registros padronizados - aqueles que têm que existir, independendo de quaisquer características da empresa Emissora - e outro método para criar os registros extras, que dependem do ramo de atividade da empresa. Devemos, então, implementar classes ConcreteBuilder diferentes para representar cada ramo de atividade possível, com cada uma criando apenas os registros extras necessários para o próprio Ramo envolvido. Por exemplo, uma ConcreteBuilder para os registros exclusivos para a indústria de medicamentos, outra para a de armamentos, etc.

A classe Director do diagrama é responsável por juntar as partes, ou seja, é ela quem constrói o objeto final, chamando na ordem correta as funções que controem cada passo separado. O Director recebe como parâmetro a instância de um ConcreteBuilder e usa esta instância para gerenciar a criação dos passos. Pela forma como opera, é comum que o padrão Builder esteja associado a outros padrões criacionais. Por exemplo, usar uma Abstract Factory para determinar qual a classe ConcreteBuilder que deve ser usada pelo Director ou Singleton para garantir que haja no máximo apenas uma instância de cada ConcreteBuilder.

A última classe é o Product, que como o nome diz, é o produto final construído pelo Director. No caso do SPED, seria uma classe contendo a lista de registros digitais com as informações que devem ser enviadas ao fisco.

Para não me estender muito, coloco em outro post um exemplo com esses conceitos mapeados como classes em Delphi.

4 de agosto de 2009

Atualizando Pivot Tables do Excel com scripts

Na semana passada me colocaram uma questão prática envolvendo Excel. Foram incluídas numa planilha diversas Pivot Tables conectadas a tabelas no banco de dados e essas Pivot Tables deveriam ser atualizadas automaticamente num determinado dia/hora, uma vez por semana.

As Pivot Tables são um recurso encontrado no Excel (e em outros programas de manipulação de dados, como o Lotus 1-2-3 e o Calc do Open Office) que permitem a criação de visões multidimensionais dos dados através de operações do tipo arrasta-e-solta nas colunas de valores. O resultado é uma poderosa ferramenta gerencial de análise que permite fazer o cruzamento dos dados das diversas colunas, sumarizando-os. O conceito é mais ou menos como os cubos de informações dos data-minings. Há uma matéria sobre o uso de Pivot Tables neste link.

Como escrevi uma série de posts mostrando como automatizar operações no Excel através de scripts VB, me pareceu que a solução estava bem próxima. Os scripts podem ser considerados como programas, de modo que é possível agendá-los sem problemas no Windows, resolvendo a questão do dia/hora especifico para atualização. Restava, então, desvendar a atualização dos dados em si.

Uma das características das Pivot Tables do Excel é que elas podem ser configuradas para reter as informações usadas para conectar com a fonte de dados, isto é, o nome do servidor e o usuário/senha para acessar o banco de dados. Você pode saber se isso já está configurado abrindo a planilha, selecionando a Pivot Table e pedindo para dar o Refresh nos dados manualmente. Se nenhuma informação sobre a conexão for solicitada pelo Excel, a configuração já está pronta para ser usada pelo script. Considerando que a planilha em questão tenha sido criada com as Pivot Tables associadas a uma fonte de dados externa (uma query num banco de dados, por exemplo), o problema fica restrito a criar um script VB que carregue a planilha, localize as Pivot Tables existentes, atualize os dados de cada uma delas e então salve a planilha com os novos valores.

Estudando o Modelo de Objetos do Excel, encontrei o método PivotTables do WorkSheet, que dá acesso à lista de todas as Pivot Tables existentes numa folha de trabalho. Também encontrei o método que faz a atualização dos dados (RefreshTable). A sintaxe para atualizar a primeira Pivot Table de uma folha de trabalho é como segue.
folha.PivotTables(1).RefreshTable

Como pode ser que não haja Pivot Tables numa determinada folha, é conveniente verificar a existência antes. PivotTables retorna uma lista e a propriedade folha.PivotTables.Count indica quantos elementos há nessa lista. A solução será mais efetiva se usar esse Count para dar Refresh em todas as PivotTables que encontrar.
For I = 1 To objWorkBook.Sheets.Count
Set folha = objWorkBook.Sheets.Item(I)
For j = 1 to folha.PivotTables.Count
folha.PivotTables(j).RefreshTable
Next
Next

objWorkBook.Save
objWorkBook.Close

No exemplo acima, todas as folhas da planilha aberta são percorridas e, para cada folha, todas as Pivot Tables são atualizadas. A instrução Save salva as alterações feitas de volta na mesma planilha que foi aberta. Se quiser salvar numa planilha diferente, use SaveAs - isso permitirá também salvar a planilha num formato diferente, preservando aquela que foi originalmente aberta.

Para ver como abrir uma planilha já existente e como obter a lista de folhas de trabalho dela, veja o post Automatizando a leitura de planilhas Excel.

Para fazer download do script de exemplo, clique aqui.

3 de agosto de 2009

Design Patterns com Delphi : Singleton

Há situações no projeto de um sistema em que claramente deve existir apenas uma instância de uma determinada classe. Geralmente, são classes criadas para gerenciar o acesso a um recurso controlado do sistema, isto é, aqueles recursos que devem ser acessados com cautela e sob certas regras para evitar o caos no sistema. São exemplos dessa situação o gerenciamento de recursos como Janelas, Impressão de relatórios e o gerenciamento de conexões com o banco de dados.

Para garantir a existência de apenas uma única instância de uma classe foi criado o Design Pattern criacional Singleton. Veja o esquema UML para esse padrão:

Esquema para Singleton
O diagrama mostra que o padrão evita a criação de múltiplas instâncias fazendo com que o constructor seja declarado com a visibilidade private (privada), de forma que apenas a própria classe tem acesso a ele.

Como então é criada uma instância para a classe ? A resposta também aparece no diagrama acima: uma variável privada da classe e um método de classe público. A diferença entre esse método e o constructor é que o método só gera uma nova instância da classe se isso ainda não foi feito antes. Esse efeito é conseguido testando-se a variável privada. Se a variável ainda está vazia (com Nil), significa que não há uma instância criada. Neste caso, cria-se a instância e armazena-se na variável de classe, que é então devolvida pela função. Se uma instância já foi criada antes, a variável de classe não estará mais vazia, de modo que a função simplesmente retorna o valor armazenado na variável.

Mapeando estes conceitos para a linguagem Delphi, temos a seguinte definição de classe e da função GetInstance:
type TWGerImpressao=class
private
constructor Create;
public
class function GetInstance : TWGerImpressao;

procedure ImprimeJob;
end;

implementation

var
_Gerenciador : TWGerImpressao;

class function TWGerImpressao.GetInstance : TWGerImpressao;
begin
if (_Gerenciador = Nil) then
_Gerenciador := TWGerImpressao.Create;
Result := _Gerenciador;
end;

Diferentemente do Delphi para .Net, o Delphi para Win32 não possui o conceito de variável de classe - apenas métodos podem ser de classe. Por isso, criei a variável _Gerenciador como parte da área implementation da unit, de forma que ela só pode ser acessada por métodos internos da unit. Essa solução funciona tanto para Win32 quanto para .Net mas falta iniciar a variável com Nil para o código funcionar. Como o _Gerenciador pertence à unit, o melhor lugar para atribuir-lhe um valor inicial é na área initialization. Da mesma maneira, o código de finalização da unit só é chamado quando ela não é mais necessária e, por isso, devo chamar o destrutor no finalization para liberar a memória e outros recursos:
initialization
_Gerenciador := Nil;

finalization
if (_Gerenciador <> Nil) then
_Gerenciador.Free;
end.

Um último lembrete. Se for utilizar esse código para implementar o Singleton em ambiente multi-thread, não esqueça de envolver a criação da instância numa seção crítica. Isso evitará que duas threads acessando simultaneamente decidam realizar a criação por ter encontrado o _Gerenciador com Nil.

2 de agosto de 2009

Windows 7 e a API para Touch Screen

A Microsoft anunciou recentemente que o Windows 7 está finalizado e que passará a ser vendido a partir do dia 22 de Outubro para o consumidor final. De início, ele está sendo distribuído apenas para os fabricantes de PC de modo que eles possam preparar computadores para serem vendidos já com o novo sistema operacional, a chamada versão RTM.

Com isso, uma nova frente de batalha é aberta, onde os empresas que desenvolvem software correm para adaptar seus produtos ao Windows 7 e tirar proveito dos novos recursos disponibilizados. Um dos recursos mais interessantes dessa versão é a tecnologia Multi-Touch, que permitirá acessar as funções de um computador através de toques na tela. É chamada de multi-touch porque será possível interagir tocando mais de um ponto da tela ao mesmo tempo. Um exemplo desse tipo de interação é tocar dois pontos diferentes de uma foto e arrastar os dedos para produzir o efeito de zoom.

A Embarcadero, atual dona dos ambientes de desenvolvimento Delphi e C++ Builder, anunciou na semana passada que incluirá suporte completo à tecnologia multi-touch em suas ferramentas de desenvolvimento. Isso significa que será mais fácil construir aplicações que respondam a interações touch-screen. Traduzo abaixo o anúncio conforme publicado pelo site da revista SDTimes (Software Development Times):

O fabricante de ferramentas para desenvolvimento de software e para bancos de dados Embarcadero dará suporte completo a toda a API do Windows 7 - incluindo a tecnologia multi-touch - nas futuras versões de seus produtos para desenvolvimento rápido de aplicações (RAD).

Tanto o C++ Builder (um ambiente RAD C++ para Windows) quanto o Delphi (RAD Visual para desenvolvimento de aplicações para Windows e bancos de dados), ambos produtos da Embarcadero, terão interfaces para trabalhar com as APIs, diz David Intersimone, que é vice presidente da empresa e evangelista-chefe.

Os dois produtos terão um componente de gerenciamento de gestos para o Windows 7 que abstrairá os recursos multi-touch da API do Windows, tornando mais fácil para os desenvolvedores usá-los em suas aplicações, diz ele.

"Os desenvolvedores arrastarão o componente para qualquer Form de sua aplicação e ele poderá receber interações através de toques na tela. Os programadores simplesmente ajustarão propriedades e responderão aos eventos de gestos que ocorrerem.", diz Intersimone. "Eles também poderão customizar gestos e sobrepor os que vêm predefinidos", acrescenta ele.

"O gerenciador de gestos também suportará toque único (single touch) para equipamentos mais antigos, como telas de quiosques, e poderá usar o mouse convencional ao invés dos dedos. Como os antigos sistemas não possuem a tecnologia touch-screen implementada pelo Windows 7, o sistema operacional os tratará como interações de mouse", exlica ele.

A Microsoft lançou o Windows 7 para fabricantes em 22 de julho. A Embarcadero terá que aguardar até que a Microsoft finalize o SDK (Software Development Kit) do Windows 7 antes que possa finalizar seus próprios produtos, diz Intersimone. "O SDK normalmente é distribuído algumas semanas depois do lançamento da versão RTM do Windows", acrescenta. Ele não forneceu uma data para a disponibilidade dos produtos da Embarcadero.

Para ver a notícia original em inglês, clique aqui.