Rui Gaspar

Those who cannot remember the past are condemned to repeat it

Como animar o GridLength das linhas e colunas de uma Grid

February 19th, 2011

Há dias precisei de fazer uma animação em XAML que alterasse o tamanho de duas colunas de uma grid.

Para fazer esta animação poderiam-se colocar as duas colunas com o Width a Auto e alterar o tamanho dos componentes que estão em cada coluna, o que iria forçar a alteração do tamanho de cada coluna e assim fazer a animação pretendida. Mas esta abordagem tem a desvantagem de não se podermos usar, por exemplo, {0.8*} , já que não podemos usar um GridLength num width ou height de um FrameworkElement.

Para resolver este problema podemos alterar directamente o GridLength utilizando uma AnimationTimeline. Em baixo encontra-se uma a class GridLengthAnimation. Para usar esta class apenas teremos de definir a propriedade To que irá indicar qual será o novo tamanho da linha ou coluna. Também se pode definir a propriedade From, mas esta é opcional e caso não se defina é usado o valor actual como ponto de partida.

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace GridLengthAnimationTest
{
    internal class GridLengthAnimation : AnimationTimeline
    {
        static GridLengthAnimation() { }

        public GridLength? From
        {
            get
            {
                return (GridLength?)GetValue(FromProperty);
            }
            set
            {
                SetValue(FromProperty, value);
            }
        }

        public static readonly DependencyProperty FromProperty =
            DependencyProperty.Register("From", typeof(GridLength?), typeof(GridLengthAnimation), new PropertyMetadata(null));

        public GridLength To
        {
            get
            {
                return (GridLength)GetValue(ToProperty);
            }
            set
            {
                SetValue(ToProperty, value);
            }
        }

        public static readonly DependencyProperty ToProperty =
            DependencyProperty.Register("To", typeof(GridLength), typeof(GridLengthAnimation));

        public override Type TargetPropertyType
        {
            get
            {
                return typeof(GridLength);
            }
        }

        protected override Freezable CreateInstanceCore()
        {
            return new GridLengthAnimation();
        }

        public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
        {
            double toVal = To.Value;
            double fromVal;

            if (this.From == null)
            {
                fromVal = defaultOriginValue != null && defaultOriginValue is GridLength ? ((GridLength)defaultOriginValue).Value : 0;
            }
            else
            {
                fromVal = this.From.GetValueOrDefault().Value;
            }

            if (animationClock.CurrentProgress != null)
            {
                if (fromVal > toVal)
                {
                    return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, To.IsStar ? GridUnitType.Star : GridUnitType.Pixel);
                }
                else
                {
                    return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, To.IsStar ? GridUnitType.Star : GridUnitType.Pixel);
                }
            }

            return null;
        }
    }
}

A seguir encontra-se um exemplo de como utilizar a animação em XAML. Este exemplo possui uma tabela com duas colunas e dois botões que permitem expandir ou restaurar o tamanho da primeira coluna.

<Window x:Class="GridLengthAnimationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Local="clr-namespace:GridLengthAnimationTest"
Title="GridLengthAnimation" Height="350" Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="FirstColumnDefinition" Width="150"/>
                <ColumnDefinition x:Name="SecondColumnDefinition" Width="50"/>
            </Grid.ColumnDefinitions>
            <Border Background="Red" Grid.Column="0"/>
            <Border Background="Blue" Grid.Column="1"/>
        </Grid>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="1" >
            <Button Content="Expandir" Margin="10" >
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <BeginStoryboard>
                            <Storyboard>
                                <Local:GridLengthAnimation Duration="0:0:1" Storyboard.TargetName="FirstColumnDefinition" Storyboard.TargetProperty="Width" To="450" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
            <Button Content="Restaurar" Margin="10">
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <BeginStoryboard>
                            <Storyboard>
                                <Local:GridLengthAnimation Duration="0:0:1" Storyboard.TargetName="FirstColumnDefinition" Storyboard.TargetProperty="Width" To="150" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
        </StackPanel>
    </Grid>
</Window>

Se quiserem usar esta animação em code-behind, poderão fazer também de uma  forma muito simples:

FirstColumnDefinition.BeginAnimation(
        ColumnDefinition.WidthProperty,
        new GridLengthAnimation()
    {
        To = new GridLength(0.8, GridUnitType.Star),
        Duration = TimeSpan.FromSeconds(1)
    });