Анимация изменения свойства MVFM WPF - PullRequest
9 голосов
/ 30 октября 2009

Я ищу чистый способ запустить анимацию, которая будет иметь динамические значения. В основном я хочу сделать анимацию, где элемент меняет ширину на основе данных другого элемента. Скажем, у меня есть TextBlock, который является свойством Text Binding. Когда это свойство изменится, я хочу, чтобы визуальный элемент произнес «Прямоугольник» ради нас, чтобы сделать DoubleAnimation, изменяя ширину от предыдущего значения до нового значения.

Я стараюсь не показывать код, если это возможно. Я посмотрел на DataTriggers, но они, кажется, требуют, чтобы вы знали, какое значение будет, например, Enum. В моем случае это просто изменение значения, которое должно вызывать раскадровку, и анимация должна начинаться с текущего (предыдущего) значения и красиво переходить к новому значению.

Любые идеи. Может быть, я просто пропустил собственность.

Ответы [ 4 ]

15 голосов
/ 04 ноября 2009

Вот решение, которое я выбрал. Для создания анимации на основе данных в моей ViewModel я использовал DataTrigger. Ниже мой стиль для контроля.

<Style TargetType="Grid" x:Key="DetailRotation" >
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=AnimationState}" Value="New">
            <DataTrigger.EnterActions>
                <StopStoryboard BeginStoryboardName="EndAnimation" />
                <BeginStoryboard Name="NewAnimation">
                    <Storyboard>
                        <ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,30,0,0" To="0,0,0,0" Duration="0:0:1" />
                        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" />
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>

            </DataTrigger.ExitActions>

        </DataTrigger>
        <DataTrigger Binding="{Binding Path=AnimationState}" Value="End">
            <DataTrigger.EnterActions>
                <StopStoryboard BeginStoryboardName="NewAnimation" />
                <BeginStoryboard Name="EndAnimation">
                    <Storyboard>
                        <ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,0,0,0" To="0,-20,0,0" Duration="0:0:1"/>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:1" />
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.EnterActions>
        </DataTrigger>

    </Style.Triggers>
</Style>
1 голос
/ 30 октября 2009

Вы можете исследовать, используя Прикрепленные свойства , чтобы подключить необходимую логику к раскадровке / анимации, которые вы хотите.

Это не обязательно помешает вам писать код, но будет держать его отдельно от представления и позволит повторно использовать его в нескольких представлениях.

0 голосов
/ 18 декабря 2014

На самом деле вы хотите связать DoubleAnimation.ToProperty со свойством ViewModel и анимировать фактический элемент управления. Проблема в том, что анимация должна быть продолжена при изменении ToProperty. Мое решение инкапсулирует всю эту логику в MarkupExtenstion, который включает Binding.

public class AnimateBindingExtension : MarkupExtension {
    static DependencyPropertyDescriptor dpd =
        DependencyPropertyDescriptor.FromProperty(DoubleAnimation.ToProperty, 
            typeof(DoubleAnimation));

    public AnimateBindingExtension(PropertyPath path) {
        Path = path;
    }

    public bool ValidatesOnExceptions { get; set; }
    public IValueConverter Converter { get; set; }
    public object ConverterParamter { get; set; }
    public string ElementName { get; set; }
    public RelativeSource RelativeSource { get; set; }
    public object Source { get; set; }
    public bool ValidatesOnDataErrors { get; set; }
    [ConstructorArgument("path")]
    public PropertyPath Path { get; set; }
    public object TargetNullValue { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider) {
        var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

        if (valueProvider == null) {
            throw new Exception("could not get IProviderValueTarget service.");
        }

        var bindingTarget = valueProvider.TargetObject as FrameworkElement;
        var bindingProperty = valueProvider.TargetProperty as DependencyProperty;

        if (bindingProperty == null || bindingTarget == null) {
            throw new Exception();
        }

        var binding = new Binding {
            Path = Path,
            Converter = Converter,
            ConverterParameter = ConverterParamter,
            ValidatesOnDataErrors = ValidatesOnDataErrors,
            ValidatesOnExceptions = ValidatesOnExceptions,
            TargetNullValue = TargetNullValue
        };

        if (ElementName != null) binding.ElementName = ElementName;
        else if (RelativeSource != null) binding.RelativeSource = RelativeSource;
        else if (Source != null) binding.Source = Source;

        // you can add a Duration property to this class and use it here
        var anim = new DoubleAnimation {
            Duration = new Duration(TimeSpan.FromSeconds(0.1)),
            AccelerationRatio = 0.2,
            DecelerationRatio = 0.8
        };
        // this can be a new subclass of DoubleAnimation that 
        // overrides ToProperty metadata and add a property 
        // change callback
        dpd.AddValueChanged(anim, (s, e) => bindingTarget.BeginAnimation(bindingProperty, anim));

        BindingOperations.SetBinding(anim, DoubleAnimation.ToProperty, binding);
        // this is because we need to catch the DataContext so add animation object 
        // to the visual tree by adding it to target object's resources.
        bindingTarget.Resources[bindingProperty.Name] = anim;
        // animation will set the value
        return DependencyProperty.UnsetValue;
    }
}

Вы можете сделать то же самое с другими классами анимации для анимации других типов.

0 голосов
/ 11 января 2012

Поскольку свойства, измененные с помощью анимации, не могут быть установлены вне «контекста» анимации, я придумал решение для кода, поскольку не смог сделать то же самое в XAML.

private void UserControl_IsVisibleChanged(object sender, 
    DependencyPropertyChangedEventArgs e)
{
    if (this.Visibility == Visibility.Visible)
    {
        DoubleAnimation fadeIn = new DoubleAnimation();
        fadeIn.From = 1d;
        fadeIn.To = 1d;
        fadeIn.Duration = new Duration(new TimeSpan(0, 0, 0));

        DoubleAnimation fade = new DoubleAnimation();
        fade.From = 1d;
        fade.To = 0d;
        fade.BeginTime = TimeSpan.FromSeconds(5);
        fade.Duration = new Duration(new TimeSpan(0, 0, 1));

        NameScope.SetNameScope(this, new NameScope());
        this.RegisterName(this.Name, this);

        Storyboard.SetTargetName(fadeIn, this.Name);
        Storyboard.SetTargetProperty(fadeIn, new PropertyPath
            (UIElement.OpacityProperty));

        Storyboard.SetTargetName(fade, this.Name);
        Storyboard.SetTargetProperty(fade, new PropertyPath
            (UIElement.OpacityProperty));

        Storyboard sb = new Storyboard();
        sb.Children.Add(fadeIn);
        sb.Children.Add(fade);

        sb.Completed += new EventHandler(sb_Completed);
        sb.Begin(this);
    }
}

void sb_Completed(object sender, EventArgs e)
{
    this.Visibility = Visibility.Hidden;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...