An animated, Reuters-style ticker in WPF

Aug 19
2010

I just need to get this out onto the blog for posterity, as it were.

This is a class that derives from StackPanel and Implements a Reuters style ticker behaviour with its children. Anything you add to the Children collection will be ticked from left to right.

The only issue with it so far is that adding something to the Children collection makes it initially show up on the left (intialised to coordinates (0,0)). This could probably be solved by adding something like an AnimatedChildren collection, and only adding to the Children collection when the animation is initiated?

This is hugely untidy at the moment, but it does work after a fashion. I don’t in any way claim this is the right way to do this or that any of this is best practice :)

Update

Ok, so I only realised a short while ago that you don’t strictly need the Storyboard class to initiate animations when you’re not using XAML. I haven’t yet investigated removing them from this code, but I’ll probably get round to that when I have a Windows VM finally set up.

public class MyPanel : Canvas
{
    private Timer _timer = new Timer(500);
    public MyPanel()
    {
        LayoutUpdated += new EventHandler(MyPanel_LayoutUpdated);
        _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
        _timer.Start();
    }
 
    void  _timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Dispatcher.BeginInvoke(new Func<object>(RunNextAnimation));
    }
 
    object RunNextAnimation()
    {
        if (storyBoardQueue.Count == 0) return null;
 
        //check the last animated child
        //if there is space, begin the animation of the next child.
        if (lastAnimated == null)
        {
            var sb = storyBoardQueue.Dequeue();
            var child = childQueue.Dequeue();
            animatingChildQueue.Enqueue(child);
            animatingStoryBoardQueue.Enqueue(sb);
            sb.Begin(child, true);
            lastAnimated = child;
        }
        else
        {
            var x = lastAnimated.RenderTransform.Value.OffsetX;
            if (x + lastAnimated.ActualWidth < ActualWidth - 10)
            {
                var sb = storyBoardQueue.Dequeue();
                var child = childQueue.Dequeue();
                animatingChildQueue.Enqueue(child);
                animatingStoryBoardQueue.Enqueue(sb);
                sb.Begin(child, true);
                lastAnimated = child;
            }
        }
        return null;
    }
 
    void MyPanel_LayoutUpdated(object sender, EventArgs e)
    {
        foreach(var child in Children)
        {
            if (animatedChildren.Contains(child as FrameworkElement)) continue;
            AnimateChild(child as FrameworkElement);
        }
    }
 
    //just to keep track of things we've created storyboards/translations for
    private List<FrameworkElement> animatedChildren = new List<FrameworkElement>();
 
    private Queue<Storyboard> storyBoardQueue = new Queue<Storyboard>();
    private Queue<FrameworkElement> childQueue = new Queue<FrameworkElement>();
 
    private FrameworkElement lastAnimated;
 
    private Queue<Storyboard> animatingStoryBoardQueue = new Queue<Storyboard>();
    private Queue<FrameworkElement> animatingChildQueue = new Queue<FrameworkElement>();
 
    private void AnimateChild(FrameworkElement child)
    {
        if (child == null) return;
 
        NameScope.SetNameScope(child, new NameScope());
 
        var anim = new DoubleAnimation
                   {
                       From = ActualWidth,
                       To = -100,
                       By = -5,
                       Duration = new Duration(TimeSpan.FromSeconds(ActualWidth/40)),
                   };
 
        var transform = new TranslateTransform();
        child.RegisterName("translate", transform);
 
        child.RenderTransform = transform;
        Storyboard.SetTargetName(anim, "translate");
        Storyboard.SetTargetProperty(anim, new PropertyPath(TranslateTransform.XProperty));
 
        var myStoryBoard = new Storyboard();
        myStoryBoard.Children.Add(anim);
        myStoryBoard.Completed += myStoryBoard_Completed;
        storyBoardQueue.Enqueue(myStoryBoard);
        childQueue.Enqueue(child);
 
        animatedChildren.Add(child);
    }
 
    void  myStoryBoard_Completed(object sender, EventArgs e)
    {
        storyBoardQueue.Enqueue(animatingStoryBoardQueue.Dequeue());
        childQueue.Enqueue(animatingChildQueue.Dequeue());
    }
}

Visit Our Friends!

A few highly recommended friends...

Pages List

General info about this blog...