Unable to apply Storyboard to multiple controls

Craig Muckleston 161 Reputation points
2020-10-21T11:30:27.263+00:00

I have a Storyboard in my resource file that I want to apply to multiple controls. I get my resource and loop through the Storyboards children and apply it to my controls. However, only 1 control get the animation when it runs. I can run it on either one, but not bot

Storyboard flashingStoryboard = Application.Current.Resources["FlashingStoryboard"] as Storyboard;
foreach (var child in flashingStoryboard.Children)
{
Storyboard.SetTarget(child, this.flashingControl1.FindChildByName("FlashingBorder"));
Storyboard.SetTarget(child, this.flashingControl2.FindChildByName("FlashingBorder"));
}
flashingStoryboard.Begin();

Universal Windows Platform (UWP)
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,762 questions
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,321 Reputation points
    2020-10-21T20:20:44.13+00:00

    Hi,
    You can animate property and bind this property to many Controls and all showed animations will be synchonously.

    XAML MainWindow:

    <Window x:Class="WpfApp1.Window004"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp004"  
            mc:Ignorable="d"  
            Title="Window004" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <StackPanel>  
        <Button Content="Add UserControl" Command="{Binding Cmd}" CommandParameter="AddUserControl"  Margin="5" Width="200"/>  
        <ItemsControl ItemsSource="{Binding UserControls}"/>  
      </StackPanel>  
    </Window>  
    

    Classes:

    using System;  
    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Animation;  
    using WpfControlLibrary1;  
      
    namespace WpfApp004  
    {  
      public class ViewModel : INotifyPropertyChanged  
      {  
        public ViewModel()  
        {  
          Animating = new AnimatedProperties();  
          Animating.AnimationCompleted += (sender, e) => OnPropertyChanged(nameof(Cmd));  
        }  
        public ObservableCollection<UserControl> UserControls { get; } = new ObservableCollection<UserControl>();  
        public AnimatedProperties Animating { get; }  
        public ICommand Cmd { get => new RelayCommand(CmdExec, CanCmdExec); }  
        private void CmdExec(Object parameter)  
        {  
          switch (parameter.ToString())  
          {  
            case "StartAnimation":  
              Animating.StartAnimation();  
              break;  
            case "AddUserControl":  
              UserControls.Add(new Window004UC1());  
              break;  
            default:  
              break;  
          }  
        }  
        private bool CanCmdExec(object obj) => Animating.ca == null;  
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      
      public class AnimatedProperties : DependencyObject  
      {  
        public AnimatedProperties() => sb.Completed += (sender, e) => { ca = null; AnimationCompleted?.Invoke(this, new EventArgs()); };  
      
        public static readonly DependencyProperty AnimatedColorProperty =  
          DependencyProperty.RegisterAttached("AnimatedColor",  
            typeof(Color), typeof(AnimatedProperties), new UIPropertyMetadata(Colors.Red));  
      
        public static Color GetAnimatedColor(DependencyObject d) => (Color)(d.GetValue(AnimatedColorProperty));  
        public static void SetAnimatedColor(DependencyObject d, Color value) => d.SetValue(AnimatedColorProperty, value);  
      
        public ColorAnimation ca;  
        private Storyboard sb = new Storyboard();  
        public event EventHandler AnimationCompleted;  
      
        public void StartAnimation()  
        {  
          if (ca == null)  
          {  
            ca = new ColorAnimation()  
            {  
              From = Colors.Yellow,  
              To = Colors.Blue,  
              Duration = new Duration(TimeSpan.FromSeconds(2.0)),  
              RepeatBehavior = new RepeatBehavior(1),  
              AutoReverse = true  
            };  
            sb.Children.Clear();  
            sb.Children.Add(ca);  
            Storyboard.SetTarget(ca, this);  
            Storyboard.SetTargetProperty(ca, new PropertyPath(AnimatedProperties.AnimatedColorProperty));  
            sb.Begin();  
          }  
        }  
      }  
      
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
    }  
    

    XAML UserControl:

    <UserControl x:Class="WpfControlLibrary1.Window004UC1"  
                 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"   
                 xmlns:local="clr-namespace:WpfControlLibrary004"  
                 mc:Ignorable="d"   
                 d:DesignHeight="450" d:DesignWidth="800">  
      <UserControl.Resources>  
        <local:ColorConverter x:Key="conv"/>  
      </UserControl.Resources>  
      <StackPanel>  
        <Button Content="Start Animation" Command="{Binding Cmd}" CommandParameter="StartAnimation"  Margin="5" Width="100"/>  
        <Rectangle Fill="{Binding Animating.AnimatedColor, Converter={StaticResource conv}}" Height="50" Width="200" Margin="10"/>  
      </StackPanel>  
    </UserControl>  
    

    Converter Color to Brush:

      public class ColorConverter : IValueConverter  
      {  
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => new SolidColorBrush((Color)value);  
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();  
      }  
    

    Result:

    34048-x.gif

    1 person found this answer helpful.
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,321 Reputation points
    2020-10-21T14:24:30.77+00:00

    Hi;
    see my other answer (in other thread). You can animate property in ViewModel and bind this property to many Controls and all showed animations will be synchonously.


  2. Nico Zhu (Shanghai Wicresoft Co,.Ltd.) 12,866 Reputation points
    2020-10-21T14:34:46.893+00:00

    Hello, CraigMuckleston

    Welcome to Microsoft Q&A!

    Unable to apply Storyboard to multiple controls

    The problem is that the Storyboard will be covered when you call SetTarget method at last time. so it can only apply last one control. For your requirement, we suggest you place the Storyboard in the UserControl like the following.

    <UserControl  
        x:Class="TestAnimation.FlashControl"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"  
        xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:local="using:TestAnimation"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        d:DesignHeight="300"  
        d:DesignWidth="400"  
        mc:Ignorable="d">  
      
        <Grid>  
            <Ellipse  
                x:Name="FlashingBorder"  
                Width="50"  
                Height="50"  
                Fill="Red"  
                Opacity="0"  
                RenderTransformOrigin="0.5,0.5"  
                StrokeThickness="5" />  
            <Interactivity:Interaction.Behaviors>  
                <Interactions:DataTriggerBehavior Binding="{x:Bind IsStartAnimation, Mode=OneWay}" Value="True">  
                    <Interactions:GoToStateAction StateName="Blink" />  
                </Interactions:DataTriggerBehavior>  
      
                <Interactions:DataTriggerBehavior Binding="{x:Bind IsStartAnimation, Mode=OneWay}" Value="False">  
                    <Interactions:GoToStateAction StateName="Standard" />  
                </Interactions:DataTriggerBehavior>  
            </Interactivity:Interaction.Behaviors>  
            <VisualStateManager.VisualStateGroups>  
                <VisualStateGroup x:Name="Normal">  
                    <VisualState x:Name="Blink">  
      
                        <Storyboard  
                            x:Name="FlashingStoryboard"  
                            AutoReverse="True"  
                            RepeatBehavior="Forever">  
                            <DoubleAnimation  
                                Storyboard.TargetName="FlashingBorder"  
                                Storyboard.TargetProperty="(UIElement.Opacity)"  
                                To="1"  
                                Duration="00:00:00.3000000" />  
                        </Storyboard>  
                    </VisualState>  
      
                    <VisualState x:Name="Standard" />  
                </VisualStateGroup>  
            </VisualStateManager.VisualStateGroups>  
        </Grid>  
    </UserControl>  
    

    Code Bebehind

    public sealed partial class FlashControl : UserControl  
    {  
        public FlashControl()  
        {  
            this.InitializeComponent();  
              
        }  
        public bool IsStartAnimation  
        {  
            get { return (bool)GetValue(IsStartAnimationProperty); }  
            set { SetValue(IsStartAnimationProperty, value); }  
        }  
      
        // Using a DependencyProperty as the backing store for IsStartAnimation.  This enables animation, styling, binding, etc...  
        public static readonly DependencyProperty IsStartAnimationProperty =  
            DependencyProperty.Register("IsStartAnimation", typeof(bool), typeof(FlashControl), new PropertyMetadata(null));  
    }  
    

    Usage

    <local:FlashControl x:Name="flashingControl1" Height="100" Width="100" IsStartAnimation="{Binding ShowAnimation, Source={StaticResource Setting}}"/>  
    
    private void Button_Click(object sender, RoutedEventArgs e)  
    {  
      
        ((Setting)Application.Current.Resources["Setting"]).ShowAnimation = true;  
    }  
    
     
    

    For global setting class please refer this case reply.

    Thank you.
    Nico Zhu


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.