marko devcic

Software Engineer
  • github:
    deva666
  • email:
    madevcic {at} gmail.com
Animating WPF ContentControl Part 3

Animating WPF ContentControl Part 3

Posted on 08/16/2014

For the final part of this series ( part 1, part 2 ), we're going to build one more animated content control. This one can be used for displaying various information messages to the user. It will be designed so whenever a message is queued, it slides in to the center of display, waits there for a second (or how long we specify) and then slides out. If more messages are queued up, they will all be presented, one by one in order they were queued.

I used this in one project where I needed to present the user with some information, e.g. if user deleted some file, confirmation string would slide in and if the user did something that else that needed confirmation new message would queue up and be displayed after previous one.

So again we'll be extending the ContentControl class and adding 2 new Storyboards that will be doing all the animation.

Complete code of the new class:

publicclass ScrollingContentControl : ContentControl
    {
        bool _processing = false;

        DispatcherTimer _centerTimer;

        Storyboard _scrollIn;
        Storyboard _scrollOut;

        Queue<string> _requests = new Queue<string>(); //replace with ConcurrentQueue<string> if multiple threads will be adding to InfoContent property and lock _processing variablepublicdouble CenterTime
        {
            get { return (double)GetValue(CenterTimeProperty); }
            set { SetValue(CenterTimeProperty, value); }
        }

        publicstaticreadonly DependencyProperty CenterTimeProperty =
            DependencyProperty.Register("CenterTime", typeof(double), typeof(ScrollingContentControl), new PropertyMetadata(2.0d));

        privatestaticvoid CenterTimeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (((double)e.NewValue) < 0)
                thrownew ArgumentException("Value must be 0 or greater");
        }

        publicstring InfoContent
        {
            get { return (string)GetValue(InfoContentProperty); }
            set { SetValue(InfoContentProperty, value); }
        }

        publicstaticreadonly DependencyProperty InfoContentProperty =
            DependencyProperty.Register("InfoContent", typeof(string), typeof(ScrollingContentControl), new PropertyMetadata(string.Empty, InfoContentCallback));

        privatestaticvoid InfoContentCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollingContentControl control = (ScrollingContentControl)d;
            if (!string.IsNullOrEmpty(e.NewValue asstring))
                control.EnqueueNewInfo(e.NewValue asstring);
        }

        public ScrollingContentControl()
        {
            InitStoryboards();
            this.RenderTransform = new TranslateTransform();
            this.Visibility = System.Windows.Visibility.Collapsed;
        }

        privatevoid InitStoryboards()
        {
            Contract.Ensures(_scrollIn != null, "Failed to init scroll in animation");
            Contract.Ensures(_scrollOut != null, "Failed to init scroll out animation");
            _scrollIn = FindResource("scrollIn") as Storyboard;
            _scrollOut = FindResource("scrollOut") as Storyboard;
        }

        privatevoid EnqueueNewInfo(string info)
        {
            _requests.Enqueue(info);
            if (!_processing)
                HandleQueue();
        }

        privatevoid HandleQueue()
        {
            _processing = true;

            if (_centerTimer == null)
                InitTimer();

            string info = _requests.Peek();
            this.Content = info;

            var scrollInClone = _scrollIn.Clone();
            scrollInClone.Completed += (s, e) =>
            {
                _centerTimer.Tick += TimerTick;
                _centerTimer.Start();
            };

            scrollInClone.Begin(this);
            this.Visibility = System.Windows.Visibility.Visible;
        }

        privatevoid TimerTick(object sender, EventArgs args)
        {
            _centerTimer.Tick -= TimerTick;
            _centerTimer.Stop();
            var scrollOutClone = _scrollOut.Clone();
            scrollOutClone.Completed += (snd, ear) =>
            {
                this.Visibility = System.Windows.Visibility.Collapsed;
                if (_requests.Count > 0)
                    _requests.Dequeue();
                if (_requests.Count == 0)
                    _processing = false;
                else
                {
                    CheckTimeInterval(); //Check if CenterTime has changed
                    HandleQueue();
                }
            };
            scrollOutClone.Begin(this);
        }

        privatevoid InitTimer()
        {
            _centerTimer = new DispatcherTimer();
            _centerTimer.Interval = TimeSpan.FromSeconds(CenterTime);
        }

        privatevoid CheckTimeInterval()
        {
            if (_centerTimer == null)
                return;
            if (_centerTimer.Interval != TimeSpan.FromSeconds(CenterTime))
                _centerTimer.Interval = TimeSpan.FromSeconds(CenterTime);
        }
    }

There are 2 dependency properties, CenterTime sets for how long the content will be presented to the user and the InfoContent is the string that will be presented.

Let's add two new storyboards and the Template for the new control.
I used a label for my ContentControl template, you can use whatever suits your needs.

<StyleTargetType="controls:ScrollingContentControl"><SetterProperty="HorizontalContentAlignment"Value="Stretch"/><SetterProperty="VerticalContentAlignment"Value="Stretch"/><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="controls:ScrollingContentControl"><Gridx:Name="root"RenderTransformOrigin="0.5,0.5"><LabelContent="{TemplateBinding Content}"FontSize="20"FontFamily="Tahoma"></Label></Grid></ControlTemplate></Setter.Value></Setter></Style><Storyboardx:Key="scrollIn"><DoubleAnimationUsingKeyFramesBeginTime="00:00:00"Storyboard.TargetProperty="(UIElement.Opacity)"><SplineDoubleKeyFrameKeyTime="00:00:00"Value="0"/><SplineDoubleKeyFrameKeyTime="00:00:01.5"Value="1"/></DoubleAnimationUsingKeyFrames><DoubleAnimationUsingKeyFramesBeginTime="00:00:00"Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"><SplineDoubleKeyFrameKeyTime="00:00:00"Value="600"/><EasingDoubleKeyFrameKeyTime="00:00:01.5"Value="0"><EasingDoubleKeyFrame.EasingFunction><CubicEaseEasingMode="EaseOut"/></EasingDoubleKeyFrame.EasingFunction></EasingDoubleKeyFrame></DoubleAnimationUsingKeyFrames></Storyboard><Storyboardx:Key="scrollOut"><DoubleAnimationUsingKeyFramesBeginTime="00:00:00"Storyboard.TargetProperty="(UIElement.Opacity)"><SplineDoubleKeyFrameKeyTime="00:00:00"Value="1"/><SplineDoubleKeyFrameKeyTime="00:00:01.5"Value="0"/></DoubleAnimationUsingKeyFrames><DoubleAnimationUsingKeyFramesBeginTime="00:00:00"Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"><SplineDoubleKeyFrameKeyTime="00:00:00"Value="0"/><SplineDoubleKeyFrameKeyTime="00:00:01.5"Value="-600"/></DoubleAnimationUsingKeyFrames></Storyboard>

Now we can add our new control and a button that will queue up the messages to the view.

<ButtonContent="Start scrolling"Command="{Binding StartScrolling}"Margin="20"/><controls:ScrollingContentControlInfoContent="{Binding InfoContent}"HorizontalAlignment="Center"CenterTime="1"/>

The view model will have a string property InfoContent that the control binds to.
Start scrolling command will queue up 5 dummy messages that will slide across the screen, one by one.

private ICommand _startScrolling;

public ICommand StartScrolling
        {
            get
            {
                return _startScrolling == null ? _startScrolling = new DelegateCommand(Scroll) : _startScrolling;
            }
        }

privatevoid Scroll()
        {
            string[] infos = newstring[] { "Info1", "Info2", "Info3", "Info4", "Info5" };
            foreach (var info in infos)
            {
                InfoContent = info;
            }

        }

privatestring _infoContent;

publicstring InfoContent
        {
            get
            {
                return _infoContent;
            }
            set
            {
                _infoContent = value;
                OnPropertyChanged("InfoContent");
            }
        }

Download the complete Visual studio project here.

Browse the full source code on Bitbucket.