31 de julho de 2009

Convertendo apresentações Power Point em animações Flash

Suponha que você montou uma apresentação no Power Point para divulgar um produto da sua empresa. Nesta apresentação, você usou diversas configurações, animações e formatações, além de incluir gráficos e figuras. A apresentação ficou tão boa que seu chefe te pediu que a publicasse na página da empresa na Internet.

Qual a melhor forma de fazer isso de uma forma fácil e amigável ? Simplesmente salvar o Power Point como um HTML pode não ser interessante para alguém acessando o site. A melhor abordagem talvez fosse convertê-la numa animação Flash que é mais apropriada para distribuir na internet. Outro ponto é que animações flash funcionam nos principais navegadores de internet dos principais Sistemas Operacionais e a animação em si não pode ser alterada pelo usuário do site.

Deparei com essa situação recentemente na ABC71 pois a empresa está preparando material para realizar treinamentos no nosso ERP Omega e bastante coisa já foi montada como Power Point. O material ainda não está totalmente pronto e não foi publicado mas encontrei uma ferramenta ideal para o que queríamos.

A ferramenta chama-se ISpring e faz exatamente isso: converte suas apresentações Power Point numa animação Flash. Ela é distribuída como um plugin do Power Point e a interação é transparente já que é criada uma guia com o mesmo look-and-feel do Office 2007:
Não consegui encontrar no site do fabricante informações sobre se o ISpring trabalha com outras versões do Office.

Além de fazer a conversão, o ISpring permite que você inclua narração junto com a apresentação de forma sincronizada. Também permite que se configure uma taxa de compressão para controlar o tamanho final da animação em bytes e que se configure a escala que o resultado final terá em relação à apresentação original, isto é, o comprimento e altura da animação.

O site do produto garante que ele é capaz de converter mais de 180 tipos de animações do Power Point e todos os tipos de transição entre slides disponíveis. Ele também embute áudio e vídeo que tiverem sido incluídos na apresentação original, além de tratar links e os botões de ações. A aparência da animação resultante impressiona bem já que reproduz fielmente o que foi feito no Power Point.

Pela reprodução da barra de ferramentas acima dá pra ver também que há um botão para gerar rapidamente um vídeo Flash compatível para publicar no YouTube.

Há uma versão gratuita da aplicação para uso pessoal, isto é, para você converter apresentações pessoais - como um álbum de suas férias, por exemplo, ou montar e mandar algum cartão multimídia para alguém. A versão gratuita apenas realiza a conversão para Flash, não disponibilizando os recursos de controle de narração, taxa de compressão ou configuração de escala. Veja aqui a comparação entre as versões.

29 de julho de 2009

Design Patterns com Delphi : Abstract Factory

Imagine uma situação em que, dependendo de certas condições do ambiente operacional em que seu programa irá executar, um conjunto diferente de classes tenha que estar disponível para criação. Um exemplo típico disso é a criação de skins para permitir variações no visual do programa de acordo com opção selecionada pelo usuário. Nesse cenário, o conjunto de classes que representam componentes visuais têm que ser diferente para atender o visual selecionado.

Para resolver esse tipo de problema, foi desenvolvido uma variação do Design Pattern Factory Method, descrito no post anterior. Esse novo Pattern Criacional foi chamado Abstract Factory e a ideia dele é criar uma interface padrão (uma função) para decidir qual o Factory Method a ser utilizado. Na prática, equivale a implementar um Factory Method dentro de outro. Veja o esquema que representa este cenário:

Esquema para Abstract Factory
Neste exemplo, o usuário poderia optar por um de dois visuais disponíveis para a aplicação (Windows ou Modern). Internamente, a aplicação possui classes bases para desenhar os EditText e os ComboBox além de disponibilizar dois conjuntos de heranças dessas classes, um para cada "skin" possível. A função GetSkinFactory da classe TWSkinFactory é a responsável por instanciar a "fábrica" correta de acordo com o tipo de skin desejado:
type
TWTipoSkin = (tsWindows, tsModern);
TWSkinFactory = class
public
class function GetSkinFactory (ATipo: TWTipoSkin): TWSkinFactory;
function CreateEditText: TWEditText;virtual;abstract;
function CreateComboBox: TWComboBox;virtual;abstract;
end;
TWWindowsSkinFactory = class(TWSkinFactory)
public
function CreateEditText: TWEditText;override;
function CreateComboBox: TWComboBox;override;
end;
TWModernSkinFactory = class(TWSkinFactory)
public
function CreateEditText: TWEditText;override;
function CreateComboBox: TWComboBox;override;
end;

implementation

class function TWSkinFactory.GetSkinFactory (ATipo: TWTipoSkin): TWSkinFactory;
begin
Result := Nil;
case ATipo Of
tsWindows: Result := TWWindowsSkinFactory.Create;
tsModern: Result := TWModernSkinFactory.Create;
end;
end;

Veja que a função GetSkinFactory é uma função de classe (não precisa de uma instância para ser usada) enquanto as funções de criação dos componentes visuais são abstratas já que o objetivo dessa classe é apenas introduzir o comportamento esperado para as heranças. Neste exemplo, o comportamento desejado é criar instâncias para os componentes visuais:
function TWWindowsSkinFactory.CreateEditText: TWEditText;
begin
Result := TWEditWindows.Create;
end;
function TWModernSkinFactory.CreateEditText: TWEditText;
begin
Result := TWEditModern.Create;
end;

Cada classe de "fábrica" cria instâncias dos componentes apropriados para o tipo de "skin" selecionado e o polimorfismo faz o resto, deixando o código do programa mais limpo e mais fácil de manter. Supondo que haja um ComboBox para escolha do visual:
var factory : TWSkinFactory;
ed1 : TWEditText;
begin
factory := TWSkinFactory.GetSkinFactory (TWTipoSkin (ComboBox1.ItemIndex));
{...}
ed1 := factory.CreateEditText;
ed1.Desenha;
{...}

A variável factory é instanciada com base na opção do usuário e o resto do programa a utiliza para criar a interface visual. Isto é, o programa não sabe de antemão com qual conjunto de classes visuais está trabalhando. Da mesma forma que no Factory Method, usar o Abstract Factory deixará um único ponto do código para ser alterado caso novas heranças de "skin" sejam necessárias.

As situações onde esses Patterns podem ser usados também são parecidas: é preciso ter conjuntos de classes com relação de herança e o programa tem que ter conhecimento prévio das classes disponíveis pois elas têm que estar presentes na função Factory.

27 de julho de 2009

Design Patterns com Delphi : Factory Method

Conforme afirmei no post anterior (Implementando Design Patterns), os Patterns do tipo Criacionais flexibilizam a criação de instâncias de classes (Objetos). No caso do Factory Method, essa flexiblização ocorre ao usar uma interface padrão para criar instâncias de classes que têm relação de herança entre si. A ideia é construir um método (a interface padrão) que é capaz de decidir qual classe deve ser instanciada, tomando por base um ou mais valores passados por parâmetro.

Neste contexto, Interface não tem a ver com o conceito embutido na palavra chave interface do Delphi. Aqui ele se refere à forma com que a função se relaciona com o mundo exterior, isto é, quais parâmetros deverão ser informados por outras partes do programa para se obter a instância desejada. A interface padrão é, então, uma função bem conhecida que centraliza a criação de instâncias para uma família de classes.

Tome, por exemplo, as classes representadas no diagrama abaixo:

Esquema para Factory Method
Há uma classe TWForma que é base para 3 heranças conhecidas (TWCirculo, TWQuadrado e TWTriangulo). Em Delphi, essa relação seria declarada como segue:
type
TWForma = class
{ ... }
procedure Desenha;virtual;
end;
TWCirculo = class(TWForma)
{ ... }
procedure Desenha;override;
end;
TWQuadrado = class(TWForma)
{ ... }
procedure Desenha;override;
end;
TWTriangulo = class(TWForma)
{ ... }
procedure Desenha;override;
end;

A outra classe existente no esquema é a TWFormaFactory. Através dela implementaremos um único método - o Factory Method - que é declarado como um método de classe, isto é, não é preciso instanciar a classe para utilizá-lo. Esse método é o coração do Design Pattern Factory Method pois é nele que fica a inteligência para instanciar as classes. Veja a declaração:
type
TWTipoForma = (tfCirculo, tfQuadrado, tfTriangulo);
TWFormaFactory = class
class function GetTWForma (ATipo: TWTipoForma): TWForma;
end;

implementation

class function TWFormaFactory.GetTWForma (ATipo: TWTipoForma): TWForma;
begin
Result := Nil;
case ATipo Of
tfCirculo: Result := TWCirculo.Create;
tfQuadrado: Result := TWQuadrado.Create;
tfTriangulo: Result := TWTriangulo.Create;
end;
end;

Aqui, a interface padrão para criar instâncias do TWForma é o método GetTWForma. Repare que ele tem conhecimento prévio sobre as classes que serão criadas. Por causa do polimorfismo, o método pode ser declarado de forma a retornar um ponteiro para a classe base TWForma e o resto do programa não precisa se preocupar com qual tipo foi realmente instanciado já que a classe TWForma introduziu acesso a todo o comportamento que é esperado. Isso configura o cenário ideal para usar o padrão Factory Method: classes com relação de herança e conhecimento prévio das classes a instanciar.

Usar esse padrão tem um clara vantagem se comparada a usar simplesmente o constructor de cada classe: se novas heranças de TWForma forem declaradas, haverá um único ponto do código para ser alterado.

Uma forma de usar o exemplo seria incluir um Combo num Form para permitir que o usuário adicione um nova forma a uma lista e essa lista controle o que será desenhado em tela:
tpForma := TWTipoForma (ComboBox1.ItemIndex);
forma := TWFormaFactory.GetTWForma (tpForma);
ListaFormas.Add (forma);
{ ... }
forma.Desenha;

25 de julho de 2009

Implementando Design Patterns

Há muitas expressões associadas à Tecnologia de Informação que são meros modismos, formas de se criar problemas para vender solução. Essas expressões surgem e logo são descartadas para dar lugar a outras mais "modernas", muitas vezes ideias requentadas que são apresentadas sob uma nova roupagem. Confesso que pensei ser esse o caso a primeira vez que ouvi falar dos Design Patterns ou Padrões de Projeto de Software, mas estava enganado.

A expressão Design Patterns diz respeito a uma série de soluções bem testadas e documentadas para resolver problemas computacionais específicos. Ou seja, alguém tinha um problema de programação e conseguiu resolvê-lo de uma forma bem estruturada, tornando a solução reaproveitável. É justamente essa a razão de não ser apenas modismo já que cada uma das soluções pode ser aplicada sempre que aparecer a mesma situação que o Pattern se propõe a resolver, independendo da linguagem de programação ou do ambiente operacional escolhido - única restrição é que a linguagem comporte o conceito de Objetos. É provável que você já use algumas das soluções propostas sem mesmo saber que se trata de um Design Pattern já estabelecido.

A ideia dos Padrões de Projeto foi introduzida na década de 1970 pelo austríaco Christopher Alexander. Ele propôs um sistema que estabelece as características de um padrão - isto é, como determinar que algo se constitui num padrão - e como descrever formalmente o padrão. A grosso modo, essa descrição formal deve detalhar o problema em questão, contextualizá-lo ao indicar em quais situações o padrão descrito se aplica e, obviamente, descrever a solução para o problema proposto. Embora Christopher Alexander seja arquiteto e sua teoria tenha sido calcada no projeto e construção como entendidos pela Arquitetura, seus conceitos de Padrões de Projeto são genéricos o suficiente para serem adotados na informática.

O transporte dessas ideias para a Computação teve início na década de 1980 mas o grupo mais conhecido de Padrões de Projeto de Software foi descrito em 1994 no livro Design Patterns. O fato do livro ter sido escrito por 4 programadores deu nome à coleção de Patterns: Gof ou Gang of Four.

Os padrões Gof foram agrupados em 3 categorias, de acordo com o momento em que são aplicados:
Criacionais: são padrões que introduzem formas de se criar instâncias de uma Classe. Os padrões dessa categoria tornam mais flexíveis a instanciação de classes em situações em que o tipo de classe necessária pode variar ao longo da existência de um programa.

Estruturais: são padrões que tratam das associações estruturais entre classes e objetos, isto é, as formas com que as classes se conectam dentro de um programa para garantir a coesão do projeto.

Comportamentais: são padrões que permitem modelar a maneira como classes e objetos colaboram entre si, estabelecendo as interações e as divisões de responsabilidades dessas classes e objetos num sistema.

Esse post é apenas uma introdução ao assunto. Pretendo colocar outros posts para mostrar na prática a utilização dos Design Patterns através de exemplos em Delphi.

24 de julho de 2009

Exportando dados para Excel com ADO

Agora que postei um pouco da teoria básica sobre o modelo de classes do ADO, posso cumprir a promessa que fiz de mostrar interação entre o Excel e o ADO através de script, populando uma planilha com os dados trazidos por uma query num banco de dados. Para ver como criar a planilha em si, consulte o post Criando uma planilha através de script já que neste eu vou focar mais a parte do uso do ADO.

De início, vou criar uma conexão com o banco de dados SQL Server pois esse é o banco mais comum entre os Clientes da ABC71. Veja o código usando VBScript:
Dim Cnxn, strCnxn, rs, query
' Conectar ao banco SQL
SetCnxn = CreateObject("ADODB.Connection")
strCnxn = "Provider=SQLOLEDB.1;Data Source=ABCVMS;Initial Catalog=DBTP;Locale Identifier=1046;"
Cnxn.Open strCnxn, "gustavo", ""
Cnxn.Execute ("set language us_english ")
Cnxn.Execute ("set dateformat dmy ")

O comando CreateObject cria uma instância do objeto "ADODB.Connection", isto é, aloca a memória necessária para que possa usar o objeto de conexão. O comando Open que vem logo em seguida estabelece a conexão com o banco de dados. Passo a ele 3 valores: a string de conexão, o nome do usuário do banco de dados e a senha deste usuário. O exemplo traz uma string de conexão para o MS SQL Server; para saber como montar essa string para outros bancos, clique aqui. Os dois comandos Execute que encerram o trecho de código configuram a linguagem e o formato de datas retornados pelo SQL Server.

Já temos a conexão, podemos então submeter uma query no banco de dados:
query = "SELECT * FROM TPUF"
Set rs = Cnxn.Execute (query)
Set folha = objWorkBook.WorkSheets (1)
for j = 0 to rs.Fields.Count - 1
folha.Cells (1,j+2) = rs.Fields.Item(j).Name
next

' Configura o cabeçalho
Set lRng = folha.Range (folha.Cells (1,1), folha.Cells (1,j+1))
lRng.Interior.Color = RGB(240,240,127)
lRng.Interior.Pattern = 1 'xlPatternSolid
lRng.Font.Name = "Tahoma"
lRng.Font.Bold = True
lRng.Font.Size = 11

Neste trecho do código, o comando Execute da conexão submete um SELECT e obtem um Recordset com os registros e campos encontrados. As definições dos campos podem ser consultadas na propriedade Fields, que é uma lista. Através do laço for, esta lista de campos é percorrida e o nome de cada campo é colocado numa coluna da primeira linha da folha de trabalho do Excel. Após o laço for, a coluna com os nomes dos campos é formatada de modo a representar um cabeçalho para os valores a serem recuperados dos registros.

Falta, então, percorrer os registros encontrados e lançar os valores sob a coluna correta na folha da planilha:
i = 2
Do While Not rs.EOF
folha.Cells (i,1) = i - 1 'Número do registro
for j = 0 to rs.Fields.Count - 1
folha.Cells (i,j+2) = rs.Fields.Item(j).Value
next
i = i + 1
rs.MoveNext
Loop

O laço Do While percorre todos os registros retornados pela query, testando se o fim foi atingido através da propriedade EOF (fim de arquivo). O laço for interno percorre a lista de campos do registro atual, lançando o valor de cada um na coluna certa da planilha. Cada registro ocupa uma linha na folha da planilha, sendo que a variável i determina qual é o número da linha. Ela é iniciada com a segunda linha já que a primeira foi preenchida com o cabeçalho.

Como arremate, inclui mais um laço para formatar cada coluna da folha com base no tipo de dado especificado na definição do campo:
for j = 0 to rs.Fields.Count - 1
Set lRng = folha.Range (folha.Cells (1,j+2), folha.Cells (i,j+2))

Select Case rs.Fields.Item(j).Type
Case 4, 5, 6, 14, 131, 139 ' Números c/ decimais
lRng.ColumnWidth = rs.Fields.Item(j).DefinedSize
lRng.NumberFormat = "#,##0.00_);[Red](#,##0.00)"
lRng.HorizontalAlignment = -4152 ' xlRight'

Case 2, 3, 16, 17, 18, 19, 20, 21 ' Números Inteiros
lRng.ColumnWidth = rs.Fields.Item(j).DefinedSize * 4
lRng.NumberFormat = "#,##0_);[Red](#,##0)"
lRng.HorizontalAlignment = -4152 ' xlRight'

Case 7, 64, 133, 135 ' Datas
lRng.ColumnWidth = 12
lRng.NumberFormat = "m/d/yyyy"
lRng.HorizontalAlignment = -4108 ' xlCenter'

Case Else ' Textos e outros
lRng.ColumnWidth = rs.Fields.Item(j).DefinedSize + 3
lRng.NumberFormat = "General"
lRng.HorizontalAlignment = -4131 ' xlLeft'
End Select
next

Neste ponto, a variável i armazena o número da última linha que foi alimentada com valores dos registros. Então, percorro novamente a definição de todos os campos, obtenho a propriedade Type de cada um e determino a formatação mais conveniente. Os tipos de dados são definidos por um enumerado do ADO chamado DataTypeEnum mas nem todos foram incluídos no exemplo. Consulte a documentação desse enumerado para mais detalhes.

22 de julho de 2009

Modelo de objetos para acesso de dados com ADO

Já faz algum tempo que os IDEs - Ambientes Integrados de Desenvolvimento - vêm facilitando a vida de quem cria programas com acesso a banco de dados. Ambientes como o Delphi e o Visual Studio permitem que se conecte a um banco e se crie telas para extrair ou dar manutenção em dados de forma rápida e intuitiva, bastando alguns cliques do mouse.

Essa abordagem permite criar aplicações rapidamente mas tem alguns problemas. Primeiro, a ferramenta acaba fazendo tudo pra você e o conhecimento para usar a tecnologia acaba se restringindo aos cliques do mouse. Depois, com a aplicação montada apenas visualmente, pode ser bastante trabalhoso caso seja necessário trocar a tecnologia de acesso ao banco. Na ABC71, por exemplo, trocamos o uso do BDE pelo ADO e a troca foi muito mais transparente porque usamos classes para persistência dos dados ao invés dos recursos visuais.

Então, se quisermos montar classes para persistência de dados usando ADO, teremos que ir um pouco mais fundo para desvendar a tecnologia. Esse conhecimento poderá então ser usado em outras situações, como em automatização do acesso a banco de dados através de scripts.

Trabalhar diretamente com a API do ADO é relativamente fácil pois há poucas classes, apenas duas para executar o básico:
Connection: Use essa classe para estabelecer conexão com um banco de dados. Antes de se conectar, é possível configurar:
a) Tempos de espera (timeout) para a conexão e para a execução de comandos;
b) O nível de isolamento das transações (isto é, se a conexão conseguirá ou não enxergar dados que ainda não sofreram Commit);
c) O local padrão para os cursores (se no próprio banco de dados ou se serão tratados no Client)
d) Parâmetros dependente do fabricante do banco de dados.

Para estabelecer uma conexão, use o método Open informando uma string de conexão. Essa "string" é um texto com parâmetros para determinar qual é o tipo do banco de dados, o endereço do servidor, quem é o usuário e sua senha, etc. Os parâmetros aceitos dependem do banco com o qual se quer conectar; no endereço http://www.connectionstrings.com há exemplos dessa string para uma ampla gama de banco de dados.

A classe Connection permite ainda controlar transações no banco de dados, através dos comandos BeginTrans, CommitTrans e RollbackTrans.

O método Execute permite submeter comandos diretamente no banco de dados, tais como UPDATE, INSERT e DELETE. No caso de comandos SELECT, um objeto do tipo RecordSet é retornado.

RecordSet: É uma coleção de registros obtidos no banco de dados. Ela permite navegar pelos registros (comando MoveNext, MoveFirst, MoveLast e MovePrevious) e trabalhar com as informações contidas neles. É importante observar que apenas um registro está posicionado por vez e que, dependendo do modo de edição em que este registro se encontra, você pode submeter alterações nos dados através do próprio Recordset. Use o método AddNew dessa classe para preparar a criação de um novo registro, Delete para removê-lo e Update para efetivar alterações (incluindo a inserção de novos registros).

Obs: quando obtido através do Execute, um RecordSet navega apenas adiante na coleção (não é permitido voltar) e os registros são read-only (não é possível alterar os dados).


Outras classes complementares ampliam as possibilidades do ADO. Por exemplo, para se obter uma coleção de registros que possam ser alterados (modo de edição "gravável'), use a classe Command, ajustando a propriedade ActiveConnection para que ela use uma conexão (Connection) que já esteja estabelecida. Um Command é mais flexível que o Execute da conexão pois pode trabalhar com queries parametrizadas e queries preparadas (para melhorar a performance), pode estabelecer que os registros retornados serão editáveis e a navegação entre os registros pode ir para frente e para trás (desde que o provedor para o banco de dados em questão tenha implementado esse recurso).

Ainda, o Recordset dá acesso a objetos do tipo Field, que representa cada um dos campos existentes no Recordset, fornecendo informações como o nome do campo, seu tipo de dado e o tamanho desse dado.

Pretendo incluir novos posts sobre esse assunto, mostrando exemplos de uso para vários dos recursos do ADO.

20 de julho de 2009

Criando programa para monitorar a criação de arquivos

Para auxiliar na migração de sistema de um novo Cliente da solução de ERP da ABC71, tive que montar um programa para resolver a seguinte situação: haverá um período de convivência entre o sistema que eles possuem hoje e o Omega de modo que, em certas operações, um arquivo gerado pelo sistema atual terá que ser carregado pelo Omega.

Minha primeira ideia para monitorar uma pasta no Windows foi colocar um temporizador (TTimer do Delphi ou C++ Builder). Dado um intervalo de tempo, a pasta seria varrida para determinar se havia arquivo novo a ser importado. Deparei, então, com a questão "Qual intervalo de tempo é apropriado ?". Se o intervalo for muito curto, o programa pode consumir demais os recursos do computador sem necessidade; se o tempo for muito longo, pode haver demora excessiva para a importação e, como o arquivo gerado tem ligação com faturamento do Cliente ...

Pesquisando um pouco mais, encontrei algumas funções de gerenciamento de pastas da API do Windows que ajudaram a abandonar o temporizador para resolver o problema. A função base é a FindFirstChangeNotification. A ideia dessa função é simplesmente criar um vínculo do seu programa com uma pasta do Windows de forma que você é notificado sempre que alguma mudança ocorrer nesta pasta. O código abaixo pede ao Windows que notifique mudanças na pasta "C:\temp":
HWND _WaitHandle = FindFirstChangeNotification ("C:\\TEMP", false, FILE_NOTIFY_CHANGE_FILE_NAME);

O primeiro parâmetro é o nome da pasta a ser monitorada - se tiver que monitorar mais pastas, terá que chamar a função uma vez para cada pasta. O segundo parâmetro indica se quero monitorar também as pastas que estão dentro daquela informada no primeiro parâmetro. Informei false pois não quero monitorar toda a árvore de pastas. O último parâmetro informa que tipo de mudança quero monitorar. O valor FILE_NOTIFY_CHANGE_FILE_NAME faz com que o programa seja notificado sempre que ocorrer qualquer mudança em nome de arquivo na pasta monitorada. Atenção apenas para um detalhe: "mudança de nome", aqui, inclui a criação de novos arquivos, a cópia de arquivos e a remoção de arquivos. Dê uma olhada na documentação da função para ver as outras possibilidades. No fim, a função retorna um Handle que será usado para responder ao evento.

Como é que o programa é notificado a respeito da mudança? Aqui entra uma diferença básica em relação ao princípio usado por eventos no Delphi/C++ Builder: o programa para de executar e fica em "espera" até que uma mudança seja notificada ou até que um tempo estipulado se esgote. Como no meu caso o programa faz outras coisas e não pode ficar esperando, criei uma Thread separada apenas para monitorar:
while (! Terminated)
{
VerificaPasta();
ret = WaitForSingleObject (_WaitHandle, 30000);

if (ret == WAIT_OBJECT_0 && (! Terminated))
{
VerificaPasta();
FindNextChangeNotification (_WaitHandle);
}
}

A função de sincronização WaitForSingleObject é que faz o programa aguardar até que o handle fornecido receba uma notificação ou que o tempo requisitado expire - no exemplo, esse tempo é de 30 segundos. Se esta função retornar o valor WAIT_OBJECT_0, significa que a espera terminou por causa de uma notificação de mudança (e não por timeout).

E como eu sei que a notificação foi motivada pela criação de um novo arquivo ou se um arquivo foi removido ou se simplesmente ele foi renomeado? É preciso vasculhar a pasta para determinar pois o evento em si não dá essa informação ... É o que faz a minha função VerificaPasta no exemplo acima.

Uma vez que a função de espera sai por causa de uma notificação, o programa deixa de receber novas notificações de mudança. Se quiser continuar a recebê-las, é preciso registrar novamente, dessa vez através de uma chamada à função FindNextChangeNotification, isto é, "aguarde a próxima notificação". Veja no exemplo que usei o mesmo Handle usado na chamada à função FindFirstChangeNotification.

Quando não quiser mais receber notificações, o handle tem que ser encerrado:
FindCloseChangeNotification (_WaitHandle);

17 de julho de 2009

Verificando assinaturas digitais com CAPICOM

Continuando a série sobre Certificados e assinaturas digitais usando a biblioteca CAPICOM, falo agora sobre como verificar se uma assinatura é válida, isto é, se os dados que foram assinados não foram adulterados de alguma forma.

Vou usar aqui o mesmo exemplo do post sobre assinatura digital com CAPICOM. Naquele exemplo, assinei o conteúdo de um arquivo e salvei o resultado num outro arquivo. Ao assinar, programei o CAPICOM para incluir na assinatura todo o conteúdo assinado.

Neste caso, para verificar se a assinatura é válida, é preciso apenas instanciar a mesma classe que foi usada para assinar: a TSignedData. Diferentemente do processo de assinatura, a validação não exige configuração extra do TSignedData pois a chave pública necessária já foi incluída na própria assinatura. O código abaixo mostra a validação da assinatura gerada no post anterior:
var lSignedData: TSignedData;
fs : TFileStream;
qt : integer;
ch : PWideChar;
msg : WideString;
ok : boolean;
begin
fs := TFileStream.Create (edMsgFile.Text, fmOpenRead);
New (ch);
repeat
qt := fs.Read(ch^, 2);
if qt > 0 then
msg := msg + ch^;
until qt = 0;
fs.Free;
Dispose (ch);

lSignedData := TSignedData.Create(self);

try
ok := false;
lSignedData.Verify (msg, false, CAPICOM_VERIFY_SIGNATURE_ONLY);
msg := lSignedData.Content;
ok := true;
except
on exc: Exception do
ShowMessage (exc.Message);
end;

lSignedData.Free;

if ok then
ShowMessage ('Conteúdo validado com sucesso !');
end;

Na primeira parte do código, leio o arquivo com a assinatura e jogo esse conteúdo para a variável msg. Lembre-se de que a assinatura foi gerada num WideString e que, portanto, cada caractere é representado por 2 bytes. Por isso, a leitura é feita usando um WideChar e lendo 2 bytes por vez antes do valor ser concatenado ao WideString com a assinatura.

Depois, crio a instância do TSignedData e apenas chamo sua função Verify para realizar a validação. O primeiro parâmetro desta função é o conteúdo assinado (que foi lido do arquivo); o segundo parâmetro tem o nome de Detached, significando "solto" ou "desprendido" em referência ao fato de indicar se o conteúdo original está "desprendido" ou se foi embutido na assinatura - por isso passo false nele: o conteúdo não está desprendido já que fiz a assinatura pedindo para inserir o conteúdo. O último parâmetro é um indicador do quê será validado: somente a assinatura ou validar também o certificado.

Se tudo ocorreu bem na verificação, a propriedade Content da classe TSignedData agora possui o mesmo conteúdo que foi originalmente assinado, isto é, este processo também pode ser usado para criptografar os dados e de quebra saber se eles não foram adulterados já que para assiná-lo é preciso ter a chave privada e só você deveria ter esta informação.

No caso de um erro acontecer na validação, uma exceção é levantada. Então, incluí o processo de validação num bloco try-except para poder tratar a mensagem retornada.

E se a assinatura for gerada sem o conteúdo ? Como validar ? Simples: alimente manualmente a propriedade Content antes de chamar o Verify. Nessa situação, tanto o conteúdo original quanto a assinatura têm que estar presentes para a validação funcionar.

16 de julho de 2009

Assinando documentos com CAPICOM

No meu último post, falei sobre a biblioteca criptográfica da Microsoft - o CAPICOM - e como usá-la para acessar o Certificate Store do Windows, recuperando e processando os certificados digitais registrados. Agora, vou mostrar como usar as classes do CAPICOM para assinar documentos e como verificar se a assinatura de um documento é válida.

Pra começar, é preciso ter um Certificado Digital disponível com a chave privada para que se possa assinar documentos. Se não possuir tal certificado, pode gerar um através do programa makecert da Microsoft. Tenha em mente apenas que o certificado assim gerado não pode assinar documentos oficialmente pois não é fornecido por uma CA (Certification Authority) válida. Um exemplo do makecert pode ser visto no quadro abaixo, onde é criado um certificado pessoal ("My") no Store Current User com validade até 1 de Janeiro de 2010:
makecert -r -pe -n "CN=Minha Empresa" -b 01/01/2005 -e 01/01/2010 -sky exchange -ss my

Para assinar um documento, precisaremos trabalhar com 2 classes do CAPICOM:
TSigner que contem as informações sobre quem está assinando um documento, incluindo o Certificado Digital.
TSignedData que permite estabelecer os dados a serem assinados. A verificação de uma assinatura também pode ser feita com essa classe.

O trecho de código abaixo carrega um arquivo, assina-o baseado num Certificado Digital e grava o conteúdo assinado num outro arquivo. Veja o post anterior para saber como gerar os fontes associados ao CAPICOM e como encontrar um Certificado no Store.
procedure TForm1.Assina (cert: TCertificate);
var lSigner : TSigner;
lSignedData : TSignedData;
fs : TFileStream;
qt : integer;
ch : PChar;
msg, content : WideString;
begin
{ Abre o arquivo original para obter dele o conteúdo a ser assinado }
fs := TFileStream.Create (edFileName.Text, fmOpenRead);
New (ch);
repeat
qt := fs.Read(ch^, 1);
if (qt > 0) then
content := content + ch^;
until qt = 0;
fs.Free;
Dispose (ch);

{ Configura o objeto responsável por fazer a assinatura, informando qual é o certificado a ser usado e o conteúdo a ser assinado }
lSigner := TSigner.Create(self);
lSigner.Certificate := cert.DefaultInterface;

lSignedData := TSignedData.Create(self);

lSignedData.Content := content;
{ Efetivamente assina o conteúdo }
msg := lSignedData.Sign(lSigner.DefaultInterface, false, CAPICOM_ENCODE_BASE64);

{ Cria um novo arquivo e grava nele o resultado da assinatura }
fs := TFileStream.Create (edMsgFile.Text, fmCreate);
for qt := 1 to Length (msg) do
fs.Write(msg[qt], 2);

fs.Free;

lSignedData.Free;
lSigner.Free;
end;

Neste código, crio uma instância do TSigner e associo a ele o certificado passado no parâmetro da função. Depois, crio uma instância do TSignedData e alimento sua propriedade Content com o conteúdo lido do arquivo (texto) que será assinado.

Com essa configuração ajustada, posso chamar a função Sign do TSignedData para assinar o conteúdo. O primeiro parâmetro desta função é o TSigner onde informei o certificado digital que tem chave privada. O segundo parâmetro indica se o conteúdo original será incluído na assinatura resultante ou não. Incluir o conteúdo na assinatura resulta uma assinatura muito maior mas o processo de verificação dela permitirá extrair o conteúdo original (desde que, é claro, a assinatura não tenha sido adulterada). Se não incluir o conteúdo na assinatura, o conteúdo terá que estar presente para que se possa executar o processo de validação; em compensação, a assinatura será pequena.

O resultado da função Sign é um WideString contendo a assinatura. No exemplo, pedi que a assinatura inclua o conteúdo original e, então, salvo toda a assinatura num arquivo separado. Como cada caracter do WideString ocupa 2 bytes, o trecho do código que grava em arquivo o resultado da assinatura é obrigado a gravar os 2 bytes por caracter. Mostro no próximo post como validar essa assinatura gerada.

14 de julho de 2009

Acessando o Certificate Store do Windows com CAPICOM

Desde que eu publiquei aqui posts sobre Certificados Digitais usando o framework .Net, várias pessoas me contataram dizendo que não desenvolvem com .Net e perguntando como acessar o Certificate Store do Windows e como assinar documentos digitalmente sem usar o Framework. Já havia falado em outra ocasião mas repito aqui: a solução é usar a biblioteca CAPICOM (Cryptographic Application Programming Interface) da Microsoft, cujo download é gratuito.

Vou mostrar nesse post como utilizar as classes do CAPICOM em Delphi para acessar o Certificate Store do Windows já que esse passo precede a assinatura de documentos - é preciso recuperar um Certificado Digital para realizar a assinatura. No post sobre como trabalhar com o Certificate Store com .NET há mais informações e terminologia sobre o assunto.

Como o CAPICOM é uma biblioteca COM, o primeiro passo é extrair as interfaces e colocá-las na forma de fontes do Delphi. Se ainda não tem os fontes, veja aqui como fazer essa extração e gerá-los. Para usar as classes exportadas, o seu programa deve incluir a unit gerada:
uses CAPICOM_TLB;
Da mesma forma que no .NET, há três classes básicas no CAPICOM para acessar o Store:
TStore que fornece os métodos para acessar o Certificate Store. Há um método Open nessa classe onde se estipula qual parte do Store se quer acessar: store pessoal ("My"), as autoridades certificadoras ("CA"), etc.
TCertificates é uma lista com todos os certificados que atendem os parâmetros especificados ao abrir o Store ou quando se usa a função de seleção Select.
TCertificate representa um certificado e suas propriedades, isto é, se ele está válido, qual é a chave pública, se há uma chave privada disponível e qual é esta chave.

O trecho de código Delphi abaixo abre o Certificate Store "My" da máquina local com privilégio apenas para leitura, recupera todos os certificados encontrados e percorre a lista pesquisando os certificados que são válidos.
var i : integer;
store : TStore;
cert : TCertificate;
certs : TCertificates;
ov : OleVariant;
begin
store := TStore.Create (self);

store.Open (CAPICOM_LOCAL_MACHINE_STORE, 'My', CAPICOM_STORE_OPEN_READ_ONLY);

certs := TCertificates.Create(self);
certs.ConnectTo(store.Certificates as ICertificates2);

cert := TCertificate.Create(self);
for i := 1 to certs.Count do begin
ov := (certs.Item [i]);
cert.ConnectTo (IDispatch (ov) as ICertificate2);

if cert.HasPrivateKey And
(cert.ValidFromDate <= Now) And
(cert.ValidToDate >= Now) then begin
{ Use o certificado aqui para, por exemplo, assinar um documento. }
end;
end;

store.Close;

cert.Free;
certs.Free;
store.Free;
end;

Algumas observações a respeito desse código:
As classes do CAPICOM foram instanciadas através de seus respectivos construtores Create. Por questão de compatibilidade, a Microsoft mantem na assinatura das funções da interface os tipos de estrutura usadas na primeira versão do CAPICOM. Como estou usando o CAPICOM 2.x, o TCertificates aqui é uma extensão da interface original ICertificates. Para poder usar todos os recursos da nova estrutura, crio na mão uma instância do TCertificates e conecto-a à referência de interface retornada pelo TStore. O mesmo vale para o ICertificate
O TCertificates é uma lista e a propriedade Count indica quantos certificados há nesta lista.
Para acessar um certificado na lista TCertificates use a propriedade Item. Valores válidos para indexar essa propriedade vão de 1 até Count
O mapeamento gerado pelo Delphi para a propriedade Item retorna um valor do tipo OleVariant. Na verdade, ela encapsula uma referência à interface ICertificate2 mas como não é permitido fazer um cast direto para esse tipo, transformo antes o valor retornado pelo Item em um IDispatch. Como o IDispatch é o ancestral de todas as interfaces (em qualquer COM), posso então fazer o cast para o tipo correto, conectá-lo à classe TCertificate e usar normalmente as propriedades do ICertificate2 para verificar se o certificado é válido e tem chave privada que possa ser usada num assinatura digital.
No exemplo acima, acesso apenas os certificados My armazenados na área da máquina local. Se quiser acessar outros certificados (CA ou root, por exemplo) em outras áreas (específicos do usuário, por exemplo) veja a documentação do Open para saber que valores passar.
A função Close do Store tem que ser chamada quando não for mais usar o Store ou se for necessário reabrí-lo para acessar outro conjunto de certificados.

11 de julho de 2009

Erro "Operação iniciada pelo Registry falhou"

Recentemente, um Cliente que tem a solução de ERP da ABC71 com Nota Fiscal Eletrônica nos notificou a respeito de uma mensagem de erro que vinha ocorrendo com frequência no servidor deles, onde reside uma parte da solução. A mensagem de erro era retornada pelo Windows e não esclarecia muito: "an i/o operation initiated by the registry failed unrecoverably".

Em princípio, suspeitei que este erro tivesse relação com o problema descrito no tópico 312362 da base de conhecimento da Microsoft (o servidor não conseguiu alocar memória no arquivo de paginação) pois uma primeira triagem de causas apontou erro na alocação de memória. Infelizmente, o procedimento descrito na base não resolveu a questão.

Achei, então, algumas informações interessantes numa discussão no fórum Parallels que foram úteis. O último comentário nesta discussão explica que o erro "an i/o operation initiated by the registry failed unrecoverably" ocorre quando não há memória disponível para executar a operação e também não foi possível paginar a memória em uso, isto é, lançar no arquivo de paginação do Windows blocos de memória que não precisem estar disponíveis num momento. Como essa falta de memória poderia ser causada por algum programa que estivesse usando muitos "handles" do Windows, passamos a verificar o uso desse recurso em nossos programas. Praticamente toda operação no Windows usa Handles: além do próprio acesso ao Registry, outros exemplos são a manipulação de arquivos, acesso a Serviços, uso da impressora e até desenhar numa janela. A quantidade de handles alocados por programa pode ser visto no Task Manager, numa coluna que deve ser incluída na guia Processes. O nome da coluna é Handle Count. Fazer ordenação decrescente por essa coluna mostra se há algum programa tentando alocar muitos handles.

De posse dessas informações, percebemos que o problema não era em nossos programas. Voltamos ao fórum onde, na sequência da discussão, o autor do comentário diz que todas as vezes que se deparou com essa mensagem tratava-se de infecção por virus ou alguma tentativa de hack. Mas as instruções descritas lá poderiam reiniciar o servidor do Cliente e até danificar arquivos (veja o fórum se quiser mais informações sobre esses procedimentos) e isso nos levou a deixar de lado essa tentativa.

Pesquisando em outras fontes, finalmente encontrei informações que resolveram o problema. No caso, era um outro software que havia sido atualizado recentemente e um bug nessa atualização deixava o Windows instável. Listo abaixo uma série de itens que levantei para serem verificados caso essa mensagem de erro apareça:
Alguns softwares podem causar o erro. Se há suspeita sobre algum, desabilite-o e veja se o erro persiste. Pode ser qualquer programa, incluindo novos programas recém instalados ou atualização de softwares que já existiam.

Se o usuário logado possui quota para uso de disco, atingir o limite da quota pode gerar o erro. Atenção: é possível configurar Serviços do Windows para que use um usuário diferente daquele que está logado no sistema operacional. É a cota do usuário especificado no Serviço que está valendo para o Serviço em questão.

Matenha atualizado o Sistema Operacional e os demais softwares, aplicando patches de correção que estiverem disponíveis.

Problemas com o hardware também podem provocar esse erro: memória ou placa-mãe defeituosa, disco rígido corrompido (executar o comando chkdsk /r pode ajudar a detectar esta situação), cabo do disco mal colocado, etc..


9 de julho de 2009

Lendo arquivos texto com ADO

Apesar do sucesso e da grande disseminação do formato XML, há ainda uma vasta gama de situações em que o uso de arquivos texto (TXT) é mais conveniente. Há um Cliente recente da ABC71, por exemplo, que está vindo de um sistema COBOL desenvolvido por eles mesmos. Esse Cliente terá que se integrar ao sistema da Nota Fiscal Eletrônica mas não haverá tempo hábil para implantar o Omega, que possui esta integração de forma nativa. A solução, neste caso, foi fazer com que o sistema COBOL gerasse arquivos no formato texto com campos de tamanho fixo para que pudéssemos colocar as informações no fluxo para a Receita.

Uma forma simples de realizar a leitura desses arquivos texto foi usar o driver ODBC para Arquivos Texto da Microsoft, que acredito ser distribuido com o Windows. Tendo um driver ODBC, fica fácil acessar os dados através de uma conexão ADO já que se usa mecanismos bem conhecidos de acesso a bancos de dados.

O conceito de uso de arquivos TXT como banco de dados é simples: todos os arquivos TXT colocados numa determinada pasta são considerados parte do banco, sendo que cada arquivo é tratado como uma tabela diferente desse banco. Nesses arquivos, cada linha corresponde a um registro. Para ligar tudo isso, é preciso criar um arquivo que descreve como os dados estão organizados em cada TXT; tal arquivo é apropriadamente nomeado schema.ini já que o que ele descreve é justamente o esquema do banco de dados.

O arquivo schema.ini é um arquivo texto comum, criado para ter uma seção para cada tabela. Assim, o nome da "tabela" vai entre um par abre/fecha colchetes numa linha e, nas linhas subsequentes, são colocadas configurações gerais para a "tabela" e uma lista com a definição dos "campos", com seus nomes e tipos de dado esperado. O quadro abaixo mostra um exemplo de schema.ini com duas tabelas. Explico em seguida o significado de cada parâmetro.
[mestre.txt]
ColNameHeader=False
Format=FixedLength
MaxScanRows=0
DateTimeFormat=YYYYMMDD
CharacterSet=ANSI
Col1=EMPRESA Long width 4
Col2=FILIAL Long width 4
Col3=INFNFE_ID Char width 47
Col4=TOTAL Float width 19
Col5=DATA_ALTER Date width 8
[detalhes.txt]
ColNameHeader=False
Format=FixedLength
MaxScanRows=0
DateTimeFormat=YYYYMMDD
CharacterSet=ANSI
Col1=INFNFE_ID Char width 47
Col2=SEQ_NF Long width 4
Col3=CODIGO Char width 60
Col4=QTDE Float width 19
Col5=VALOR Float width 19
Col6=DATA_ALTER Date width 8

Nesse exemplo, referenciei dois arquivos como tabelas: mestre.txt e detalhes.txt, cada uma descrita em sua própria seção e cada parâmetro inserido numa linha separada. O significado dos parâmetros é o seguinte:
  • ColNameHeader indica se a primeira linha do arquivo texto especifica o nome das colunas. No meu caso, os arquivos têm apenas dados, razão pela qual informei False nas duas "tabelas".
  • Format É o modo como os valores dos campos estão distribuídos nas tabelas. Informei FixedLength porque no meu caso cada campo terá uma quantidade fixa de caracteres em todas as linhas (registros) do arquivo. Esse parâmetro poderia ainda assumir os valores TabDelimited (onde o valor de cada campo seria separado por um caracter de tabulação), CSVDelimited (se ao invés do TAB o separador for uma Vírgula) ou Delimited (caso você queira estipular um separador diferente).
  • MaxScanRows Esta configuração é usada somente quando se usa o gerenciador do ODBC do Windows para configurar o schema.ini. O número especificado nele determina quantos registros (linhas) o Gerenciador deve ler antes de tentar determinar os tipos de dados dos campos. Tentei usar o Gerenciador mas não fui muito feliz; preferi criar na mão ...
  • DateTimeFormat diz qual é o formato das datas incluídas no arquivo. O valor YYYYMMDD significa que, no meu caso, as datas terão 4 dígitos para o ano, 2 para o mês e 2 para o dia, sem separadores.
  • CharacterSet é o codepage para os caracteres a serem usados. Especifiquei o conjunto Ansi, que é o valor padrão e atende a maioria dos casos.
Todos esses parâmetros e outros que não usei são explicados no MSDN.

Em seguida, vêm os parâmetros começados com COL que descrevem as colunas (campos) da tabela TXT. A sintaxe é
Coln=NOME TIPO [COMPRIMENTO]

Onde:
  • n é um número sequencial, isto é, o primeiro campo é Col1, o segundo Col2 e assim por diante.
  • NOME é um nome único para o meu campo. Pode-se usar esses nomes em comandos SELECT para extrair informações do banco de dados.
  • TIPO é um dos tipos de dados permitidos.
  • [COMPRIMENTO] indica a extensão do campo em quantidade de caracteres. Para o formato de arquivo FixedLength, esse comprimento é obrigatório para todos os campos; nos demais formatos informe apenas para campos do tipo Char.

Para usar no ADO, crie uma conexão com uma Connection String apropriada, como no exemplo:
m_strCnxn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\PastaTxt;Extended Properties=""Text;HDR=No"""
m_Cnxn.Open strCnxn, "", ""

O caminho c:\PastaTxt é o local onde coloquei os arquivos TXT e o SCHEMA.INI. Veja que não foi necessário criar uma fonte de dados ODBC para se conectar! Para finalizar, você pode submeter queries a esse banco de dados usando o Execute da conexão:
Set m_ResultSet = m_Cnxn.Execute("select * from mestre.txt")

É interessante observar que foi informado o nome do arquivo TXT como se fosse um nome de tabela.


6 de julho de 2009

Incluindo gráfico numa planilha Excel com scripts

Publiquei em junho uma série de posts sobre como automatizar a criação de planilhas Excel através de VBScript. Inclui informações sobre os principais objetos do Excel, um exemplo de como criar uma planilha básica, exemplo mostrando formatação de planilhas e criação de fórmulas, tudo utilizando scripts. Dentro do que eu tinha proposto, faltou incluir informações e exemplo sobre como trabalhar com gráficos numa planilha.

Para isso, vou incrementar o cenário descrito nos outros posts e o exemplo incluído neles. O objetivo é criar um gráfico de linhas que mostre a evolução por dia dos valores das colunas Mês 1 e Mês 2, conforme criados naqueles exemplos.

Há duas formas de se ter gráficos numa planilha: incluir uma nova folha de trabalho que contenha um gráfico ou embutir um Chart dentro de um Worksheet já existente. Vou usar aqui este último método mas, do ponto de vista dos scripts, há poucas diferenças entre um e outro.

Dentro do objeto WorkSheet há uma propriedade chamada Shapes que permite gerenciar todos os objetos desenhados dentro da folha, tais como AutoShapes, FreeForms, imagens e objetos OLE. Como os gráficos são objetos OLE, podem ser facilmente inseridos na folha usando Shapes. O método AddChart do Shapes serve para isso e aceita como parâmetros o tipo de gráfico a ser criado e o posicionamento do gráfico dentro da folha. Apesar do nome da função ser AddChart, o objeto resultante é, na verdade, um Shape. Para obter o gráfico propriamente dito, usamos a propriedade Chart do Shape criado. Veja o exemplo:
Dim grafico
Set grafico = folha.Shapes.AddChart (4, 200, 10).Chart

O número 4 passado como parâmetro indica que o tipo de gráfico a ser criado é o de linhas. Para mais informações sobre os códigos dos tipos válidos, veja a documentação do enumerado XlChartType. Os outros dois números indicam a posição em pontos (pixels) do canto superior esquerdo (a esquerda e o topo, respectivamente) do gráfico. Vou deixar que o próprio Excel calcule o comprimento e altura mas também estes valores poderiam ter sido informados como parâmetros.

O próximo passo é informar ao Excel quais são os dados a serem considerados pelo gráfico. Agora que tenho a variável grafico do tipo Chart posso usar os métodos dessa classe para fazer todas as configurações necessárias. Os dados, por exemplo, consigo ajustar com a função SetSourceData.
grafico.SetSourceData folha.Range("A1:C31"), 2

Com esse comando, o gráfico é configurado para mostrar os dados existentes nas colunas A, B e C, linhas 1 a 31. O número 2 no segundo parâmetro indica que as séries devem ser criadas usando colunas (veja o enumerado XlRowCol) de modo que a primeira coluna (A) contém os valores para o eixo horizontal do gráfico e as outras duas colunas B e C são as séries de dados propriamente ditas. Obs: a Coluna A foi alimentada com os dias e as colunas B e C têm valores para os meses 1 e 2.

O código abaixo faz outras configurações e formatações ao gráfico, como acrescentar Legendas na parte de baixo e um título:
grafico.HasTitle = True
grafico.HasLegend = True
grafico.ChartTitle.Text = "Distribuição por Mês"
grafico.Legend.Position = -4107 ' xlLegendPositionBottom

Como já havia falado em outro post, tudo que você consegue fazer com o Excel através da interface visual você também consegue fazer através de script.

Dica: Uma boa forma de se descobrir como implementar por script uma sequência de passos dentro do Excel é gravar uma macro. Ligue o gravador de macros e execute a sequência de passos desejada. Depois que encerrar a gravação, edite o código VBA gerado para ver as funções que foram chamadas.

Estou usando o Office 2007 para essas implementações mas acredito que não haja diferenças importantes na forma de trabalhar.

O VBScript com o exemplo completo - incluindo os conceitos e exemplos dos outros posts meus - pode ser baixado aqui.

3 de julho de 2009

Mais sobre o HTML 5

Conforme vai evoluindo a especificação da nova versão do HTML - de número 5, a primeira alteração em 10 anos - mais análises sobre seu impacto vão sendo feitas e mais desdobramentos são revelados.

A análise mais interessante saiu em junho. Especialistas publicaram que os recursos que estão sendo incorporados ao HTML 5 tem um potencial grande de tornar obsoletas as ferramenteas atuais de desenvolvimento de aplicações RIA (Rich Internet Applications). Como o HTML 5 está sendo modelado visando a criação de aplicações Web mais ricas, incluindo maior flexibilidade no controle da aparência das páginas (com o novo Canvas e novas propriedades de posicionamento) e mais simplicidade na inclusão de áudio e vídeo, aplicações feitas com padrões proprietários tenderiam a desaparecer. Veja matéria sobre esse assunto na InfoWorld (em inglês) e na IDG Now! (em português).

Embora esses novos recursos do HTML 5 permitam reproduzir nativamente o comportamento e os efeitos obtidos com ferramentas como o Flash e o Silverlight, talvez ainda demore um bocado para que sejam efetivamente abandonadas. Primeiro, por causa do mercado: é fácil encontrar desenvolvedores com know-how nessas ferramentas pois há muita gente que trabalha com elas e que gosta delas, enquanto em HTML 5 ainda há alguns poucos. Depois, porque há muita coisa boa já em funcionamento, principalmente com Flash, e reescrever aplicações custa e tem seus riscos.

Apesar disso, usar uma tecnologia que é padrão - como será o HTML 5 - é melhor do que usuar tecnologias proprietárias já que nesse caso se está nas mãos de um único fabricante. O quê fazer se ele descontinuar a tecnologia ou modificá-la sensivelmente ou, pior, se o fabricante sumir do mercado? Com um padrão dentro do próprio browser torna-se menos arriscado apostar na tecnologia, além do fato de não ser preciso instalar outros softwares (plugins), bastando ter o browser.

O fato é que a batalha por ter um padrão ainda não está ganha. Esta semana saiu na InfoWorld uma notícia sobre a disputa em torno de qual o padrão de codificação - o codec - deve ser usado para áudio e qual o de video na especificação do HTML 5 (veja aqui, em inglês). Os principais fabricantes de browsers não conseguiram chegar a um acordo a este respeito e o editor responsável pela especificação W3C do HTML 5, Ian Hickson, decidiu não incluir recomendação de um codec específico para áudio nem para vídeo. Na prática, cada fabricante está livre para implementar o codec que achar mais conveniente. Ou seja, provavelmente ainda teremos que ter uma porção de plugins instalados no browser ...

Um outro ponto que estava causando alguma confusão é que o W3C tem um grupo paralelo trabalhando na especificação do XHTML 2, uma versão XML do HTML. A versão 2 estava sendo preparada para oferecer recursos de internacionalização e capacidade para executar em sistemas móveis. Visando aumentar os recursos disponíveis na confecção do HTML 5 e minimizar a confusão, a W3C anunciou esta semana que descontinuará o desenvolvimento do XHTML 2.

1 de julho de 2009

Sobre o Google Wave (e o Bing)

No começo de Junho, a Microsoft lançou oficialmente seu novo sistema de buscas na Internet - o Bing. O objetivo é que a ferramenta ofereça mais do que simplesmente busca na internet, usando o conceito de "busca semântica", isto é, o sistema tentaria interpretar as palavras-chaves digitadas pelo usuário e com isso conseguiria trazer resultados mais relevantes para a pesquisa.

Como é novidade, acaba aguçando a curiosidade e todo mundo acessa para dar ao menos uma espiada. Vem daí a melhora da posição da Microsoft no ranking do uso de buscadores reportado pelos medidores de audiência da Internet. Eu mesmo fiz esse acesso para testar o Bing mas não encontrei nada particularmente melhor do que os serviços que já existiam anteriormente, incluindo o Live Search da própria Microsoft e o onipresente Google.

Na mesma semana, a Google anunciou que está preparando um novo produto e que este também promete revolucionar o modo como usamos a Internet. O produto, feito com tecnologia Web 2.0 (basicamente HTML e JavaScript), foi anunciado no fim de maio no evento Google I/O 2009, uma conferência para desenvolvedores. O site do preview do Google Wave define a ferramenta como algo para comunicação e colaboração através da Internet, integrando e-mail, troca de mensagens instantâneas, bate-papo, rede social, compartilhamento de documentos e imagens, criação de blogs e wikis.

A base do sistema do Google são as Waves, canais hospedados na internet que comportarão as conversações entre as pessoas vinculadas. Por conversações aqui entenda-se não só troca de mensagens textuais (quer via email, quer via chat) mas também o compartilhamento de fotos, vídeos, e outros documentos, além de interação com mapas. Mas o principal diferencial é permitir a produção de documentos colaborativamente, isto é, várias pessoas da wave trabalhando num documento e até mesmo editando-o de forma simultânea.

Tudo que é dito, postado ou produzido fica registrado na wave, de modo que alguém que não estiver on-line pode acompanhar tudo o que acontece na wave no momento que lhe for mais oportuno, sabendo quem disse ou fez o quê e quando.

Esses recursos todos são construídos em cima de uma API - a Google Wave API - que é um conjunto de ferramentas para desenvolvimento. Essa API é disponibizada pela Google para que seja possível a desenvolvedores externos criar extensões que automatizem tarefas numa wave. Ela também permite que se incluia uma wave dentro de outro site para torná-lo mais interativo. Com isso, muitos desenvolvedores de software devem criar gadgets compatíveis com o ambiente das waves, incrementando cada vez mais as funcionalidades existentes, da mesma forma que acontece com os plugins para o navegador Firefox.

Além disso tudo, a Google está trabalhando num protocolo aberto para permitir a comunicação entre waves. A ideia é que o protocolo, chamado Google Wave Federation Protocol, estimule o surgimento de outros produtores de waves e que waves publicados por diferentes produtores possam compartilhar informações entre si.

Como o Google Wave ainda não está em funcionamento, teremos mesmo que aguardar para saber o que de fato é revolucionário e o que não passa de marketing. A promessa é boa e deve sair ainda em 2009 ...