8 de fevereiro de 2010

Interagindo com HTML em programas Delphi e C++ Builder - parte II

Neste post, vou dar sequência ao anterior e mostrar como interagir com uma página HTML dentro de um programa desktop em Delphi - esquema que vale igualmente para o C++ Builder com a única ressalva de que nesse ambiente o nome do componente a ser usado é TCppWebBrowser ao invés de WebBrowser.

Na ABC71, exibimos uma página HTML para recepcionar os novos usuários de nosso ERP, apresentando-lhe uma espécie de tutorial da ferramenta. Uma caixa de seleção (checkbox) é apresentada no canto inferior esquerdo da página e permite ao usuário optar por remover a recepção, de modo que ela não é mais exibida quando ele se logar no sistema. Este efeito é obtido através da interceptação do evento de navegação na página HTML.

A caixa de seleção da página HTML que eu citei tem um evento de "clique" que força a navegação para uma URL controlada por mim. Através de um código javascript na página, eu adiciono a essa URL o status atual da caixa de seleção, isto é, se ela está marcada ou não. O trecho abaixo mostra o ponto do javascript em que a URL é redirecionada:
function ExibirClick () {
Exibir = ! Exibir;
if ( Exibir ) {
document.getElementById("img_exibir").src = "Checked.jpg";
location.href = "http://event-omega/?exibir=true";
}
else {
document.getElementById("img_exibir").src = "Unchecked.jpg";
location.href = "http://event-omega/?exibir=false";
}
}
Repare no endereço HTTP incluído na URL: http://event-omega/. Esse é um endereço que não existe de verdade. Ele está aí apenas para marcar posição já que minha aplicação Delphi pesquisará por esse nome para detectar que se trata da navegação que eu quero interceptar. Com isso, se tentar usar essa página sem o programa que intercepta a navegação, uma página de erro será exibida avisando que o endereço acessado não existe.

Repare ainda que é acrescentada à URL um texto iniciado por um sinal de interrogação (?). Este texto emula o padrão usado pela tag FORM de um HTML. Quando se configura a ação de uma tag Form para usar o método GET, o "clique" para submeter o conteúdo prepara a URL acrescentando-lhe a interrogação e uma sequência de pares de valores (que correspondem a parâmetros da aplicação e o respectivo valor de cada um) separados por um símbolo & (E Comercial). O navegador, então, usa a URL resultante para realizar a próxima navegação. No exemplo, apenas um parâmetro é acrescentado manualmente - exibir - que está assumindo o valor "false" ou "true", dependendo se a caixa está marcada ou não no HTML.

No programa Delphi que utilizará este HTML, devemos começar exibindo-o, através da função Navigate do TWebBrowser:
var lURL: String;
begin
lURL := 'file://' + ExtractFilePath(Application.ExeName) + 'bemvindo/BV_menu.html/';
FBrowser.Navigate(lURL);
end;
Agora, podemos responder ao evento de navegação OnBeforeNavigate2. Esse evento deve detectar que se trata da nossa URL, extrair os valores passados como parâmetros e utilizá-los conforme a necessidade.
if AnsiStartsStr ('http://event-omega/', URL) then
begin
lHtmlVars := TStringList.Create;
try
{ Captura apenas os parâmetros, isto é, o que vem após o sinal de interrogação }
lPos := AnsiPos ('?', URL);
lUrl := AnsiRightStr (URL, Length (URL) - lPos);
lHtmlVars.Add (lUrl);
finally
trataEvento (lHtmlVars);
FreeAndNil (lHtmlVars);
end;

{ cancela a navegação pois o tratamento já foi feito }
Cancel := true;
A presença de um StringList neste código é um atalho para facilitar o tratamento de pares de valores separados pelo sinal de igual (=). Valores com essas características adicionados a um StringList são acessados mais facilmente. Por exemplo, dentro da minha função trataEvento eu posso recuperar diretamente o valor do parâmetro 'exibir' enviado pelo HTML:
var lStrExibir: String;
begin
lStrExibir := AHtmlVars.Values ['exibir'];
Com o valor recuperado, posso armazenar a opção do usuário num banco de dados ou no Registry do Windows e não exibir mais a página quando ele se loga.

O programa de exemplo montado com Delphi 2005 pode ser baixado aqui.

4 de fevereiro de 2010

Interagindo com HTML em programas Delphi e C++ Builder - parte I

A linguagem HTML é amplamente utilizada na criação de sites devido as suas características, que permitem criar facilmente textos formatados e com diagramação complexa que incluam também imagens, animações e outros recursos.

No entanto, ela tem sérias restrições quando se tenta usá-la para criar aplicações ricas. Por "aplicação rica" eu quero dizer aquelas que são capazes de prover interações com o usuário de uma forma mais completa e responsiva. Elas contem recursos como organização em guias, apresentação de informações em árvores ou listas que podem responder a cliques do usuário, etc. As aplicações Windows tradicionais - (ditas Desktop) como o Excel ou um ERP como o Omega da ABC71 são exemplos de tais aplicações.

Por outro lado, em aplicações desktop é difícil de se tratar a exibição de textos formatados em que ocorram variações de tipo de fonte ou que apresentem hiperlinks e exibam imagens e animações - exatamente os recursos encontrados em um HTML.

Então, ter a capacidade de exibir páginas HTML numa aplicação desktop pode ser de grande utilidade. O Delphi e o C++ Builder incluem em suas paletas um componente chamado WebBrowser (ou CppWebBrowser) que permite exibir facilmente páginas HTML em aplicações desktop. Na verdade, este componente é um encapsulamento do navegador Internet Explorer de modo que ele tem os mesmos recursos que o navegador, podendo exibir páginas de forma idêntica. A grande diferença é que, como o componente está embutido em seu programa, é possível interceptar os eventos gerados pelo navegador e executar ações em cima desse eventos. Os eventos incluem seguir um link (permitindo redirecionar para uma outra página, por exemplo), o clique de botões (permitindo tratar o envio das informações contidas num formulário HTML), acompanhamento de downloads, entre outros.

O principal uso desse componente é exibir páginas HTML, o que pode ser obtido através da função Navigate:
FBrowser.Navigate('http://balaiotecnologico.blogspot.com/');

O parâmetro passado para esta função é a URL onde está o documento a ser exibido. A URL pode também apontar um endereço local, permitindo distribuir os documentos HTML necessários juntos com o seu programa:
var lURL: String;
begin
{ A URL deve ter o caminho completo, por isso extraio o caminho onde o executável está para prepará-la adequadamente, concatenando a esse caminho o protocolo FILE:// e acrescentando o nome do HTML }
lURL := 'file://' + ExtractFilePath(Application.ExeName) + 'bemvindo/bemvindo.html/';
FBrowser.Navigate(lURL);
end;

O evento OnDocumentComplete notifica o programa quando a carga de uma página terminou, o que pode ser usado para habilitar partes da interface gráfica no programa Delphi - por exemplo, habilitar um botão ou uma caixa de edição na tela. Caso a página indicada na URL seja constituida de Frames, este evento é executado uma vez para cada Frame, sempre que a carga de cada um é completada. Tenha em mente ainda que o OnDocumentComplete é disparado mesmo que a carga da página solicitada tenha dado erro. Neste caso, é possível detectar que uma situação de erro aconteceu respondendo ao evento OnNavigateError.

Outro evento importante é o OnBeforeNavigate2. Ele ocorre sempre que o usuário segue algum link na página ou pressiona um botão para submeter os dados de um formulário. O evento também é acionado quando se faz uma chamada à função Navigate dentro de seu programa. Todas as informações relativas à navegação são repassadas para este evento - a nova URL, os dados do formulário (se houver), o nome do frame aonde a página deve ser apresentada, etc. - de modo que é possível consistir tais informações e modificá-las apropriadamente se for necessário. Por exemplo, se o usuário tentar seguir um link ao qual ele não deve ter acesso, é possível redirecionar para uma página de erro ou até mesmo cancelar a navegação.

Esse componente também tem recursos para se transformar num editor de HTML no estilo WYSWYG, permitindo dar ao usuário final de seu programa um meio visual para modificar o conteúdo de páginas HTML mesmo sem conhecer essa linguagem. Falo desse recurso em outro post.

No próximo post eu mostro como fazer com que as interações do usuário com um HTML exibido no TWebBrowser sejam tratadas por um programa Delphi.

29 de janeiro de 2010

Amazon libera SDK para o Kindle

Na mesma semana que a Apple anunciou o lançamento de seu tão aguardado tablet PC - o iPad, a Amazon libera ferramentas para que terceiros possam criar aplicações e conteúdos para o dispositivo de leitura Kindle.

Apesar de ter saido na midia que alguns analistas acreditam que o iPad não substituirá o Kindle, é difícil não ver essa liberação como um movimento da Amazon para trazer para o seu lado produtores de conteúdo. Apesar do Kindle ser um aparelho exclusivamente para leitura (com tecnologia bem mais confortável para esse fim), o iPad é um computador e, como tal, pode ser usado para leitura e para muitas outras coisas, justificando uma preocupação da Amazon.

De acordo com a notícia publicada na InfoWorld, o SDK abre a possibilidade de que sejam desenvolvidas aplicações que exibam conteúdo dinamicamente:

A Amazon anunciou na última quinta-feira (28/01/2010) um kit de desenvolvimento de software para o Kindle, que habilita desenvolvedores de software a construir e carregar "conteúdo interativo" no aparelho.

O kit oferece acesso a interfaces de programação, ferramentas e documentação para construção de conteúdo para o Kindle. O kit também trás programas de exemplo e um simulador para construção e teste de conteúdos, simulando o comportamento do Kindle em computadores com sistema Mac, Windows ou Linux.

As possibilidades de conteúdo incluem livros de viagem que podem sugerir atividades baseado nas condições do tempo atuais ou de eventos que estejam programados no local, livros de culinária que recomendem um cardápio baseado no número de convidados de uma festa e histórico de alergia dos convidados, jogos com palavras, quebra-cabeças, etc. Os desenvolvedores poderão construir seus conteúdos e torná-los disponíveis através da loja do Kindle, a ser aberta ainda essa ano.

"O kit de desenvolvimento para o Kindle abre muitas possibilidades -- nossa expectativa é sermos surpreendidos pela inventividade dos desenvolvedores", declarou Ian Freed, vice presidente do Amazon Kindle.

Os desenvolvedores poderão disponibilizar seus conteúdos no sistema sem fio 3G do Kindle, através da rede Amazon Whispernet e exibí-los no papel eletrônico de alta resolução do aparelho. Esse papel se parece com papel de verdade e permite leitura da mesma maneira, diz a empresa. O Kindle contam também com um bateria que dura sete dias com a rede sem fio ativada.

Os participantes de um programa beta limitado no mês que vem poderão fazer o download do SDK, terão acesso ao suporte a desenvolvedores, poderão testar seus conteúdos no Kindle e submeter o conteúdo finalizado. Uma lista de espera para o programa também será oferecida.

Kindle e Kindle DX são leitores portáteis que permitem o download de livros, revistas, blogs e documentos pessoais através de rede sem fio e exibem esse conteúdo num visor com uma tinta eletrônica.

O texto original em inglês pode ser encontrado neste link.

26 de janeiro de 2010

Um Factory Method mais flexível em Delphi

No ano passado, eu mostrei aqui no blog o funcionamento do Design Pattern criacional chamado Factory Method. A implementação apresentada naquela ocasião seguia a solução tradicional para este pattern, isto é, exigia a criação de uma função - o método Factory - que, baseada nos valores de um ou mais parâmetros, decidia qual classe deveria ser instanciada.

No entanto, essa abordagem tem uma característica que pode se tornar um inconveniente em certas situações: o Factory Method obrigatoriamente tem que conhecer de antemão todas as classes que forem passíveis de serem criadas por ele. O "conhecer" aqui significa que o constructor de cada classe é chamado diretamente dentro da função Factory.

Neste momento, por exemplo, estou montando uma infraestrutura que a ABC71 usará para criar programas de assistência à implantação do nosso ERP. Dependendo do assistente desejado, os tipos e as quantidades de passos incluidos em cada programa vão variar. Por isso, o Factory Method tradicional não satisfaz plenamente já que ele exigiria que eu incluísse todos os tipos de passos possíveis em todos os programas, o que deixaria esses programas muito maiores do que deveriam.

Para resolver esse inconveniente, fiz uma pequena alteração no Factory Method original para que ele usasse uma lista de ponteiros para funções indexada pelo nome da classe que a função é capaz de criar. Quando quero instanciar uma determinada classe, informo ao Factory o nome dela. O Factory, então, recupera o ponteiro para a função associada e chama essa função para criar a instância correta. Para isso funcionar de acordo, as classes disponíveis devem ser adicionadas à lista. No caso do Assistente citado, as próprias classes de cada tipo de Passo se registram na lista, de modo que apenas aqueles passos necessários em cada programa são de fato incluidos.

O primeiro passo para implementar essa solução foi declarar um tipo "ponteiro para função" com os parâmetros necessários para se criar qualquer Passo para o Assistente:
{ AObj são dados que o construtor do passo pode usar para gerar a instância, podendo ser um nó XML ou outro dado relevante. }
TWPassoCreationFnc = function (AWizMan: TWWizardManager; AObj: TObject): TWPasso;

O primeiro parâmetro representa o gerenciador do Assistente onde o Passo deve ser criado, enquanto o segundo parâmetro é genérico, podendo conter qualquer informação relevante para o construtor da classe.

Em seguida, montei uma função para incluir na lista de tipos de passos disponíveis uma associação entre o nome de uma classe e a função que a constrói. Uma outra função permite construir um passo a partir do nome de sua classe. Ambas estão listadas abaixo:
procedure RegisterPassoInFactory (AClassName: String; AConstructor: TWPassoCreationFnc);
var idx : integer;
begin
idx := _FactoryPassos.Add (AClassName);
_FactoryPassos.Objects [idx] := @AConstructor;
end;

function CreatePassoFromFactory (AWizMan: TWWizardManager; AClassName: String; AObj: TObject) : TWPasso;
var lConstructor: TWPassoCreationFnc;
idx : Integer;
begin
idx := _FactoryPassos.IndexOf (AClassName);
if (idx >= 0) then
begin
@lConstructor := _FactoryPassos.Objects[idx];
Result := lConstructor (AWizMan, AObj);
end
else
Raise Exception.Create('Classe desconhecida: ' + AClassName);
end;

Observe que nos pontos onde o ponteiro para função é parte de uma atribuição eu utilizo um arroba (@) para me referir a ele. A sintaxe @lConstructor evita que a função (ou procedimento) seja chamada, indicando que eu estou apenas recuperando o endereço onde ela está na memória. Depois que o ponteiro foi recuperado, uso-o para fazer uma chamada à função que ele representa, passando-lhe os parâmetros conforme declarado no início. Para completar esta etapa, declarei a lista global na área implementation da minha unit e usei os blocos initialization e finalization dela para, respectivamente, criar e destruir tal lista.
implementation

var

_FactoryPassos : TStringList;

{ ... }
initialization
_FactoryPassos := TStringList.Create;

finalization
FreeAndNil (_FactoryPassos);
end.

Com isso, o cenário está preparado. Agora resta fazer com que cada classe que eu queira construir dessa maneira seja apropriadamente registrada na lista. Veja um exemplo para um passo cuja classe foi denominada TWPassoOpcao:
implementation

function CreateWPassoOpcao (AWizMan: TWWizardManager; AObj: TObject): TWPasso;
begin
Result := TWPassoOpcao.Create (AWizMan);
end;

{ ... }
initialization
RegisterPassoInFactory ('TWPassoOpcao', CreateWPassoOpcao);

end.

O mesmo terá que ser feito para as outras classes que denotam passos. Neste exemplo, eu omiti o uso do parâmetro AObj mas, no contexto onde isso é aplicado aqui na ABC71, esse parâmetro é um nó de uma estrutura XML a partir da qual os passos são criados. No entanto, como ele é um TObject, pode representar qualquer outra informação que seja necessária para criar com sucesso a instância da classe.

Esta implementação permite, agora, que cada programa assistente seja compilado apenas com os passos que ele realmente vai precisar, bastando fazer chamadas à função CreatePassoFromFactory para criá-los.