WPF MVVM Property Change Animation

前端 未结 4 1006
盖世英雄少女心
盖世英雄少女心 2020-12-13 22:30

I am looking for a clean way to start an animation that will have dynamic values. Basically I want to do an animation where an element changes width based on the data of an

相关标签:
4条回答
  • 2020-12-13 23:10

    Since properties modified by animation cannot be set outside the animation 'context', I came up with a code solution since I could not do the same in XAML effectively.

    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;
    }
    
    0 讨论(0)
  • 2020-12-13 23:16

    You could explore using Attached Properties to hook up the necessary logic to the Storyboard/Animation that you desire.

    This won't necessarily stop you from having to write code, but it will keep it separated from the view and allow it to be re-used across multiple views.

    0 讨论(0)
  • 2020-12-13 23:17

    Actually you want to bind the DoubleAnimation.ToProperty to the ViewModel property and animate actual control. The problem is animation should be continued when ToProperty changed. My solution encapsulate all this logic to a MarkupExtenstion which wraps a 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;
        }
    }
    

    You can do the same with other animation classes to animate other types.

    0 讨论(0)
  • 2020-12-13 23:25

    Here is the solution I ended up with. To do the Animation based on data in my ViewModel I used a DataTrigger. Below is my Style for the control.

    <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>
    
    0 讨论(0)
提交回复
热议问题