Click here to Skip to main content
15,879,613 members
Articles / Desktop Programming / WPF

Making a Simple Marquee Text Control, Drip Animation and Roll Animation in WPF

Rate me:
Please Sign up or sign in to vote.
4.88/5 (28 votes)
30 Dec 2009CPOL12 min read 116.7K   33   12
Techniques of making a simple marquee text control, Drip animation and Roll animation

Introduction

An animation consists of a series of frames. To perform the animation, these frames are shown one after the other at a regular time interval. Here I will describe how I came up with marquee text control, Drip animation, and Roll animation. Marquee text control creates scrolling text in a certain direction. Marquee text is created using property based animation of WPF. Drip animation and Roll animation are created using manual animation approach (dispatcher timer).

WPF Property Based Animation System

Animation involves changing some visible characteristic of a user interface over some time period. WPF has a new dependency property-based animation system. Every property based animation acts on a single dependency property and changes its value according to timeline. According to MSDN, for a property to have animation capabilities, it must meet the following three requirements:

  • It must be a dependency property.
  • It must belong to a class that inherits from DependencyObject and implements the IAnimatableinterface.
  • There must be a compatible animation type available.

Why Property Based Animation of WPF Does Not Work Sometimes?

However, there are some animations, where by only changing dependency property you cannot achieve the desired animation. For example, using property based animation you cannot add or remove any new geometry object/UI object. In those cases, you have to make animation using dispatcher timer, which runs in UI thread. In this article, only Marquee text is created using property based animation of WPF and for the rest of the animations, Dispatcher timer has been used.

Manual Animation Approach

In manual animation approach, we have to set up a timer and a callback function that is periodically called back based on the interval value (frequency) of the timer. Inside the callback function, we will update the UI based on the current UI state and elapsed time. When we are done with the animation, we will stop the timer and/or remove the event handler. WPF has DispatcherTimer class that can be used for implementing manual animation. We can attach an event handler to its Tick event and can set the DispatcherTimer’s frequency of firing tick event by setting its Interval property. Disadvantages of manual animation: Manual Animation approach is not in sync with the monitor’s vertical refresh rate nor are they in sync with the WPF rendering engine. it doesn't get the best possible performance.

Why I Have Used Dispatcher Timer Instead of System.windows.forms/System.Timers.timer for Manual Animation?

System.windows.forms timer/ System.Timers.timer runs on a different thread than the user interface (UI) thread whereas Dispatcher Timer runs in the user interface (UI) thread. So from DispatcherTimer, you can access WPF UI Controls but from System.windows.forms timer, you cannot without cross thread operation. According to MSDN, DispatcherTimer is a timer that is integrated into the dispatcher queue, which is processed at a specified interval of time and at a specified priority. Dispatcher timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the dispatcher queue like other UI operations. The execution of tick event of dispatcherTimer is dependent on the other UI jobs and their priorities in the dispatcher queue. System.windows.forms/System.Timers.timer are guaranteed to execute exactly when the time interval occurs but you cannot directly access UI using this thread. You have to perform cross thread operation for UI update.

Marquee Text Control

marq1.gif

A WPF marquee is a scrolling piece of text displayed either horizontally across or vertically down your container. Here I will describe how I came up to make a marquee text control using Canvas and TextBlock. This marquee text control scrolls text left to right, right to left, top to bottom and bottom to top. The following three techniques are used to achieve marquee animation:

  • Negative Positioning in canvas: If we set negative value to any of the positioning property of Canvas: Left, Right, Top and Bottom for an UIelement, Canvas draws that UIelement outside of Canvas area.
  • ClipToBounds property of canvas: If you set the value of the ClipToBounds property to true, Canvas will clip its children when they go outside of Canvas.
  • Changing Position of UIelement continuously: By changing continuously any of the canvas positioning property Left, right, Top, Bottom, we can get moving text.

Here marquee text control is a user control that consists of textblock and canvas. The XAML of marquee text user control is in the following:

XML
<UserControl x:Class="SomeAnimations.MarqueeText"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Canvas  ClipToBounds="True" Name="canMain" >
        <TextBlock  Name="tbmarquee"> </TextBlock>
    </Canvas>
</UserControl>

How is Left to right Marquee Text Implemented in this Control?

Here a canvas is used as the container of marquee text. To move the text outside of canvas, negative left property of canvas is used. However, if negative left property of canvas is applied to any uielement like textblock, it will go outside of its parent container but will still remain visible outside of the parent container. To make marquee text invisible outside of canvas CliptoBound property with true value is used. Then using double animation left property of canvas for that element is being changed continuously to get moving text. The whole thing is done using the following code:

C#
private void LeftToRightMarquee()
{
    double height = canMain.ActualHeight - tbmarquee.ActualHeight;
    tbmarquee.Margin = new Thickness(0, height / 2, 0, 0);
    DoubleAnimation doubleAnimation = new DoubleAnimation();
    doubleAnimation.From = -tbmarquee.ActualWidth;
    doubleAnimation.To = canMain.ActualWidth;
    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(_marqueeTimeInSeconds));
    tbmarquee.BeginAnimation(Canvas.LeftProperty, doubleAnimation);
}

How is Right to Left Marquee Text Implemented in this Control?

As we know, a canvas is used as the container of marquee text. Here marquee text is moved outside of canvas area equal to marquee text width and it is moved equal to canvas width using double animation. To move the text outside of canvas in the right direction, negative right property of canvas is used. However, if someone uses negative right property of canvas, it will go outside of its parent container but will remain visible outside of the parent container. To make marquee text invisible outside of canvas, CliptoBound property is used. Then using double animation, right property of canvas for that element is being changed continuously to get moving text. The whole thing is done using the following code:

C#
private void RightToLeftMarquee()
{
    double height = canMain.ActualHeight - tbmarquee.ActualHeight;
    tbmarquee.Margin = new Thickness(0, height / 2, 0, 0);
    DoubleAnimation doubleAnimation = new DoubleAnimation();
    doubleAnimation.From = -tbmarquee.ActualWidth;
    doubleAnimation.To = canMain.ActualWidth;
    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(_marqueeTimeInSeconds));
    tbmarquee.BeginAnimation(Canvas.RightProperty, doubleAnimation);
}

How is Top to Bottom Marquee Text Implemented in this Control?

Here marquee text is moved outside of canvas area equal to marquee text height and to behave like scrolling text, it is moved equal to canvas height using double animation. To move the text outside of canvas in the top direction, negative top property of canvas is used. However, if someone uses negative top property of canvas, it will go outside of its parent container but will remain visible outside of the parent container. To make marquee text invisible outside of canvas CliptoBound property with true value is used. Then using double animation top property of canvas for that element is being changed continuously to get moving text in the top to bottom direction. The whole thing is done using the following code:

C#
private void TopToBottomMarquee()
{
    double width = canMain.ActualWidth - tbmarquee.ActualWidth;
    tbmarquee.Margin = new Thickness(width / 2, 0, 0, 0);
    DoubleAnimation doubleAnimation = new DoubleAnimation();
    doubleAnimation.From = -tbmarquee.ActualHeight;
    doubleAnimation.To = canMain.ActualHeight;
    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(_marqueeTimeInSeconds));
    tbmarquee.BeginAnimation(Canvas.TopProperty, doubleAnimation);
}

How is Bottom to Top Marquee Text Implemented in this Control?

To behave like each character coming out at a time, marquee text is placed outside of canvas area equal to marquee text height and to behave like scrolling text, it is moved equal to canvas height using double animation in the bottom to top direction. To place the text outside of canvas in the bottom direction, negative top property of canvas is used. However, if someone uses negative bottom property of canvas, it will go outside of its parent container but will remain visible outside of the parent container. To make marquee text invisible outside of canvas CliptoBound property with true value is used. Then using double animation on bottom property of canvas for that element is being changed continuously to get moving text in the top to bottom direction. The whole thing is done using the following code:

C#
private void BottomToTopMarquee()
{
    double width = canMain.ActualWidth - tbmarquee.ActualWidth;
    tbmarquee.Margin = new Thickness(width / 2, 0, 0, 0);
    DoubleAnimation doubleAnimation = new DoubleAnimation();
    doubleAnimation.From = -tbmarquee.ActualHeight;
    doubleAnimation.To = canMain.ActualHeight;
    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(_marqueeTimeInSeconds));
    tbmarquee.BeginAnimation(Canvas.BottomProperty, doubleAnimation);
}

Roll Animation

I know one picture can tell what the Roll animation looks like instead of writing many sentences. The animation looks like the following:

roll3.gif

Opacity masks allow us to make portions of a UIelement or visual either transparent or partially transparent. To apply opacity mask, we can assign a brush to the Opacity Mask property of an element or Visual to provide a mask to the opacity. There are a variety of brushes in WPF: Solid Color Brush, Image Brush, Linear Gradient Brush, and Visual Brush. You can apply any of them you want. The regions, which are not covered by the mask, will remain transparent. The Alpha value of ARGB is used to specify the opacity of the color. An alpha value of 00 indicates completely transparent color and an alpha value of FF indicates fully opaque.

How Can We Give an Image 3D Look in WPF?

I have used transparent Gradient Stop to give the image 3D look. I have made a Linear Gradient Brush with some gradient stops where centre, top, and bottom are transparent. Then I have set the LinearGradientBrush as the opacity mask of Image.

How Can we Reverse the Image?

To reverse the image, I have used scale transform. As I have wanted to reverse the image in y Direction, the scale value for y-axis is -1 and scale value for x-axis is 1. The used C# code for Roll animation is in the following:

C#
public partial class RollAnimation : Window
{
    DispatcherTimer _timer = null;
    double _rollTopLeft = 0;
    double _imageWidth = 400;
    double _imageHeight = 300;
    double _offset = 3;
    double _3DSize = 50;
    Image _tempimage = null;
    public RollAnimation()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Window1_Loaded);
    }
    void Window1_Loaded(object sender, RoutedEventArgs e)
    {
        _tempimage = new Image();
        _tempimage.Width = _imageWidth;
        _tempimage.Height = _imageHeight;
        _tempimage.Source = 
        	loadBitmap(SomeAnimations.Properties.Resources.Copy__2__of_images5);
        ScaleTransform scaleTransform = new ScaleTransform(1, -1, 0, 0);
        _tempimage.LayoutTransform = scaleTransform;
        
        image.Source = 
        	loadBitmap(SomeAnimations.Properties.Resources.Copy__2__of_images5);
        image.Clip = new RectangleGeometry(new Rect(0, 0, _imageWidth, _rollTopLeft));
        
        LinearGradientBrush maskBrush = new LinearGradientBrush();
        maskBrush.StartPoint = new Point(0, 0);
        maskBrush.EndPoint = new Point(0, 1);
        
        GradientStop BlackStop1 = 
		new GradientStop(Color.FromArgb(30, 255, 255, 255), 0.0);
        GradientStop BlackStop3 = new GradientStop(Colors.Black, 0.0);
        GradientStop BlackStop4 = new GradientStop(Colors.Black, .9);
        GradientStop transparentStop = 
        	new GradientStop(Color.FromArgb(175, 255, 255, 255), .5);
        GradientStop BlackStop2 = 
		new GradientStop(Color.FromArgb(100, 255, 255, 255), 1);
        
        maskBrush.GradientStops.Add(BlackStop1);
        maskBrush.GradientStops.Add(transparentStop);
        maskBrush.GradientStops.Add(BlackStop2);
        maskBrush.GradientStops.Add(BlackStop3);
        maskBrush.GradientStops.Add(BlackStop4);
        
        CanAni.OpacityMask = maskBrush;
        
        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromSeconds(.1);
        _timer.Tick += new EventHandler(_timer_Tick);
        _timer.IsEnabled = true;
    }
    
    void _timer_Tick(object sender, EventArgs e)
    {
        _rollTopLeft = _rollTopLeft + _offset;            
        image.Clip = 
        	new RectangleGeometry(new Rect(0, 0, _imageWidth, _rollTopLeft));
        double yPosition = _rollTopLeft + _3DSize;
        double height = _3DSize;
        if (Math.Abs((_imageHeight - _rollTopLeft)) < _3DSize)
        {
            height = Math.Abs(image.Height - _rollTopLeft);
            CanAni.Height = height;
            CanWrap.Height = height;
            yPosition = _rollTopLeft;
            Linetwo.Y1 = height;
            Linetwo.Y2 = height;
        }
        Lineone.Visibility = Visibility.Visible;
        Linetwo.Visibility = Visibility.Visible;
        if (_imageHeight == _rollTopLeft)
        {
            _timer.IsEnabled = false;
            Lineone.Visibility = Visibility.Collapsed;
            Linetwo.Visibility = Visibility.Collapsed;
        }
        _tempimage.Clip = 
        	new RectangleGeometry(new Rect(0, yPosition, _imageWidth, height));
        VisualBrush vb = new VisualBrush(_tempimage as Visual);
        CanAni.Background = vb;
        Canvas.SetTop(CanWrap, _rollTopLeft);
    }
    public static BitmapSource loadBitmap(System.Drawing.Bitmap source)
    {
        return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
        	(source.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
            System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
    }
}

Here four canvases are used. The outer canvas is the main container for this application. The second canvas is used to wrap necessary UIelements to achieve this animation. The third canvas is used to make transparent portions while the fourth canvas is used to achieve 3D look. I have set an image background to the fourth canvas clipping certain portion of the image. To make this canvas moving, I am changing the top position of the canvas continuously. This trick is applicable to any UIelement type. Here I have taken image as the UIelement type to implement the Roll animation. I have calculated the height and width of the image and started animation from (0,0) point. Every WPF UIelement type has a clip property and you can set any geometry object to clip. However, for this animation, I have used Rectangle geometry as clip geometry. As I am changing the brush of animated canvas at runtime, property based animation does not work here. For that reason, I have used dispatcher timer based animation. I am using dispatcher timer here instead of classic Timer as dispatcher timer runs in UI thread and I do not need to write thread switching code The used XAML for this is as follows:

XML
<Canvas  x:Name="CanOuter" Height="300" Width="400">
       <Image  Canvas.Left="0" Canvas.Top="0"
       Height="300" Width="400" Stretch="Fill"  Name="image" />
       <Canvas Canvas.Left="0" x:Name="CanWrap"
   Canvas.Top="0" Background="White" Margin="0,0" Height="50" Width="400"  >
           <Canvas x:Name="CanAni"   Height="50" Width="400"  >
               <Line  Visibility="Collapsed"  X1="01" Y1="00"
       Name="Lineone" X2="400" Y2="0" Stroke="Black"    StrokeThickness="1">
                   <Line.BitmapEffect>
                       <BlurBitmapEffect Radius="1" KernelType="Box" />
                   </Line.BitmapEffect>
               </Line>
               <Line Visibility="Collapsed" X1="00" Y1="50" X2="400"
           Y2="50" Name="Linetwo" Stroke="Black" StrokeThickness="1">
                   <Line.BitmapEffect>
                       <BlurBitmapEffect Radius="1" KernelType="Box" />
                   </Line.BitmapEffect>
               </Line>
           </Canvas>
       </Canvas>
   </Canvas>

Drip Animation

The drip animation looks like the following:

drip2.gif

The main theme of drip animation is repainting one or two pixels in x-direction to fill the region. In other words, the main theme of drip animation is based on making a brush from 2*height region of animated image and paint a canvas using this brush and placing this canvas over the image to mask some portion of image. For animating purposes, I have changed masking region continuously and the image region of size 2*height is also continuously changed to make consistent brush. We can set any geometry like rectangle geometry to define the visible region of a WPF UIelement type. To make it possible, every WPF UIelement type has a clip property and you can set any geometry object to clip. Only the area that is within the region of the geometry will be visible. Portion of UIelement outside the geometry will be visually clipped in the rendered layout. I have used this concept in this animation to set visible portion of image. For drip animation, the following XAML is used:

XML
<Canvas ClipToBounds="True" Name="canMain"    Height="300" Width="300"  >
       <Canvas Name="canani"    Height="300" Width="300"  ></Canvas>
       <Image Height="300"   Stretch="Fill"  Width="300"   Name="gh" />
   </Canvas>

For drip animation, I have used Visual brush. At first, I made an image element clipping a 2-pixel * image height region from the left most end of the original image. To implement this, I set rectangle geometry with 2-pixel * image height as clip geometry to an image type UIelement. Then I created a visual brush from the image type UIelement. Then I set this brush to the mask canvas. I have used the canvas for masking the image and applied the drip effect on the canvas. The mask canvas has zindex of 2 and the original image has zindex of 1. As zindex of masked canvas is greater than the original image, masked canvas will always appear in front of the original image. For drip animation, the following code is used:

C#
public partial class DripAnimation : Window
{
    DispatcherTimer _timer = null;
    Image _tempimage = null;
    double leftposition = 0;
    public DripAnimation()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Window1_Loaded);
    }
    
    void Window1_Loaded(object sender, RoutedEventArgs e)
    {           
        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromSeconds(0.03);
        _timer.Tick += new EventHandler(_timer_Tick);
        _timer.IsEnabled = true;
        _tempimage = new Image();
        _tempimage.Width = 300;
        _tempimage.Height = 300;
        _tempimage.Stretch = Stretch.Fill;
        _tempimage.Source = 
        	loadBitmap(SomeAnimations.Properties.Resources.Copy__2__of_images5);
        _tempimage.Clip = new RectangleGeometry(new Rect(10, 0, 2, 300));
        gh.Source = loadBitmap(SomeAnimations.Properties.Resources.Copy__2__of_images5);
        VisualBrush vb = new VisualBrush(_tempimage as Visual);
        canani.Background = vb;
        Canvas.SetZIndex(canani, 3);
        Canvas.SetZIndex(gh, 2);           
    }
    public static BitmapSource loadBitmap(System.Drawing.Bitmap source)
    {
        return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
        	(source.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
            System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
    }
    
    void _timer_Tick(object sender, EventArgs e)
    {
        _tempimage.Clip = new RectangleGeometry(new Rect(leftposition, 0, 2, 300));
        VisualBrush vb = new VisualBrush(_tempimage as Visual);
        canani.Background = vb;
        Canvas.SetLeft(canani, leftposition);
        leftposition++;
        if (leftposition >= 300) _timer.IsEnabled = false;              
    }
}

At each tick event of dispatcher timer, I am resetting the x coordinate position of rectangle clip geometry to change the clipping region but height and width of clipping region remains the same. Here _tempimage is the image UIelement that is used to create a visual brush. I slice the bitmap image horizontally with width 2 each and make the visual brush from the sliced image. To implement this, I have used rectangle geometry as clip geometry. Here VB is the visual brush that is set to the mask canvas as brush. As I have set the clipstobound property of outermost canvas to true, the unnecessary portion will be clipped automatically.

Known Issue

Here roll and drip animations are made using dispatcher timer, which is not guaranteed to fire in exact time and runs in UI thread. To make the animation time controlled using dispatcher timer, you can adjust the change (offset) of animation in each step. On the other hand, to make the animation time controlled, you can use System.timer instead of dispatcher timer, which is guaranteed to execute in exact time and run in different thread instead of UI thread. If you use System.timer, you have to perform cross thread operation for UI update. To keep the code simple, I have not implemented System.timer here.

Conclusion

Thanks for reading this write up. Hope this will save some of your time. If you guys have any questions, I will love to answer them. I always appreciate comments. If you find any issue, please let me know. I will try my level best to resolve the issue.

History

  • Initial release – 30/12/09

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) CP
Australia Australia
I am an Independent Contractor in Brisbane, Australia. For me, programming is a passion first, a hobby second, and a career third.

My Blog: http://weblogs.asp.net/razan/






Comments and Discussions

 
GeneralMy vote of 5 Pin
ISuryansyah6-Nov-11 6:59
professionalISuryansyah6-Nov-11 6:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.