5 de março de 2010

Usando "streams" para navegação no TWebBrowser

Postei em Fevereiro a respeito do componente TWebBrowser, que é um encapsulamento do navegador Internet Explorer da Microsoft. Naquele post, eu apresentei a função básica do componente (que permite navegar para uma determinada página HTML), além dos eventos mais comuns.

Aquela solução mostrada lá, no entanto, exige que se tenha uma página HTML externa ao programa, seja esta página existente em disco ou disponível através da Internet. Isso pode ser um inconveniente, já que temos que garantir que o arquivo HTML esteja distribuído junto com a aplicação.

Então, fiz uma pesquisa para tentar descobrir se havia jeito de se inserir o texto HTML diretamente no componente, sem ter que passar pelo arquivo externo. Nessa pesquisa eu encontrei no site Delphi Dabbler um artigo descrevendo como fazer isso através de streams. O artigo na verdade é um pouco mais complexo do que eu precisava, de modo que extraí dele apenas a parte relativa a como passar um stream para o WebBrowser e como obter de volta o stream em uso. Com isso, posso ler o stream com o HTML para exibição a partir de um banco de dados ou usar o componente que permite embutir um arquivo diretamente na aplicação.

O Web Browser implementa uma interface COM chamada IPersistStreamInit cuja principal função é interagir com streams, disponibilizando métodos para carregar e salvar conteúdos. Teremos que usar o método QueryInterface existente em todo COM para obter acesso às funções dessa interface. A interface, no entanto, só está acessível se por acaso você já tiver navegado para alguma página. Para resolver isso, podemos antes solicitar ao Browser que exiba uma página especial em branco. A URL dessa página é 'about:blank' :
var PersistStreamInit: IPersistStreamInit;
begin
FWebBrowserInt.Navigate('about:blank');

if FWebBrowserInt.Document.QueryInterface(IPersistStreamInit, PersistStreamInit) = S_OK then
{ ... }
A declaração para IPersistStreamInit está na unit ActiveX, sendo portanto obrigatória a inclusão dela na cláusula uses do nosso programa. A propriedade Document (usada para obter a instância da interface) representa o documento HTML ativo e dá acesso a toda a sua estrutura DOM.

Agora que temos como acessar as funções da IPersistStreamInit, temos mais um problema. A carga de um stream exige que esse stream implemente a interface IStream (também disponível na unit ActiveX) mas a classe da VCL TStream e suas descendentes não têm esse recurso. Para contornar essa limitação, há uma classe chamada TStreamAdapter que pega um TStream e implementa para ele o comportamento exigido pela interface IStream. O código para carga do HTML, então, fica assim:
var lStream : TStream;
StreamAdapter: IStream;
begin
{ ... }
lStream := GetHTMLStreamFromDB;

StreamAdapter:= TStreamAdapter.Create(lStream);

PersistStreamInit.Load(StreamAdapter);
{ ... }
A função GetHTMLStreamFromDB é minha e busca um stream no banco de dados com o HTML que deve ser carregado.

Salvar o documento atual de volta para um stream é similar ao que já foi exposto, bastando substituir o Load pelo Save. Como já disse num outro post, o WebBrowser também tem recursos para edição do documento que está em exibição; é nesse contexto que salvar o documento atual faz mais sentido.
lStream := TMemoryStream.Create;
StreamAdapter := TStreamAdapter.Create(lStream);
PersistStreamInit.Save(StreamAdapter, True);
Quando se está editanto um HTML com o WebBrowser, ele mantém internamente um status indicando se o contéudo foi alterado. O parâmetro True passado para a função Save instrui o componente a limpar esse status para refletir a condição de que o conteúdo já foi salvo.

Da forma como está, essa solução ainda apresenta um inconveniente: se o HTML carregado como stream fizer referência a objetos externos (imagens, vídeos, etc.) eles não serão exibidos corretamente, mesmo que distribuídos junto com a aplicação. Volto numa outra oportunidade para falar disso.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.