Introdução

O Office 2007 trouxe mudanças significativas para esta suíte de aplicativos: a mais visível foi o Ribbon, uma nova interface de usuário que facilita muito o uso dos aplicativos.

Outra mudança, menos visível à primeira vista, permite que os aplicativos do Office se integrem com uma grande variedade de programas: o novo formato de arquivo. Até as versões anteriores, o formato de arquivo era proprietário: quando se queria abrir ou gravar um documento do Office em nossas aplicações, era necessário usar automação OLE, o que requeria que o Office estivesse instalado na máquina do cliente, ou então tentar descobrir o formato dos arquivos, que não era completamente documentado e podia ser alterado a cada nova versão.

O novo formato de arquivo, além de documentado, é baseado em padrões abertos, o que permite que qualquer aplicação, em qualquer linguagem ou plataforma possa abrir ou criar arquivos Office 2007. Este novo padrão, chamado de Open XML, é baseado em compactação zip e arquivos XML,  gera arquivos com menor tamanho que os anteriores e permite que outras aplicações abram e alterem estes arquivos.

As possibilidades que se abrem são inúmeras:

  • Programas que permitem indexar e pesquisar textos a partir dos arquivos no disco
  • Programas para geração de documentos em lotes, a partir de bancos de dados e modelos
  • Programas para substituição de textos em lotes
  • Processadores de texto simplificados que geram arquivos Office
  • Geradores de planilhas eletrônicas a partir de dados provenientes de diversas fontes

Neste artigo, iremos mostrar o novo formato de arquivos e como podemos acessá-los e criá-los usando o Delphi, sem a necessidade de instalar o Office.

Analisando um arquivo OpenXML

Um arquivo OpenXML, seja ele um documento (docx), planilha (xlsx) ou apresentação (pptx) é, na realidade, um arquivo zip composto de diversas pastas e arquivos XML. Podemos ver isso na prática, criando um documento no Word composto de algum texto e uma imagem. Ao salvar este documento, podemos renomear o arquivo docx para zip e abri-lo com qualquer programa que abra arquivos zip:

Como podemos ver na Figura acima, o arquivo contém, na raiz, três diretórios, _rels, docProps e word e um arquivo, [Content_Types].xml. Esta estrutura de diretórios é criada pelo Word, e não é obrigatório que ela seja mantida. A localização dos arquivos fica no arquivo .rels, que está no diretório _rels. Este arquivo contém as relações entre o pacote e os arquivos de nível superior.  O seguinte código mostra o arquivo .rels criado em nosso exemplo:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail" Target="docProps/thumbnail.wmf"/>
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
  <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>

Analisando este arquivo, vemos o seguinte:

  • As propriedades principais (core-properties) estão no arquivo docProps/core.xml.
  • A imagem reduzida (thumbnail) está em docProps/thumbnail.wmf.
  • O documento principal (officeDocument) está em word/document.xml.
  • As propriedades estendidas (extended-properties) estão em docProps/app.xml.

Com base nestas informações, podemos abrir os arquivos que compõem o pacote OpenXML.

Adicionalmente, podemos ver que no diretório word existem um subdiretório _rels, que contém as relações para o documento. No arquivo document.txt.rels, encontramos as seguintes relações:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
  <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
  <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
  <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>
</Relationships>

Aqui são encontradas as relações para o documento. Vemos que os estilos aplicados no documento encontram-se no arquivo styles.xml e que a imagem que adicionamos está em media/image1.png. Assim, podemos acessar qualquer parte do documento.

A seguir iremos criar um pequeno programa em Delphi que abre um arquivo do Office e lista suas propriedades (tanto as principais como as estendidas) em um componente TValueListEditor.

Acessando os arquivos OpenXML

Para acessar os arquivos OpenXML , precisamos dividir nosso programa nas seguintes partes:

  • Abrir o pacote OpenXML com um componente que permite ler e gravar arquivos zip
  • Abrir o arquivo .rels em _rels e ler as relações, extraindo a localização das partes que nos interessam
  • Acessar as partes, executando a funcionalidade desejada.

Para abrir os arquivos Zip, usaremos o componente TZipFile, que está disponível no Delphi a partir da versão XE2. Este componente permite manipular arquivos zip de maneira relativamente simples. Crie um novo projeto no Delphi e adicione à Form um botão, um OpenDialog e um Memo.

Configure a propriedade Caption para Abrir. Configure a propriedade Filter do OpenDialog para “Arquivos Word (*.docx, *.docm)|*.docx;*.docm| Arquivos Excel (*.xlsx, *.xlsm)|*.xlsx;*.xlsm| Arquivos Powerpoint (*.pptx, *.pptm)|*.pptx;*.pptm”.

No handler do evento OnClick do botão, coloque o seguinte código:

procedure TMainFrm.Button1Click(Sender: TObject);
var
  ZipStream: TStream;
  XmlNode: IXMLNode;
  i: Integer;
  AttType: String;
  ZipFile: TZipFile;
  LocalHeader: TZipHeader;
begin
  if OpenDialog1.Execute then begin
    ZipFile := TZipFile.Create();
    try
      ZipFile.Open(OpenDialog1.FileName, TZipMode.zmRead);
      try
        // Lê relações
        ZipFile.Read('_rels/.rels', ZipStream, LocalHeader);
        ZipStream.Position := 0;
        XMLDocument1.LoadFromStream(ZipStream);
        Memo1.Text := XMLDoc.FormatXMLData(XMLDocument1.XML.Text);
      finally
        ZipStream.Free;
      end;
    finally
      ZipFile.Close();
      ZipFile.Free;
    end;
  end;
end;

Se o usuário escolher um arquivo, indicamos o nome do arquivo para o componente ZipFile e depois extraímos o arquivo .rels para um stream e carregamos as linhas do Memo com este stream, formatado com a função FormatXMLData. A figura a seguir mostra o resultado da execução.

Uma vez que temos o arquivo .rels, devemos lê-lo e interpretar as relações. Poderíamos usar as funções de leitura de arquivos texto e interpretar o documento, porém essa não é a melhor maneira de fazer esta operação. O ideal é usar um componente próprio para a leitura de arquivos XML, como o componente TXMLDocument, que vem com o Delphi na guia Internet da paleta de componentes.

Coloque dois componentes TXMLDocument e um componente TValueListEditor na Form.  Modifique a propriedade TitleCaptions do TValueListEditor para Propriedade/Valor. No evento OnClick do botão, modifique o código para:

procedure TMainFrm.Button1Click(Sender: TObject);
var
  ZipStream: TStream;
  XmlNode: IXMLNode;
  i: Integer;
  AttType: String;
  ZipFile: TZipFile;
  LocalHeader: TZipHeader;
begin
  if OpenDialog1.Execute then begin
    ZipFile := TZipFile.Create();
    try
      ZipFile.Open(OpenDialog1.FileName, TZipMode.zmRead);
      // Lê relações
      ZipFile.Read('_rels/.rels', ZipStream, LocalHeader);
      try
        ZipStream.Position := 0;
        XMLDocument1.LoadFromStream(ZipStream);
        Memo1.Text := XMLDoc.FormatXMLData(XMLDocument1.XML.Text);
        ValueListEditor1.Strings.Clear;
        // Processa nós
        for i := 0 to XMLDocument1.DocumentElement.ChildNodes.Count - 1 do begin
          XmlNode := XMLDocument1.DocumentElement.ChildNodes.Nodes[i];
          // Pega o tipo de relação.
          // Ela é a parte final do atributo Type
          AttType := ExtractFileName(XmlNode.Attributes['Type']);
          if AttType.EndsWith('core-properties') or
             AttType.EndsWith('extended-properties') then
            // Adiciona as propriedades no ValueListEditor
            LePropriedades(ZipFile, XmlNode.Attributes['Target']);
        end;
      finally
        ZipStream.Free;
      end;
    finally
      ZipFile.Close();
      ZipFile.Free;
    end;
  end;
end;

Carregamos o stream em XMLDocument1 e processamos os nós, para encontrar aqueles com os tipos que queremos (core-properties ou extended-properties). Quando os encontramos, passamos o nome do arquivo (que está no atributo Target) para a função LePropriedades, que irá ler o arquivo de propriedades e adicioná-las ao ValueListEditor. A função LePropriedades é:

procedure TMainFrm.LePropriedades(ZipFile: TZipFile; const Arquivo: String);
var
  ZipStream: TStream;
  i: Integer;
  XmlNode: IXMLNode;
  LocalHeader: TZipHeader;
begin
  ZipFile.Read(Arquivo, ZipStream, LocalHeader);
  try
    ZipStream.Position := 0;
    XMLDocument2.LoadFromStream(ZipStream);
    // Lê as propriedades
    for i := 0 to XMLDocument2.DocumentElement.ChildNodes.Count - 1 do
    begin
      XmlNode := XMLDocument2.DocumentElement.ChildNodes.Nodes[i];
      try
        // Achou nova propriedade adiciona
        ValueListEditor1.InsertRow(XmlNode.NodeName, XmlNode.NodeValue, True);
      except
        // Propriedade não é um valor simples - despreza.
        On EXMLDocError do;
        // Propriedade é nula - adiciona sting nulo
        On EVariantTypeCastError do
          ValueListEditor1.InsertRow(XmlNode.NodeName, '', True);
      end;
    end;
  finally
    ZipStream.Free;
  end;
end;

Esta função é parecida com a anterior. Iremos ler o arquivo de propriedades no segundo TXMLDocument e inserir uma linha no ValueListEditor para cada propriedade encontrada. Tratamos aqui dois tipos de exceção: EXMLDocError, que pode acontecer quando o tipo de dado não é um tipo simples, como um string ou inteiro e EVariantTypeCastError, que acontece quando o valor é nulo. Desta maneira, adicionamos as propriedades na lista, como mostra a figura a seguir.

Como podemos ver, o acesso aos dados de um arquivo OpenXML é relativamente simples, e pode ser feito usando componentes disponíveis no Delphi, mas isso não é tudo que pode ser feito: como estamos trabalhando com pacotes zip normais e arquivos XML, usando tecnologia aberta, podemos também modificar os arquivos, usando as mesmas técnicas.

Até agora, vimos como acessar um arquivo. Porém, nada impede que possamos criar um arquivo a partir de nossos dados.

Criação de um arquivo OpenXML

Para gerar um arquivo OpenXML, precisamos criar alguns arquivos que irão compor o pacote. O pacote mínimo deve conter três arquivos:

  • [Content_Types].xml
  • _rels/.rels
  • xml

Não é necessário criar uma estrutura de diretórios como a do Word, basta apenas que indiquemos no arquivo .rels a localização dos dados. À medida que  vamos adicionando novas funcionalidades, como imagens, cabeçalhos, temas e estilos, precisamos incluir novos arquivos para adicionar estas partes ao documento. Inicialmente, iremos criar um arquivo simples, para mostrar o processo de geração de um arquivo e, em seguida, mostraremos como criar um arquivo mais complexo.

Crie um novo projeto e coloque um Label, um Memo e um botão na Form. Mude a propriedade Caption do Label para Texto:, a propriedade Caption do Button para Criar e limpe a propriedade Lines do Memo.

Coloque um componente XMLDocument. No evento OnClick do botão, coloque o seguinte código:

procedure TMainFrm.Button1Click(Sender: TObject);
var
  zipFile: TZipFile;
  contentTypes: TStream;
  rels: TStream;
  doc: TStream;
begin
  zipFile := TZipFile.Create();
  try
    zipFile.Open('ArquivoSimples.docx', TZipMode.zmWrite);
    contentTypes := CriaContentTypes();
    try
      zipFile.Add(contentTypes, '[Content_Types].xml');
    finally
      contentTypes.Free;
    end;
    rels := CriaRels();
    try
      zipFile.Add(rels, '_rels\.rels');
    finally
      rels.Free;
    end;
    doc := CriaDocumento();
    try
      zipFile.Add(doc, 'word\document.xml');
    finally
      doc.Free;
    end;
  finally
    zipFile.Close();
    zipFile.Free;
  end;
end;

O programa irá criar os diversos arquivos necessários, adicionar os streams para o arquivo zip e criar um arquivo com o nome ArquivoSimples.docx. A função que cria o arquivo [Content_Types.xml] é:

function TMainFrm.CriaContentTypes(): TStream;
var
  Root: IXmlNode;
  Tipo: IXmlNode;
  XMLDoc: IXmlDocument;
begin
  Result := TMemoryStream.Create();
  XMLDoc := CriaXml;
  // Nó raiz
  Root := XMLDoc.addChild('Types',
    'http://schemas.openxmlformats.org/package/2006/content-types');
  // Definição de tipos
  Tipo := Root.addChild('Default');
  Tipo.Attributes['Extension'] := 'rels';
  Tipo.Attributes['ContentType'] :=
    'application/vnd.openxmlformats-package.relationships+xml';
  Tipo := Root.addChild('Default');
  Tipo.Attributes['Extension'] := 'xml';
  Tipo.Attributes['ContentType'] :=
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml';
  // Grava no stream de saída
  XMLDoc.SaveToStream(Result);
  Result.Position := 0;
end;

A função que cria o arquivo de relações é:

function TMainFrm.CriaRels(): TStream;
var
  Root: IXmlNode;
  Rel: IXmlNode;
  XMLDoc: IXmlDocument;
begin
  Result := TMemoryStream.Create();
  XMLDoc := CriaXml;
  // Nó raiz
  Root := XMLDoc.addChild('Relationships',
    'http://schemas.openxmlformats.org/package/2006/relationships');
  // Definição de relações
  Rel := Root.addChild('Relationship');
  Rel.Attributes['Id'] := 'rId1';
  Rel.Attributes['Type'] :=
    'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
  Rel.Attributes['Target'] := 'word/document.xml';
  // Grava no stream de saída
  XMLDoc.SaveToStream(Result);
  Result.Position := 0;
end;

O código para gravar o documento com o texto digitado no Memo é:

function TMainFrm.CriaDocumento(): TStream;
var
  Root: IXmlNode;
  XMLDoc: IXmlDocument;
begin
  Result := TMemoryStream.Create();
  XMLDoc := CriaXml;
  // Nó raiz
  Root := XMLDoc.addChild('wordDocument',
    'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
  // Grava texto
  Root.addChild('body').addChild('p').addChild('r').addChild('t').NodeValue :=
    Memo1.Text;
  // Grava no stream de saída
  XMLDoc.SaveToStream(Result);
  Result.Position := 0;
end;

Aqui apenas precisamos gravar um nó dentro do nó raiz wordDocument: ele  é o corpo do documento, que tem um parágrafo (nó p), um “run” (nó r) e o texto, que é o conteúdo do Memo. Ao compilar e executar o programa, podemos digitar um texto no Memo e clicar no botão Criar. O arquivo docx é criado com o texto digitado.

Colocando mais informações no arquivo

Uma vez que sabemos como criar nossos arquivos, podemos adicionar mais informações no que está sendo criado. Criaremos agora um exemplo que mostra as fontes disponíveis no sistema. Este documento será gerado em formato paisagem, e colocaremos um cabeçalho com três colunas e o número da página.

Crie um novo projeto e coloque um botão, um XmlDocument  Altere a propriedade Caption do botão para Criar. No evento OnClick do botão, coloque:

procedure TMainFrm.Button1Click(Sender: TObject);
var
  ZipFile: TZipFile;
  MemStream: TMemoryStream;
begin
  ZipFile := TZipFile.Create();
  try
    ZipFile.Open('ArquivoComplexo.docx', TZipMode.zmWrite);
    MemStream := TMemoryStream.Create();
    try
      CriaContentTypes(MemStream);
      ZipFile.Add(MemStream, '[Content_Types].xml');
      MemStream.Clear;
      CriaRels(MemStream);
      ZipFile.Add(MemStream, '_rels\.rels');
      MemStream.Clear;
      CriaDocumento(MemStream);
      ZipFile.Add(MemStream, 'word\document.xml');
    finally
      MemStream.Free;
    end;
  finally
    ZipFile.Close();
    ZipFile.Free;
  end;
end;

As funções CriaRels e CriaContentTypes são as mesmas da rotina anterior. A função CriaDocumento é a seguinte:

procedure TMainFrm.CriaDocumento(AStream: TStream);
var
  Root, Body, PgSz: IXMLNode;
  i: Integer;
  SectPr: IXMLNode;
  Header: IXMLNode;
begin
  LimpaXML;
  CriaCabecalho;
  // Nó raiz
  Root := XMLDocument1.addChild('w:wordDocument');
  Root.DeclareNamespace('w',
    'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
  Body := Root.addChild('w:body');
  for i := 0 to Screen.Fonts.Count - 1 do
    AdicionaFonte(Body, Screen.Fonts[i]);
  
  // Grava no stream de saída
  XMLDocument1.SaveToStream(AStream);
  AStream.Position := 0;
end;

Iremos varrer as fontes do sistema, chamando a função AdicionaFonte, que irá adicionar o texto formatado no arquivo Document.xml:

procedure TMainFrm.AdicionaFonte(Body: IXMLNode; NomeFonte: String);
var
  Fonte: IXMLNode;
  Run: IXMLNode;
  RunPr: IXMLNode;
begin
  Run := Body.addChild('w:p').addChild('w:r');
  RunPr := Run.addChild('w:rPr');
  Fonte := RunPr.addChild('w:rFonts');
  Fonte.Attributes['w:ascii'] := NomeFonte;
  Fonte.Attributes['w:hAnsi'] := NomeFonte;
  Fonte.Attributes['w:cs'] := NomeFonte;
  RunPr.addChild('w:sz').Attributes['w:val'] := 30;
  Run.addChild('w:t').NodeValue := NomeFonte;
  Run.addChild('w:tab');
  Run.addChild('w:t').NodeValue :=
    'The quick brown fox jumps over the lazy dog';
end;

Para cada fonte do sistema, adicionamos um parágrafo e, nele, um Run. O Run deve ser formatado com o elemento rPr, colocando-se como filho o elemento rFonts e o nome da fonte como valores dos atributos ascii,  hAnsi e cs. Também mudamos o tamanho da fonte adicionando o elemento sz. Em seguida, colocamos o nome da fonte como texto, adicionando o elemento tab para gerar uma tabulação e um texto de exemplo. Ao rodar o programa, vemos que a lista de fontes é gerada no documento.

O próximo passo é fazer que o documento seja colocado em paisagem. Para isso, devemos adicionar ao final do documento um elemento sectPr (propriedades da seção), que indica a formatação da seção. Coloque o seguinte código ao final de CriaDocumento, antes da linha   XMLDocument1.SaveToStream(AStream):

SectPr := Body.addChild('sectPr');
PgSz := SectPr.addChild('w:pgSz');
PgSz.Attributes['w:w'] := Round(297 / 25.4 * 1440);
PgSz.Attributes['w:h'] := Round(210 / 25.4 * 1440);
PgSz := SectPr.addChild('w:pgMar');
PgSz.Attributes['w:top'] := 1440;
PgSz.Attributes['w:bottom'] := 1440;
PgSz.Attributes['w:left'] := 720;
PgSz.Attributes['w:right'] := 720;
PgSz.Attributes['w:header'] := 720;
PgSz.Attributes['w:footer'] := 720;

Neste código adicionamos o elemento pgSz (Page size), dando os atributos w e h para a largura e altura da página. Estas medidas são em twips (1/1440 de polegada), assim fazemos a conversão do tamanho da página A4 para twips. Em seguida, colocamos o elemento pgMar (Page margins), que determina as margens da página e a posição do cabeçalho e rodapé. Ao rodarmos o programa e abrirmos o documento, vemos que ele está em paisagem.

O último passo é colocar o cabeçalho. Colocamos o cabeçalho em um arquivo separado e, desta maneira, devemos alterar todas as referências para que este novo documento seja lido.

Inicialmente, criamos uma referência para o cabeçalho na seção, como filho de sectPr. Coloque o seguinte código em CriaDocumento, após a linha SectPr := Body.AddChild(‘sectPr’):

Header := SectPr.addChild('w:headerReference');
Header.Attributes['w:type'] := 'default';
Header.Attributes['r:id'] := 'rId1';

Para usar as referências, devemos adicionar um novo namespace ao documento. Isto é feito adicionando a seguinte linha após a declaração do namespace em CriaDocumento:

Root.DeclareNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');

Criamos uma referência rId1 no documento. Devemos então criar uma função que cria este relacionamento no arquivo word\_rels\document.xml.rels:

procedure TMainFrm.CriaDocRels(AStream: TStream);
var
  Root: IXMLNode;
  Rel: IXMLNode;
begin
  LimpaXML;
  CriaCabecalho;
  // Nó raiz
  Root := XMLDocument1.addChild('Relationships',
    'http://schemas.openxmlformats.org/package/2006/relationships');
  // Definição de relações
  Rel := Root.addChild('Relationship');
  Rel.Attributes['Id'] := 'rId1';
  Rel.Attributes['Type'] :=
    'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header';
  Rel.Attributes['Target'] := 'header1.xml';
  // Grava no stream de saída
  XMLDocument1.SaveToStream(AStream);
  AStream.Position := 0;
end;

Esta função é semelhante à que cria o relacionamento do pacote. A função que cria o cabeçalho no arquivo header1.xml é:

procedure TMainFrm.CriaHeader(AStream: TStream);
var
  Root, Header, PTab: IXMLNode;
begin
  LimpaXML;
  CriaCabecalho;
  // Nó raiz
  Root := XMLDocument1.addChild('w:hdr');
  Root.DeclareNamespace('w',
    'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
  Header := Root.addChild('w:p');
  Header.addChild('w:r').addChild('w:t').NodeValue := 'Texto 1';
  PTab := Header.addChild('w:r').addChild('w:ptab');
  PTab.Attributes['w:relativeTo'] := 'margin';
  PTab.Attributes['w:alignment'] := 'center';
  PTab.Attributes['w:leader'] := 'none';
  Header.addChild('w:r').addChild('w:t').NodeValue := 'Texto 2';
  PTab := Header.addChild('w:r').addChild('w:ptab');
  PTab.Attributes['w:relativeTo'] := 'margin';
  PTab.Attributes['w:alignment'] := 'right';
  PTab.Attributes['w:leader'] := 'none';
  Header.addChild('w:fldSimple').Attributes['w:instr'] := 'PAGE \* MERGEFORMAT';
  // Grava no stream de saída
  XMLDocument1.SaveToStream(AStream);
  AStream.Position := 0;
end;

Aqui criamos o cabeçalho com um texto alinhado à esquerda, uma tabulação para alinhar o texto centralizado e outra tabulação para alinhar o número da página à direita. O número da página é dado pelo elemento fldSimple, usando-se  o atributo instr com o valor PAGE \* MERGEFORMAT. Após criar estas funções devemos colocar o código para chamá-las, ao final do evento OnClick do botão:

MemStream.Clear;
CriaDocRels(MemStream);
ZipFile.Add(MemStream, 'word\_rels\document.xml.rels');
MemStream.Clear;
CriaHeader(MemStream);
ZipFile.Add(MemStream, 'word\header1.xml');

Agora, precisamos apenas fazer uma pequena mudança em [Content_Types].xml, adicionando o elemento Override, para determinar o tipo de header1.xml. Coloque o seguinte código em CriaContentTypes, antes da linha XMLDocument1.SaveToStream(AStream):

Tipo := Root.addChild('Override');
Tipo.Attributes['PartName'] := '/word/header1.xml';
Tipo.Attributes['ContentType'] :=
  'application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml';

Com isso, nosso programa está pronto. Ao executá-lo, geramos um documento semelhante ao mostrado na figura a seguir:

Conclusões

O formato OpenXML traz grandes vantagens para quem quer processar e abrir arquivos do Office. Como este formato usa tecnologias abertas e está completamente documentado, podemos acessar, alterar ou mesmo criar arquivos do Office usando quaisquer ferramentas de desenvolvimento (ou mesmo alterando-se os arquivos manualmente), em qualquer plataforma ou linguagem.

Não são necessárias APIs proprietárias ou programas especiais, o que permite que a informação esteja disponível para qualquer um que queira acessá-la. Mostramos aqui como manipular os arquivos em Delphi, fazendo notar que utilizamos apenas componentes padrão do Delphi, utilizando apenas arquivos  zip e XML.

O código fonte para este projeto está em https://github.com/bsonnino/OpenXmlDelphiPort

 

Acabo de concluir uma série de vídeos sobre o uso de sensores em UWP, que publiquei no Channel 9. São vídeos curtos, com até 15 minutos cada. Vale a pena dar uma conferida e ver como usar os sensores disponíveis no Windows 10 (os programas funcionam tanto no desktop, em tablets, como no smartphone Windows 10):

https://channel9.msdn.com/Series/Windows-Development/Trabalhando-com-Sensores-em-UWP-Parte-1-Sensor-de-Luz – Sensor de Luz – Mostra como usar o sensor de luz para mudar a visualização conforme a luminosidade do ambiente

https://channel9.msdn.com/Series/Windows-Development/Trabalhando-com-Sensores-em-UWP-Parte-2-Bssola – Bússola – Mostra como usar a bússola para dizer ao usuário a sua orientação

https://channel9.msdn.com/Series/Windows-Development/Trabalhando-com-Sensores-em-UWP-Parte-3-Inclinmetro – Inclinômetro – Usa o inclinômetro para mover uma bola na tela conforme o usuário inclina seu dispositivo para a direita ou para a esquerda

https://channel9.msdn.com/Series/Windows-Development/Trabalhando-com-Sensores-em-UWP-Parte-4-Acelermetro – Acelerômetro – Usa o acelerômetro para fazer uma bola pular na tela quando se chacoalha o dispositivo

https://channel9.msdn.com/Series/Windows-Development/Trabalhando-com-Sensores-em-UWP-Parte-5-Geolocalizao – Geolocalização – Usa o sensor de localização para obter o local atual, consultar um serviço de meteorologia e saber se irá chover no seu local atual

Os novos dispositivos 2-em-1 Windows 10 trouxeram uma novidade: você pode usar os seus diversos sensores para aumentar a experiência do usuário em seus programas.

O Delphi trouxe a possibilidade de usar estes sensores em seus programas, tanto usando a VCL como no FireMonkey. A minha palestra na CodeRage Brasil mostra como você pode criar seus programas Delphi que utilizam a classe TSensor para melhorar a experiência do usuário. Confira em  https://www.youtube.com/watch?v=U2uKyuKwJcY

O Visual Studio 2015 introduziu novas ferramentas de debugging de UI para as aplicações WPF, Windows 8.1 ou Windows 10: a Live Visual Tree e o Live Property Explorer.

Com estas duas ferramentas, você pode examinar a interface que é gerada em tempo de execução e também ver os valores das propriedades dos elementos.

Uma coisa muito interessante é que você, além de poder examinar as propriedades, também pode alterá-las dinamicamente, avaliando como fica a interface – sem dúvida, isso é uma mão na roda para quem quer fazer experiências com a UI e ver por que algo não ficou exatamente como esperado!

Se você quer saber mais sobre isso, pode dar uma olhada em meu vídeo no Channel 9: https://channel9.msdn.com/Series/Windows-Development/Ferramentas-de-Debugging-de-UI-no-VS-2015

Ontem (9/2/2012) apresentei uma palestra na Campus Party 2012 e, ao final da palestra, mostrei uma série de recursos para aprendizado desta plataforma. Estou repetindo aqui, para que vocês possam verificar a quantidade disponível e aprender a desenvolver para Winddows Phone.

  • Windows SDK and Tools – http://create.msdn.com – Ponto de partida do AppHub, onde você pode baixar gratuitamente todas as ferramentas para desenvolver para Windows Phone
  • Windows Phone 7 Developer Portal – http://bit.ly/tIPZK3 – Portal de desenvolvimento do Windows Phone, com inúmeros recursos. Vale a pena começar com o Getting Started, onde há links para artigos, blogs e vídeos
  • Windows Phone 7 Training Kit – http://bit.ly/uKo8Fg – Link para download do training kit, contendo programas, exercícios para iniciar o aprendizado na prática
  • Livro Windows Phone 7 Development – Charles Petzold – http://charlespetzold.com/phone/index.html – Livro de 1000 páginas para download gratuito sobre desenvolvimento de Windows Phone
  • Videos Windows Phone 7 JumpStart – http://bit.ly/wp7jumpstart – Curso em vídeo com quase 20 horas sobre desenvolvimento para Windows Phone
  • Portal de Windows Phone da Microsoft em português – http://bit.ly/yxE8Po

Todos estes recursos são gratuitos, vocês não tem desculpas para não começar a aprender já!

Um dos benefícios indiretos de ser um MVP é que muitos produtores de ferramentas ou componentes cedem cópias de seus produtos para os MVPs para uso pessoal, para que possam usar, avaliar e, eventualmente, recomendar.

Eu uso muitas dessas ferramentas no meu dia a dia, e gosto muito delas. Assim, nada mais justo que fazer um post sobre elas e como elas ajudam meu dia-a-dia.

Sem dúvida, a ferramenta que eu mais uso é o ReSharper (http://www.jetbrains.com/resharper/), um add-in para o Visual Studio para aumentar sua produtividade. Ao usar o Visual Studio, ela está sempre lá, ativa, me ajudando a escrever o código.

Análise de código

Ao abrir um projeto, o ReSharper analisa seu código e começa a dar sugestões para melhorá-lo. Abrindo um projeto console, já vemos o seguinte no editor:

image

Do lado direito, é adicionada uma barra com as indicações de ajuda para seu código. O quadrado amarelo mostra que há avisos para melhoria de código. Se houver erros de compilação, o quadrado fica vermelho e, se seu código estiver ok, o quadrado fica verde. Abaixo do quadrado, estão linhas com os pontos onde há problemas. Se você passar o mouse sobre estas barras, uma tooltip irá mostrar o problema.

image

Para corrigir o erro, é muito fácil. É só clicar na barra e o editor é posicionado no local do erro. Uma lâmpada vermelha indica o erro e podemos clicar nela para ver as ações disponíveis. Esta lista pode ser selecionada com Alt-Enter. Aliás, se eu tivesse a possibilidade de escolher um único atalho de teclado para lembrar, seria este. Com o ReSharper e o Alt-Enter o seu código irá melhorar bastante!

 

image

Selecionamos a ação desejada e o ReSharper corrige o código (no caso, eliminando as cláusulas using desnecessárias). Para o erro seguinte, podemos ver que o parâmetro args de Main está ém cinza. Isto quer dizer que ele não está sendo usado na função. Quando colocamos o mouse em args aparece uma pirâmide, mostrando que há um refactoring a ser feito.

image

Aqui ele irá remover o parâmetro não usado. Além desta mudança, ele irá verificar todos os lugares onde a função é chamada e mudar a chamada a ela. Isto é ótimo quando colocamos um parâmetro e verificamos que ele não é mais necessário e queremos retirá-lo. Se fizermos isso, o código irá quebrar em todos os lugares onde a funçao é chamada. Com este refactoring, as chamadas são alteradas automaticamente. Fazendo esta mudança, nosso código fica ok, como mostra a figura abaixo.

image

O ReSharper está constantemente analisando nosso código e verifica a consistência na nomenclatura de variáveis e métodos. Você pode dizer como gosta de nomear os campos, propriedades e métodos e ele irá verificar a consistência para você.

image

Por exemplo, eu gosto de nomear meus campos privados usando CamelCase, iniciados com minúscula (sem um sublinhado antes). Assim, se eu colocar um campo com nomenclatura diferente, o ReSharper irá me avisar e se prontificar para alterar. Nada que um Alt-Enter não resolva. E ele irá mudar todas as referências para essa variável.

image

Melhoria do código

Além da consistência de nomes, ele ainda verifica a qualidade do seu código. Sim, eu sei que seu código (como o meu) é impecável, mas será que dá para melhorar? Veja na figura abaixo, uma aplicação WPF onde implementei o manipulador do evento Click do botão em codigo, com button1.Click += <tab><tab>.

image

O ReSharper mostra dois avisos e um lembrete. O primeiro aviso é sobre MainWindow. Ele indica que o tipo pai (Window) já foi declarado em outro lugar desta classe parcial e é desnecessário. Devemos lembrar que quando criamos uma janela WPF estamos criando uma classe parcial, declarada em dois lugares: no XAML e no arquivo de code behind. O XAML já declara que MainWindow é derivado de Window e podemos tirar esta declaração redundante do code behind. O segundo aviso é devido ao fato que podemos eliminar a criação do delegate ao adicionar o manipulador. O lembrete é para indicar que temos uma exceção de “não implementado”, indicação clara que devemos colocar código ali. Dois Alt-Enter e nosso código fica assim:

image

O ReSharper analisa seu código, verificando possíveis erros e melhorias. Ao digitar este código

image

Vemos na barra lateral um aviso e uma possível melhoria. O aviso indica que ToString é redundante e que devemos especificar uma cultura para a conversão. O fato de ser redundante faz com que ele possa ser eliminado. Alt-Enter e lá se vai ele. A melhoria está no for, indicando que pode ser convertido para uma expressão LINQ. Ao teclar Alt-Enter mais uma vez ficamos com o código assim:

image

Outras melhorias de código muito comuns são otimização de if e de return: quando colocamos um código como

 

if (x == 3) y = 2; else y = 5;

o ReSharper otimiza para:

y = x == 3 ? 2 : 5;

Ou então este código:

if (x == 3) return 2; else return 5;

 

Aqui teríamos duas otimizações: eliminar o else

if (x == 3) return 2; return 5;

ou então usar o operador ternário:

return x == 3 ? 2 : 5;

Refactoring

Mas isto não é tudo o que o ReSharper pode fazer por você. Seus recursos de refactoring e criação de código são fantásticos. Queremos extrair o código do manipulador de eventos para um novo método. Podemos teclar Ctrl-Shift-R (Refactor this) e o ReSharper irá mostrar os refactorings disponíveis.

image

Selecionamos o refactoring de extração de métodos e a seguinte tela é mostrada:

image

Podemos indicar os parâmetros, o nome da função e seu tipo de retorno. Ao clicar em Next, obtemos algo como

image

Criação de código

Podemos ainda inserir código a partir de templates. O ReSharper permite criar templates com código para novos arquivos ou snippets de texto.

image

 

Temos três tipos de templates:

  • Live templates – templates de snippets de código, inseridos como snippets normais do Visual Studio. Para acionar, teclamos o snippet e <tab> para inserir. Outra maneira de acionar estes templates é usando Ctrl-E-L. Na figura, criei o snippet propnp, que cria uma propriedade com backing field e chama o manipulador de PropertyChanged
  • Surround templates, que permitem envolver o código selecionado com um template, por exemplo, para envolver o código com try…catch. Eles são acionados com Ctrl-E-U.
  • File templates – que criam novos arquivos no nosso projeto, como classes, interfaces. Eles são acionados com Ctrl-Alt-Ins.

Vamos criar uma nova classe em nosso projeto. Teclamos Ctrl-Alt-Ins e criamos uma nova classe. Chamamos de NewClass. Teclamos Alt-Ins e inserimos o construtor para a classe. Nos parâmetros do construtor colocamos uma dependência:

image

Note que IDependency está em vermelho, pois não está definida. Teclamos Alt-Enter e as opções mostradas são as seguintes

image

Note que ele verificou que colocamos o nome do tipo iniciado com I, indicando uma interface e sugere a criação desta. Podemos então criar a interface a partir daqui. Após criar a interface, teclamos Alt-Enter novamente em IDependency e podemos declarar o campo para guardar a dependência:

image

A nossa interface foi criada no mesmo arquivo da classe. Podemos ir para sua declaração e teclar Alt-Enter. O ReSharper oferece para movê-la para um arquivo separado. É criado um arquivo IDependency.cs com a interface. Como vocês podem ver, é muito fácil criar e refatorar o código com o ReSharper. Ele facilita também a declaração de variáveis. Por exemplo, ao incluirmos o código

total = -1;

no construtor, vemos que total está em vermelho. Teclando Alt-Enter, temos as seguintes opções:

image

Podemos então criar um campo total no nosso código. Como vimos, temos diversas maneiras de mudar e melhorar nosso código.

Auxílio no XAML

Mas isso não é tudo. Podemos usar o ReSharper também no XAML, o que facilita bastante. Por exemplo, se criamos um Converter no código:

public class DebugConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } }

Quando queremos usá-lo no XAML devemos seguir uma série de passos: namespace, declaração nos recursos, etc. Para isso, temos algumas facilidades.

image

Teclamos Alt-Enter em DebugConverter e selecionamos Create resource in type element <Window>. Ele cria a seção Resources e cria o recurso. Colocamos o nome DebugConverter. Não colocamos o namespace. Aparece uma tooltip indicando o namespace

image

Teclamos Alt-Enter e o namespace é declarado e colocado no elemento. Se não gostarmos do nome do namespace, podemos renomeá-lo com Ctrl-R-R. Pronto, a declaração do converter, o namespace, tudo está pronto, como necessário. Se tivermos um DataContext associado ao componente e ele não tiver uma propriedade chamada Valor, podemos usar o ReSharper para criar esta propriedade na classe do DataContext.

image

Navegação no código

O ReSharper possui atalhos para navegação no código, que facilitam muito. Por exemplo, Ctrl-T navega para um tipo. Abre-se uma tela como a seguinte

image

Digitamos parte do nome e ele mostra os símbolos definidos que casam com a pesquisa. Da mesma maneira, podemos usar Shift-Alt-T para ir para um símbolo ou Ctrl-Shift-T para ir a um arquivo. A pesquisa é feita em todos os arquivos da solução. A navegação entre os membros de uma classe pode ser feita com Alt-`. Você pode ir ao membro anterior ou posterior com Alt-Up ou Alt-Down. Se você quiser, pode até navegar em código .NET descompilado. O ReSharper tem um descompilador que descompila o código e permite navegar até ele.

image

Utilidades diversas

Mas isto não é tudo o que o ReSharper oferece. Você tem um unit test runner, onde pode rodar os seus testes unitários. Você pode estar pensando quo o Visual Studio já tem um test runner, mas o que o VS oferece é exclusivo para testes com MSTest. O runner do ReSharper funciona com outros frameworks de teste, como o NUnit, QUnit ou xUnit. Uma outra funcionalidade interessante é o Explore Stack Trace. Você tem o stack trace de uma exceção, obtido através de uma ferramenta de log qualquer. Basta copiar o stack trace para a área de transferência e, no Visual Studio, usar Ctrl-E-T. O stack trace é mostrado com links para o seu código, e você pode assim verificar onde ocorreu a exceção.

image

Conclusão

Esta é só parte da funcionalidade oferecida pelo ReSharper, há muito mais para explorar, mas isto eu deixo para vocês. A ferramenta é paga, mas você pode baixar uma versão de avaliação por 30 dias, em http://www.jetbrains.com/resharper/download/index.html.

Em conclusão, eu acho que esta é uma ferramenta indispensável para quem desenvolve com o Visual Studio, e só tenho um ponto contrário: ela é realmente viciante, e uma vez que você se acostuma a usar o Resharper, é muito difícil voltar a desenvolver sem ele Alegre

No post anterior mostramos os behaviors em Blend e apresentamos os dois tipos de behaviors: Actions e Behaviors.

Ali, mostramos como criar uma Action, que atua sobre um elemento Target, fazendo uma ação sobre ele. Neste post, iremos mostrar os Behaviors e como podemos criar um behavior para ser usado (e reutilizado) no Blend.

Um behavior atua sobre um elemento associado e executa uma determinada ação, que não precisa ser sobre outro elemento, pode ser inclusive sobre ele mesmo. Aqui iremos mostrar como criar um behavior para movimentar elementos de uma listbox arrastando e soltando-os na nova posição.

Antes de iniciar o desenvolvimento do behavior, devemos saber como fazer o Drag & Drop em WPF. Para que um componente possa suportar Drag & Drop, devemos configurar sua propriedade AllowDrop para True. Em seguida, devemos manipular dois eventos: DragOver, que é ativado quando algo está sendo arrastado sobre o elemento e Drop, quando algo é solto sobre o elemento.

No evento DragOver devemos dizer qual é o cursor que deve ser apresentado quando há algo sendo arrastado sobre o elemento. Usamos o segundo parâmetro do manipulador de eventos, do tipo DragEventArgs, configurando a propriedade Effects com um código semelhante a este:

 

void ListboxDragOver(object sender, DragEventArgs e) { e.Effects = DragDropEffects.Move; }

Este código faz que o cursor de movimentação seja mostrado. No evento Drop devemos concluir a ação.

Com esta pequena introdução, podemos criar nosso behavior. Desta vez, iremos iniciar nosso projeto no Blend. Crie um novo projeto WPF, do tipo WPF Application

image

Em seguida, vá para o menu Project e selecione Add New Item, escolhendo a opção Behavior. Dê o nome de DragListItemBehavior

image

O Blend cria um código semelhante ao seguinte:

using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Interactivity; //using Microsoft.Expression.Interactivity.Core; namespace WpfApplication5 { public class DragListItemBehavior : Behavior<DependencyObject> { public DragListItemBehavior() { // Insert code required on object creation below this point. // // The line of code below sets up the relationship between the command and the function // to call. Uncomment the below line and add a reference to Microsoft.Expression.Interactions // if you choose to use the commented out version of MyFunction and MyCommand instead of // creating your own implementation. // // The documentation will provide you with an example of a simple command implementation // you can use instead of using ActionCommand and referencing the Interactions assembly. // //this.MyCommand = new ActionCommand(this.MyFunction); } protected override void OnAttached() { base.OnAttached(); // Insert code that you would want run when the Behavior is attached to an object. } protected override void OnDetaching() { base.OnDetaching(); // Insert code that you would want run when the Behavior is removed from an object. } /* public ICommand MyCommand { get; private set; } private void MyFunction() { // Insert code that defines what the behavior will do when invoked. } */ } }

 

Devemos sobrescrever dois métodos, OnAttached e OnDetaching. No método OnAttached iremos fazer o processo de inicialização do nosso behavior: conectar manipuladores, configurar propriedades, etc. No método OnDetaching fazemos a limpeza do processo.

Agora que já temos nosso projeto criado, podemos editar seu código no Visual Studio. No painel Projects, clique com o botão direito do mouse sobre a solução e selecione Edit in Visual Studio. O projeto é aberto no Visual Studio.

image

Abra o arquivo do behavior para editá-lo. Queremos que o behavior só atue sobre ListBoxes. Para isto iremos alterar seu tipo:

public class DragListItemBehavior : Behavior<ListBox>

Em seguida, devemos colocar, no método OnAttached, o código de inicialização, onde iremos configurar as propriedades e conectar os manipuladores:

protected override void OnAttached() { base.OnAttached(); AssociatedObject.AllowDrop = true; AssociatedObject.PreviewMouseLeftButtonDown += ListboxPreviewMouseLeftButtonDown; AssociatedObject.DragOver += ListboxDragOver; AssociatedObject.Drop += ListboxDrop; }

Note que conectamos os eventos DragOver e Drop, mas também usamos o evento PreviewMouseLeftButtonDown. Isto é necessário por que iremos iniciar a ação de Drag & Drop quando o usuário clica num item da Listbox. O behavior tem uma propriedade AssociatedObject, que é o objeto associado ao behavior. Como definimos que esse é um Behavior<ListBox>, o AssociatedObject é uma Listbox.

No método OnDetaching iremos fazer a limpeza:

protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewMouseLeftButtonDown -= ListboxPreviewMouseLeftButtonDown; AssociatedObject.DragOver -= ListboxDragOver; AssociatedObject.Drop -= ListboxDrop; }

Podemos então criar o código para fazer o Drag & Drop nos manipuladores dos eventos. No evento PreviewMouseLeftButtonDown obtemos o item que foi clicado e iniciamos o processo de Drag & Drop:

private object dragItem; private void ListboxPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var parent = (ListBox)sender; dragItem = GetDataFromListBox(parent, e.GetPosition(parent)); if (dragItem != null) DragDrop.DoDragDrop(parent, dragItem, DragDropEffects.Move); }

O método GetDataFromListBox procura na árvore de elementos e retorna o item que foi clicado:

private object GetDataFromListBox(ListBox source, Point point) { var element = source.InputHitTest(point) as UIElement; if (element == null) return null; object data = DependencyProperty.UnsetValue; while (data == DependencyProperty.UnsetValue) { data = source.ItemContainerGenerator.ItemFromContainer(element); if (data == DependencyProperty.UnsetValue) element = VisualTreeHelper.GetParent(element) as UIElement; if (element == source) return null; } return data != DependencyProperty.UnsetValue ? data : null; }

No evento DragOver iremos configurar o cursor. Se o Drag & Drop é da própria Listbox, deixamos arrastar, senão configuramos o cursor para None:

private void ListboxDragOver(object sender, DragEventArgs e) { e.Effects = e.Source == sender ? DragDropEffects.Move : DragDropEffects.None; }

Finalmente, no evento Drop fazemos a movimentação do item arrastado:

private void ListboxDrop(object sender, DragEventArgs e) { var parent = (ListBox)sender; var dropItem = GetDataFromListBox(parent, e.GetPosition(parent)); var dragItemIndex = parent.Items.IndexOf(dragItem); var dropItemIndex = dropItem == null ? parent.Items.Count - 1 : parent.Items.IndexOf(dropItem); if (dragItemIndex != dropItemIndex) { parent.Items.RemoveAt(dragItemIndex); parent.Items.Insert(dropItemIndex, dragItem); } }

Verificamos onde o item arrastado foi solto e, se for numa posição diferente da atual, movemos o item para uma nova posição. Nosso behavior está pronto. Compile o projeto e abra o Blend para recarregá-lo. Nosso behavior está lá e pode ser usado.

 

image

Coloque uma Listbox na janela e coloque alguns itens nela. Arraste um DragListItemBehavior para a Listbox. Execute o programa. Você pode ver que podemos arrastar os itens da Listbox para movê-los de lugar. Este behavior é reutilizável e pode ser usado em qualquer Listbox, mesmo por aqueles que não tem conhecimento de C#. Como você pode ver, um behavior pode modificar o comportamento de outros componentes, estendendo-os e trazendo novos comportamentos, sem que seja necessário criar um controle derivado.

No último post mostramos como animar transições usando o Blend e Visual States. Uma parte importante naquele mecanismo é o uso de behaviors. Com behaviors, podemos executar ações bastante complexas, apenas arrastando um behavior para um componente da janela. Isto, além de poderoso, traz outros benefícios:

  • É reutilizável. Podemos incluir o mesmo behavior em diversas situações
  • Permite que os designers possam incluir funcionalidade no design sem necessidade de código

Temos dois tipos de behaviors:

  • Actions – executam uma ação associado a um evento. Por exemplo, você pode criar uma Action que, associada a uma TextBox, emite um “click” a cada tecla pressionada, ou outra action que faz um controle crescer quando o mouse está sobre ele
  • Full behaviors – neste caso, há um comportamento mais complexo, não necessariamente associado a um único trigger. Um exemplo disso é o MouseDragElementBehavior, que permite movimentar um elemento, arrastando-o com o mouse

No Blend, encontramos os dois tipos de behaviors, com o final do nome indicando o tipo (ex. CallMethodAction ou FluidMoveBehavior).

image

Além dos pré-instalados, você pode encontrar outros behaviors, na galeria do Blend, em http://gallery.expression.microsoft.com (quando verifiquei, existiam 114 behaviors disponíveis lá).

Os behaviors estão associados a um objeto e podem ter propriedades adicionais, além do trigger que os ativa. Por exemplo, o GoToStateAction tem como propriedades adicionais o componente destino e o estado a ser ativado, além da propriedade booleana UseTransitions, para usar as transições para mudar de um estado a outro.

image

Além de configurar as propriedades da Action, você pode especificar condições para que ela possa ser ativada. Por exemplo, usando o projeto do post anterior, podemos usar uma checkbox para permitir a ativação da transição. Para isso, basta clicar no botão “+” de Condition List, clicar no botão de propriedades avançadas da condição, criar um Data Binding com a checkbox e fazer um binding com a propriedade IsChecked. Desta maneira, a animação só será ativada se a checkbox estiver checada.

image

Além das Actions padrão, podemos criar Actions personalizadas para fazer o que queremos. No post anterior, usamos as Actions padrão, mas precisamos criar os Visual States. Além disso, temos um outro inconveniente: se quisermos fazer as animações para cima ou para baixo, temos que criar novos Visual States. Assim, criaremos nossa Action para fazer o que queremos, sem a necessidade de configuração especial.

No Visual Studio, crie um novo projeto WPF. Vamos adicionar agora a nossa Action. O Visual Studio, por padrão, não tem o template para criar uma Action. Poderíamos fazer isso usando o Blend. Ao abrir o projeto no Blend e selecionar a aba Project, você pode dar um clique com o botão direito do mouse, selecionar Add New Item e adicionar uma Action ao projeto.

image 

Outra maneira de fazer isso é usar os templates online do Visual Studio. No Solution Explorer do Visual Studio, clique com o botão direito do mouse no projeto e selecione Add New Item. Na tela, vá para Online Templates e tecle action na caixa de pesquisa. Selecione o template C# Action Template for WPF e dê o nome de TransitionControlAction.

image

O template adiciona uma referência a System.Windows.Interactivity e gera uma classe semelhante a este código:

using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Interactivity; namespace WpfApplication4 { // // If you want your Action to target elements other than its parent, extend your class // from TargetedTriggerAction instead of from TriggerAction // public class TransitionControlAction : TriggerAction<DependencyObject> { public TransitionControlAction() { // Insert code required on object creation below this point. } protected override void Invoke(object o) { // Insert code that defines what the Action will do when triggered/invoked. } } }

Temos dois tipos de actions: TriggerAction e TargetedTriggerAction. TriggerAction é uma action que não se refere a outro controle. Por exemplo, se quisermos criar uma action que executa o Notepad quando acontece um evento, usaríamos uma TriggerAction. TargetedTriggerAction refere-se a um outro elemento, chamado de Target. Este elemento é uma propriedade da action e pode ser acessado no Blend.

Nós iremos criar uma TargetedTriggerAction. Portanto devemos mudar o código para derivar de TargetedTriggerAction, como mostra o comentário no início da classe. Esta action irá executar o mesmo código que criamos no primeiro post para fazer a animação. Devemos alterar também o tipo de objeto em que ela atua. Usaremos o FrameworkElement, pois este elemento tem as propriedades ActualWidth e ActualHeight.

public class TransitionControlAction : TargetedTriggerAction<FrameworkElement>

Inicialmente, iremos criar a enumeração para as posições e duas DependencyProperties com o tipo de animação desejada e a duração, de modo que ela possa ser selecionada no Blend.

public enum TipoAnimacao { Direita, Esquerda, Cima, Baixo } [Category("Common Properties")] public TipoAnimacao Tipo { get { return (TipoAnimacao)GetValue(TipoProperty); } set { SetValue(TipoProperty, value); } } public static readonly DependencyProperty TipoProperty = DependencyProperty.Register("Tipo", typeof(TipoAnimacao), typeof(TransitionControlAction)); [Category("Common Properties")] public TimeSpan Duracao { get { return (TimeSpan)GetValue(DuracaoProperty); } set { SetValue(DuracaoProperty, value); } } public static readonly DependencyProperty DuracaoProperty = DependencyProperty.Register("Duracao", typeof(TimeSpan), typeof(TransitionControlAction), new UIPropertyMetadata(TimeSpan.FromMilliseconds(500)));

Note que colocamos o atributo Category nas propriedades Tipo e Duração para que elas apareçam junto com o grupo Common Properties. Ao compilarmos o projeto e abri-lo no Blend, vemos que nossa action aparece na aba Assets.

image

Ao arrastarmos uma TransitionControlAction para um botão, as propriedades aparecem no editor de propriedades:

image

Mas nossa action ainda não faz nada. Para fazer algo, devemos sobrescrever o método Invoke da action, colocando ali o código que deve ser executado. Usaremos o código que escrevemos na primeira postagem, modificando um pouco para usar o controle Target:

private void AnimaControle(FrameworkElement controle, TimeSpan duração, TipoAnimacao tipo) { double xFinal = 0; double yFinal = 0; if (tipo == TipoAnimacao.Esquerda) xFinal = -controle.ActualWidth; else if (tipo == TipoAnimacao.Direita) xFinal = controle.ActualWidth; else if (tipo == TipoAnimacao.Cima) yFinal = -controle.ActualHeight; else if (tipo == TipoAnimacao.Baixo) yFinal = controle.ActualHeight; // cria a transformação e atribui ao controle var translate = new TranslateTransform(0, 0); controle.RenderTransform = translate; // cria a animação e anima-a if (tipo == TipoAnimacao.Esquerda || tipo == TipoAnimacao.Direita) { var da = new DoubleAnimation(0, xFinal, new Duration(duração)); translate.BeginAnimation(TranslateTransform.XProperty, da); } else { var da = new DoubleAnimation(0, yFinal, new Duration(duração)); translate.BeginAnimation(TranslateTransform.YProperty, da); } }

Finalmente, basta chamar o método AnimaControle a partir do método Invoke:

protected override void Invoke(object o) { AnimaControle(Target, Duracao, Tipo); }

Com isto, nosso behavior está completo. Podemos abrir o projeto no Blend, arrastar a action para o botão, configurar o objeto Target apontando para a grid e executar o projeto. Ao clicar o botão, a grid faz uma transição animada para a direção selecionada. Note que não precisamos fazer nada. Basta arrastar a action para o botão, configurar o elemento Target e as propriedades. A action está pronta para ser executada.

image

Conclusão

Foi um longo trajeto até aqui. Vimos quatro maneiras diferentes de animar a transição, começamos com código e terminamos usando o mesmo código. No meio do caminho vimos diversos conceitos: partir de um código fixo para um código mais refatorado e flexível, usar componentes para transição, eliminar o code behind usando MVVM, usando o Nuget, Templates Implícitos, usar Visual States para criar animações sem usar código e, finalmente, behaviors, para criar ações que podem ser usadas por designers, de maneira flexível e sem usar código. Espero que tenham gostado!

Nos dois últimos posts, mostrei como animar uma transição usando código. O primeiro post mostrou como animar a transição usando code behind, criando uma animação em código. O segundo post mostrou o uso de componentes para facilitar estas transições. Embora o uso de componentes seja uma boa alternativa a criar as animações em código, ainda tem algumas desvantagens:

  • É necessário incluir uma referência à dll do componente ou incluir o código do componente no projeto.
  • Está sujeito a bugs – embora muitas pessoas usem estes componentes nada impede que eles tenham bugs. O fato de serem open source e terem seu código disponível pode minimizar isto, mas nem sempre é fácil debugar este código.
  • Pode ficar defasado. Com novas versões do Silverlight e WPF, um componente que não é mantido há muito tempo pode não funcionar nas novas versões

Assim, vamos ver aqui uma nova opção para animar as transições, que não usam código. “Como assim, não usam código?”, você deve estar se perguntando. Sim, o WPF 4 (ou o 3.5, com o WPF Toolkit) e o Silverlight introduziram um novo recurso, que dispensa o uso de código em C# ou VB para animar as transições: os Visual States. Com Visual States, você define qual é o estado de seu controle em diversas situações e transiciona entre eles sem usar código. Tudo é feito com o XAML.

Para este projeto, não usaremos o VisualStudio. A criação de Visual States é muito mais fácil usando o Blend.

Abra o Blend e crie um novo projeto WPF.

Neste projeto, inclua uma linha na Grid principal, na parte de baixo da janela, com 40 pixels de altura e, na segunda linha, coloque um botão com a propriedade Content configurada para o texto Esconde. Na primeira linha da grid, coloque outra grid, com fundo vermelho.

No painel do projeto, escolha a aba States. Ela deve estar vazia.

image

Clique no primeiro botão da barra superior da aba para adicionar um novo grupo de estados. Mude o nome do grupo para EstadosGrid. Clique no segundo botão deste grupo para adicionar um novo estado. Mude seu nome para Aparente. Adicione um novo estado e mude o nome dele para Escondido.

Note que o tempo padrão de transição (mostrado na frente do texto Default Transition) é de 0s

image

Mude este tempo para 1. Mude também o Easing Function para CubicInOut, clicando no segundo botão

image

Olhando a figura acima, você pode notar que estamos em modo de gravação, “gravando” o estado Escondido. Quando selecionamos um estado, todas as alterações que fazemos no layout são atribuídos a este estado. Assim, podemos mudar a aparência de nossos controles apenas mudando de um estado para outro. O estado Aparente é o nosso estado padrão. No estado Escondido iremos esconder a grid. A transição é feita automaticamente quando mudarmos de um estado para outro.

Selecione a grid e mude a propriedade RenderTransform X para –625, a propriedade Opacity para 0 e a propriedade Visibility para Collapsed. Desta maneira, a grid irá para a esquerda, ao mesmo tempo que fica cada vez mais transparente. Nossos estados estão prontos. Poderíamos mudar de estado usando o code behind, colocando o seguinte código no event Click do botão:

private void button_Click(object sender, System.Windows.RoutedEventArgs e) { VisualStateManager.GoToElementState(LayoutRoot, "Escondido", true); }

Mas assim estaríamos na mesma situação do post anterior, onde temos code behind. Além disso, eu prometi que não iríamos colocar código. E promessa é dívida!

O Blend tem um recurso muito interessante para executar ações sem a necessidade de código: Behaviors. Behaviors são ações personalizadas, usadas diretamente nos componentes, sem que seja preciso escrever código para executá-las (na realidade, você precisa escrever código para escrever um behavior mas, uma vez criado, basta arrastá-lo para um componente para ser usado). O Blend venm com diversos behaviors pré definidos. Para usá-los, basta ir na janela do projeto, na aba Assets e selecionar a opção Behaviors.

image

Usaremos o behavior GoToStateAction. Atribuímos este behavior a um componente, dizemos qual é o evento que o ativa e qual é o novo estado que se deve ativar quando o evento foi acionado. Selecione o GoToStateAction e arraste-o para o botão. Note que um GoToStateAction é adicionado ao botão, no inspetor de objetos.

 

image

No editor de propriedades, iremos configurar a ação.

image

O Trigger já está configurado: queremos ativar a action quando o evento Click do botão foi acionado. Falta apenas configurar o estado que queremos selecionar quando o botão for clicado. Para isto, basta configurar a propriedade StateName para Escondido.

image

Nossa aplicação está pronta. Ao executá-la e clicar no botão, ocorre a transição animada, que movimenta a grid para fora. E tudo isso sem uma única linha de código!

Vamos fazer agora uma pequena mudança para dar um pouco mais de funcionalidade à nossa aplicação. Mude a visualização do editor para Split, clicando no terceiro botão de mudança de visualização.

image

Com isso, podemos alterar o código XAML diretamente e alterar o nosso botão. Queremos que ele não seja um botão normal, e sim um ToggleButton. Para isso, altere o componente no XAML, mudando o seu tipo de Button para ToggleButton:

<ToggleButton x:Name="button" Content="Esconde" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="65" Height="25"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:GoToStateAction StateName="Escondido"/> </i:EventTrigger> </i:Interaction.Triggers> </ToggleButton>

O ToggleButton pode estar checado ou não. Faremos que quando ele está checado, mostre o estado Escondido e, quando não está checado, mostre o estado Aparente.

Para isso, devemos mudar o evento que ativa o estado Escondido. No inspetor de objetos, selecione o GoToStateAction e mude a propriedade EventName para Checked. Na paleta do projeto, selecione o GoToStateAction e arraste um segundo GoToStateAction para o botão. Configure a propriedade EventName para Unchecked e a propriedade StateName para Aparente. Execute o programa.

Agora temos uma animação para esconder a grid quando o botão é checado e outra para mostrar a grid, quando o botão não está checado. Fácil, não?

Aqui pudemos ver a quantidade de recursos que temos à disposição para criar estados e ativá-los, tudo feito visualmente, sem precisar escrever código. Ainda não terminamos nossa jornada, ainda temos maneiras de animar transições, mas isto é assunto para um outro post. Até lá!

No post anterior vimos como animar uma transição usando código. Como falei, não acho aquela a melhor solução, pois obriga a usar code behind, o que não é de fácil manutenção. Poderíámos refatorar o código, criando uma classe para a animação e usá-la. Isto traria um pouco mais de separação, mas ainda teríamos de usar code behind.

Nesta segunda parte, usaremos um enfoque diferente: o uso de componentes prontos. Podemos usar diversos componentes, como o Kevin Bag-O-Tricks (https://github.com/thinkpixellab/bot), FluidKit (http://fluidkit.com), Silverlight Toolkit (http://silverlight.codeplex.com – só para Silverlight), o Transitionals (http://transitionals.codeplex.com).

Usaremos aqui o Transitionals, para WPF. Se quisermos fazer animações em Silverlight, devemos escolher outro dos componentes acima.

Seu uso é muito simples: após baixar o componente e adicionar uma referência ao projeto para a dll Transitionals.dll, devemos adicionar um componente TransitionElement no local onde queremos a animação, configurar a animação e colocar um conteúdo para o componente. Ao mudar o conteúdo, ocorre a transição selecionada.

Vamos então fazer o nosso projeto de animação. Crie um novo projeto WPF e adicione uma referência a Transitionals.dll. Em seguida, coloque um TransitionElement na grid principal:

<Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="40" /> </Grid.RowDefinitions> <transc:TransitionElement x:Name="TransitionBox"> <transc:TransitionElement.Transition> <transt:TranslateTransition StartPoint="1,0" EndPoint="0,0" Duration="0:0:1"/> </transc:TransitionElement.Transition> </transc:TransitionElement> <Button Width="65" Grid.Row="1" Content="Esconde" Margin="5" Click="Button_Click" /> </Grid>

Devemos definir os namespaces para o TransitionElement e para a TranslateTransition na definição da janela:

<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:transc="clr-namespace:Transitionals.Controls;assembly=Transitionals" xmlns:transt="clr-namespace:Transitionals.Transitions;assembly=Transitionals" Title="MainWindow" Height="350" Width="525">

Em seguida, é só colocar um conteúdo no TransitionElement:

<transc:TransitionElement x:Name="TransitionBox"> <transc:TransitionElement.Transition> <transt:TranslateTransition StartPoint="1,0" EndPoint="0,0" Duration="0:0:1"/> </transc:TransitionElement.Transition> <Grid Background="Red" /> </transc:TransitionElement>

O código do botão muda o conteúdo do TrasitionElement e, com isso ativa a transição:

private void Button_Click(object sender, RoutedEventArgs e) { TransitionBox.Content = new Grid() {Background = Brushes.Blue}; }

Desta maneira, o código fica muito mais fácil, só precisamos mudar o conteúdo do elemento. Além disso, o componente Transitionals tem muitos tipos de transições, e podemos configurá-las de diversas maneiras. Por exemplo, o TranslateTrasition tem as propriedades StartPoint e EndPoint, dizendo onde começa e onde termina a transição. Para fazer da esquerda para direita, StartPoint deve ser –1,0 e EndPoint, 0,0. De cima para baixo, StartPoint deve ser 0,-1 e EndPoint, 0,0. Podemos inclusive fazer uma transição diagonal usando os pontos 1,1 e 0,0.

Eliminando o Code Behind

Uma das coisas que podem ser melhoradas aqui é a eliminação do code behind, usando o padrão de projeto MVVM. Para isso, usaremos o framework MVVM Light, que pode ser obtido gratuitamente em http://galasoft.ch, ou ainda instalado diretamente no projeto usando o Nuget, uma extensão para o Visual Studio que facilita o download e instalação de bibliotecas e ferramentas no Visual Studio. Se você ainda não baixou o Nuget, vá imediatamente para http://nuget.org e baixe-o. Vale a pena!

Uma vez instalado o Nuget, clique com o botão direito em References, no Solution Explorer e  selecione “Manage Nuget Packages”. Tecle “mvvm” na caixa de pesquisa e instale o pacote MVVM Light:

image

Isto instala o MVVM Light em nosso projeto, adiciona as referências necessárias e cria uma pasta ViewModel, com dois arquivos, MainViewModel.cs e ViewModelLocator.cs. MainViewModel.cs é o ViewModel referente à janela princiapl e ViewModelLocator é um localizador de ViewModel, referente à View.

Em MainViewModel.cs, colocamos uma propriedade, do tipo ViewModelBase, que conterá os ViewModel referente à View do conteúdo atual:

private ViewModelBase conteudo; public ViewModelBase Conteudo { get { return conteudo; } set { conteudo = value; RaisePropertyChanged("Conteudo"); } }

Criaremos em seguida dois ViewModels, que serão referentes à nossas Views. Os dois ViewModels são muito semelhantes e têm apenas uma propriedade:

public class ViewModelA : ViewModelBase { private string texto; public string Texto { get { return texto; } set { texto = value; RaisePropertyChanged("Texto"); } } } public class ViewModelB : ViewModelBase { private string texto; public string Texto { get { return texto; } set { texto = value; RaisePropertyChanged("Texto"); } } }

Em seguida, crie no Solution Explorer um diretório chamado View e coloque lá dois UserControls, com apenas uma Grid com um TextBlock:

<UserControl x:Class="WpfApplication2.View.ViewA" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid Background="Red"> <TextBlock Text="{Binding Texto}" FontSize="36" /> </Grid> </UserControl>

Para eliminar o code behind, precisamos fazer um data binding da propriedade Content do TransitionElement com a propriedade conteúdo do ViewModel:

<transc:TransitionElement x:Name="TransitionBox" Content="{Binding Conteudo}"> <transc:TransitionElement.Transition> <transt:TranslateTransition StartPoint="1,0" EndPoint="0,0" Duration="0:0:1"/> </transc:TransitionElement.Transition> </transc:TransitionElement>

e eliminar o clique do botão, substituindo-o por um Command:

<Button Width="65" Grid.Row="1" Content="Esconde" Margin="5" Command="{Binding EscondeCommand}" />

O command é definido no MainViewModel:

private ICommand escondeCommand; public ICommand EscondeCommand { get { return escondeCommand ?? (escondeCommand = new RelayCommand(MudaConteudo)); } } private void MudaConteudo() { Conteudo = conteudo is ViewModelA ? (ViewModelBase)new ViewModelB() { Texto = "ViewModel B"} : (ViewModelBase)new ViewModelA() {Texto = " ViewModel A"}; }

Finalmente, devemos definir o DataContext para a View principal:

<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:transc="clr-namespace:Transitionals.Controls;assembly=Transitionals" xmlns:transt="clr-namespace:Transitionals.Transitions;assembly=Transitionals" Title="MainWindow" Height="350" Width="525" DataContext="{Binding Source={StaticResource Locator}, Path=Main}">

Ao executar o programa, temos algo como mostrado na figura abaixo:

image

A view não é mostrada, apenas o nome da classe do conteúdo. Isso era de se esperar, pois a propriedade Conteúdo é do tipo ViewModelBase e sua representação é o método ToString(). Poderíamos ter colocado a View como sendo o conteúdo, mas isto vai contra o padrão MVVM: o ViewModel não deve conhecer a View. Uma solução para mostrar a View a partir do ViewModel é usar um recurso disponível no WPF ou no Silverlight 5: Data Templates implícitos. Em um Data Template implicito, não explicitamos a Key, apenas dizemos qual é o DataType e, com isso o WPF/Silverlight renderezam este DataTemplate toda vez que um conteúdo for do tipo referente a ele.

Definimos então os DataTemplates na seção Resources da janela:

<Window.Resources> <DataTemplate DataType="{x:Type ViewModel:ViewModelA}" > <View:ViewA /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModel:ViewModelB}" > <View:ViewB /> </DataTemplate> </Window.Resources>

Agora, ao executar, temos a transição entre as duas views, sem o uso de code behind.

 

image

Conclusões

Usando componentes de animação de transições, temos a possibilidade de fazer transições muito sofisticadas, de maneira muito simples bastando mudar o conteúdo do componente.

Em seguida, vimos como tirar o código do code behind, colocando-o em um ViewModel, o que facilita na manutenção e permite maior testabilidade do código. Como bonus, vimos como usar implicit templates para ligar uma view a um viewmodel sem usar código. Este recurso está disponível apenas no WPF e no Silverlight 5. Embora você possa achar que não vale a pena (eliminamos apenas uma linha de código e incluímos dois viemodels, duas views, um novo componente MVVM, entre outros), minha intenção aqui foi mostrar como usar outros recursos, como a introdução do MVVM ao invés do code behind e como usar novos recursos, como o DataTemplate implícito. Numa aplicação maior, que requer maiores cuidados, estas mudanças se justificam plenamente.

Mas estas não são as únicas maneiras de se fazer transições de controles. Nos próximos artigos, veremos quais são as outras maneiras. Até lá!