Animando transições em WPF/Silverlight

Do Bruno Sonnino

Maio 2012

JJ125506.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Quando eu vi uma pergunta de um usuário nos forums do MSDN sobre animação de um user control para dar o efeito de saída ao clicar um botão, eu pensei que esta seria uma excelente oportunidade de criar um artigo mostrando diversas maneiras de fazê-lo.

Apesar de colocar as alternativas aqui, algumas podem não ser tão boas: a primeira opção, onde eu ponho animação no code behind, apesar de ser fácil de implementar, tem algumas desvantagens:

  • O código da animação é posto no code behind, uma técnica que eu não gosto. Eu nao acho que o padrão MVVM deva ter zero code behind, mas eu não gosto de por código lá, a menos que seja indispensável e estritamente relacionado com a view.
  • Não é design-friendly – se o designer quer mudar a animação, o código deve ser alterado.
  • Não é portável – se você quer criar a mesma animação em outros lugares, o código deve ser copiado ou refatorado para ser acessado de outros lugares.
  • Não é fácil de manipular – tudo é feito em código. Quaisquer mudanças, como a duração da animação deve ser alterado em código.

Temos o seguinte problema: temos um controle na janela e queremos escondê-lo quando o botão é clicado, usando uma animação, como vemos abaixo:

JJ125506.1D66FFB6467A943D41CA801AA68AD724(pt-br,MSDN.10).png

Para fazer isso, devemos criar uma animação para o controle, do tipo RenderTransform e animá-la. Podemos fazer tudo em código desta maneira: no clique do botão, criamos a transformação, adicionamos ao controle e animamos ela:

private void Button_Click(object sender, RoutedEventArgs e)
{ 
  // create the transformation and add it to the control
  var translate = new TranslateTransform(0, 0);     
  gridLogin.RenderTransform = translate;     
  // create the animation and start it    
  var da = new DoubleAnimation(0, -ActualWidth, new Duration(TimeSpan.FromMilliseconds(1000)));
  translate.BeginAnimation(TranslateTransform.XProperty, da);
}

Este código tem muitos inconvenientes:

  • Ele só pode ser aplicado ao nosso controle. Para adicioná-lo a outro controle, devemos copiar o código.
  • A duração da animação é 1 segundo – a duração é fixa.
  • Somente anima da direita para a esquerda.

Nesta primeira fase, refatoraremos o código para deixá-lo mais genérico:

public enum AnimationType
{
    Right,
    Left,
    Up,
    Down
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    AnimateControl(gridLogin, TimeSpan.FromMilliseconds(1000), AnimationType.Left);
}

private void AnimateControl(UIElement control, TimeSpan duration, AnimationType type)
{
    double xEnd = 0;
    double yEnd = 0;
    if (type == AnimationType.Left)
        xEnd = -ActualWidth;
    else if (tipo == AnimationType.Right)
        xEnd = ActualWidth;
    else if (tipo == AnimationType.Up)
        yEnd = -ActualHeight;
    else if (tipo == AnimationType.Down)
        yEnd = ActualHeight;
    // create the transformation and add it to the control
    var translate = new TranslateTransform(0, 0);
    control.RenderTransform = translate;
    // create the animation and start it
    if (tipo == AnimationType.Left || tipo == AnimationType.Right)
    {
        var da = new DoubleAnimation(0, xEnd, new Duration(duration));
        translate.BeginAnimation(TranslateTransform.XProperty, da);
    }
    else
    {
        var da = new DoubleAnimation(0, yEnd, new Duration(duration));
        translate.BeginAnimation(TranslateTransform.YProperty, da);
    }
}

Apesar do código ser maior, ele é mais genérico e permite ser reutilizado. Podemos usar qualquer controle, configurar a deuração e a direção da animação. Ainda não é o idela, mas é melhor que o código anterior, não reutilizável. A seguir veremos outras maneiras de fazer isso.

Na seção 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://www.github.com/thinkpixellab/bot), 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:

JJ125506.0C0333411DBFC49D1CF3A044BD4D6AAD(pt-br,MSDN.10).png

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:

JJ125506.40FE0A442F91263B7C37CFD33D7A91E8(pt-br,MSDN.10).png

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.

JJ125506.C2CF94562191C0684E0D8D2FB4472BA9(pt-br,MSDN.10).png

Como pudemos ver, 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. A seguir, veremos quais são as outras maneiras.

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.

JJ125506.50BA7896648FED4442ED5522AC569746(pt-br,MSDN.10).png

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

JJ125506.6302AB8336D24347A864D3F9B04DB600(pt-br,MSDN.10).png

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

JJ125506.613749A3245CD25ABEF166CBB3EF242F(pt-br,MSDN.10).png

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.

JJ125506.C0D4C8AC2F6D0910C180C751E40FD0A8(pt-br,MSDN.10).png

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.

JJ125506.0F3ACE340964AE9D34A0E1FB9E401643(pt-br,MSDN.10).png

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

JJ125506.A39C9DEBB15BDE5A8EE5B22E70035A5B(pt-br,MSDN.10).png

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.

JJ125506.028F0F22FBB799A38BEB8F65F922F218(pt-br,MSDN.10).png

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.

JJ125506.9C32A68ECD68681BF774517C2D89BB21(pt-br,MSDN.10).png

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, e veremos a seguir.

Na última seção 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).

JJ125506.AE213B4FA58F3FBB45D5A9473AE1FD30(pt-br,MSDN.10).png

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.

JJ125506.65C5C8194B94FDCB288869A911E65E93(pt-br,MSDN.10).png

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.

JJ125506.7DDF4A01E1A4F6D833368546AD8C1120(pt-br,MSDN.10).png

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.

JJ125506.CFF49B20F832C3644C7FB6A9CE38F21B(pt-br,MSDN.10).png

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.

JJ125506.C442866220DD1D8BE386E01A638DDC1B(pt-br,MSDN.10).png

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.

JJ125506.4B14334DE5D6A9B4350D722C07E05150(pt-br,MSDN.10).png

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

JJ125506.DB8A5CCA0A769C59CBC0ED1E2F97F496(pt-br,MSDN.10).png

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.

JJ125506.4C1853391CA3C3A07860F67ECD1CC37D(pt-br,MSDN.10).png

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!


Mostrar: