devxlogo

Creating High-Performance Animated Controls

Creating High-Performance Animated Controls

learned very early in my career that the sexiness of an application can have as much effect on its salability as its functionality. I’ve seen a non-technical boss take one look at a highly functional application and pass poor judgment on it because it doesn’t look “cool.” Customers evaluating your software may not understand everything your software can do after a 10-minute sales demo, but they do take away an emotional impression that?in general?is linked directly to the overall look of the application.

Adding relatively simple features such as animated buttons can go a long way toward getting customers to like your application. The only downside is the performance penalty you’ll incur for adding those fancy animated buttons. Worse, if you try to add cool features such as compositing anti-aliased text readout or alpha-channel clickable regions to your control, you have a recipe for a CPU-hogging application.

You’re going to learn about minimizing frame rates, avoiding flicker and caching frames to make the component as high performance as reasonably possible. Creating an animated control is simple but creating an efficient animated control is more difficult.

In this article, you’ll see how to create an animated button component that lets users adjust a sliding percentage value between 0 and 100 by clicking arrows on the component as shown in Figure 1. The percentage itself serves only to force the animation to display a control value as part of the animation?in other words, because the displayed value can change, you can’t simply cache a series of images and display them in round-robin fashion to create the animation; instead, the animation (and any caching scheme for it) must draw and take the current value of the control into account. Users see the current value in the center of each animated button component update immediately when they click either arrow.

?
Figure 1. Animated Buttons: The figure shows a Windows form containing animated buttons from the sample application that accompanies this article

Choosing a Frame Rate
Frame rate is a count of how many times your animation updates the screen during a specified time interval, usually specified in number of refreshes per second, called frames per second (fps). If the frame rate is too slow, animations look jerky. It’s worth looking at other common animation frame techniques to get a sense of the common range of values. For example, television broadcasts in the U.S. refresh every other line on the screen 60 times per second yielding 30 full frames per second. Hard-core gamers tweak their systems to wring up to 100fps from the latest first-person shooters, but that’s clearly overkill for typical animated business or productivity applications; therefore, you should look for more reasonable measures. Film is shot at 24 frames per second. Shocked? Yes, even the multi-million dollar cutting edge computer animated film you recently saw at the theatre was running at only 24 frames per second. You can safely assume, then, that 24fps is sufficient for most desktop animations.

Before you go back and adjust every animation you’ve ever done to run at 24fps you should consider another constraint?judder. Judder occurs when the movement isn’t fluid; viewers will detect the frames in the process of changing as opposed to a smooth movement. Judder can be caused by:

  • Objects moving very rapidly across the screen.
  • Objects rotating, such as looking at a rotating bicycle wheel from the side.

Such objects require a faster frame rate, so you can’t simply decide that 24fps is sufficient for every animation.

Using Animations Reasonably
The goal of an animated control is to provide visual interest to the application without distracting the user. The most notorious abuse of this concept is the HTML tag. In the early days of the Internet, blinking items appeared everywhere. Fortunately, Web developers quickly learned that the blink did nothing but annoy end users. Subtlety wins the day, adding coolness without the annoyance. Simple types of movements like slow-moving clouds, waves, and slowly warping textures are all good candidates.

You’ll also want to choose something with minimal color variation. If your button animation includes a full rainbow of colors, you’ll have a lot of difficulty choosing contrasting colors for the control’s text. You should definitely consult a color wheel if you’re unsure about which colors contrast well with your background without clashing.

Avoiding Flicker
Flicker is the natural enemy of all animation and is the close cousin of judder. Flicker is the appearance of unsteadiness in your animation, for example, when the background shines through momentarily or users see lines appear as they’re being drawn rather than seeing the whole frame at once.

You can easily avoid flicker by using double buffering. To fully enable double buffering you’ll need to set the properties AllPaintingInWMPaint, UserPaint, and DoubleBuffer to true. AllPaintingInWMPaint tells Windows not to erase your background, UserPaint tells Windows that you’ll handle all the drawing tasks for your component, and double buffering uses an off-screen buffer for the drawing you do in the OnPaint method and blits it to the onscreen buffer screen all at once. You can enable all three options in your animated control just by adding a few lines to your constructor.

   SetStyle(ControlStyles.UserPaint, true);    SetStyle(ControlStyles.AllPaintingInWmPaint, true);    SetStyle(ControlStyles.DoubleBuffer, true);

Setting Paint Timing
The easiest way to control timing in WinForms is to use a Timer control. To add a timer control to your component, simply drag and drop a timer to your form in design view. In the component’s constructor, initialize the timer to fire 24 times per second.

   AnimationTimer = new Timer();   AnimationTimer.Interval = 1000/24;   AnimationTimer.Tick += new       EventHandler(AnimationTick);   AnimationTimer.Start();

Every time the timer fires, advance the frame and call the Refresh method, which forces a Paint event to fire.

   private void AnimationTick(      object sender, EventArgs e)   {      CurrentFrame += 1;      if(CurrentFrame >         OriginalImages.Length-1)      { CurrentFrame=0; }      this.Refresh();   }

Accepting User Input
Accepting clicks and changing the control in response to user input is simple, thanks to the rich GDI+ libraries available. For the code example used in this article I wanted to create a simple “hover” effect, so I added an OnMouseMove handler that calls a method to check whether the user’s mouse is over either of the two arrows on the button. If it is, the example changes the arrow background from transparent white to solid white.

Because the code uses the OnMouseMove event, you have to be a trifle paranoid. For example, consider what happens if you get an OnMouseMove event, indicating that you should highlight the arrow?but then the user moves the mouse out of the control quickly. You’ll find that the control will be stuck in the rollover state about 30 percent of the time. One way to solve the problem is to put your rollover detection code in its own function as shown in the code example below, and call that same rollover detection code in the frame paint method whenever it paints the arrows. Think of it as a quick double check to make sure you don’t paint a rollover state when it’s no longer needed.

   protected void CheckRollover()   {      Point cLoc =          this.PointToClient(Control.MousePosition);      HighlightLeft=lArrow.Contains(cLoc);      HighlightRight=rArrow.Contains(cLoc);   } 

Accepting user clicks that adjust the percentage readout upwards and downwards works much the same way that the rollover detection code does; however, you need to put the hit detection code in the OnMouseUp handler instead.

Reducing CPU Utilization Using Frame Caching
The primary reason that animated buttons such as the ones in the sample code can be CPU hogs is that they draw on top of the background images before they’re painted. The AnimatedButton control paints two little arrows for the user to click on and draws a percentage readout in the middle. Listing 1 shows the complete code for the AnimatedButton control.

Performing those drawing actions 24 times per second for each button can burn a large number of CPU cycles. So if you have 10 animated buttons in your form, and you’re rendering the control value on the images 24 times per second, the computer must draw the control values 240 times per second. Even for modern desktops, that’s too much drawing.

With that said, there is no reason why you should have to paint the same frames every time. You can cache most of the frames by keeping an array that describes the state of each button?in this case the percent to display?alongside the array of frames. If the array describing the state of the control’s data has a different version of the data for the frame you’re about to draw, then you need to draw a new frame. If the data hasn’t changed since the last time you showed that frame, you can use a cached version of the frame you painted last time.

For example, in the excerpt of the constructor below, you take the array of images that represent the frames of your animation and store them in a private variable called OriginalImages. You then initialize a CachedImage array the same size as your original image array. The CachedImage array stores all the pre-rendered frames. Finally, you initialize a CachedValue array of integers (again, the same size as the original image array) containing invalid values, which forces the frame to be drawn the first time the frame is rendered. The CachedValue array stores the control value of the control at the time a particular frame was rendered, giving you all the information you need to know to determine whether to redraw the frame or not.

   public AnimatedButton(Image[] imgs, Random ran)   {      InitializeComponent();      this.OriginalImages=imgs;      CachedImages = new         Image[this.OriginalImages.Length];      CachedValue = new         int[this.OriginalImages.Length];      for (int i=0; i

In the OnPaint method, you check the CachedValue array to see if the control's current value matches the cached value for that particular frame. If it does, then you paint the pre-rendered cached image.

If the value doesn't match, your code has a lot more work to do. First, you'll need to get a copy of the original image from the OriginalImages array and draw the custom display on top of it. Then you'll need to store that rendered frame in the cache and set the CacheValue array to your current value so the next time around you'll know if the frame needs to be redrawn.

   protected override void OnPaint(PaintEventArgs e)   {      if(CachedValue[CurrentFrame] ==          percentage)      {            //The cached value matches the         //current value, therefore you can         //just draw a cached frame instead         //of re-rendering.            e.Graphics.DrawImage(            CachedImages[CurrentFrame]            ,0,0,Width, Height);         //You used the cached image         //So you're done               return;      }      Image thisFrame =          (Image)OriginalImages      [CurrentFrame].Clone();      Graphics imgG =          Graphics.FromImage(thisFrame);         //DRAW IMAGE ON thisFrame         this.CachedImages[CurrentFrame]=          thisFrame;      e.Graphics.DrawImage(thisFrame,         0,0,this.Width, this.Height);      imgG.Dispose();   }

Extending the Concept
There are a number of ways you can modify the source code included with this article to get even more performance. For example, you could share the frame cache between all buttons on the same page, but only at the terrible cost of complexity. Another option would be to store an array of objects representing all states of the button?including rollover state?to provide more caching opportunities.

By caching the rendered states of a custom control, you can make a significant reduction in the amount of processing required to render animation effects. You can download the source code provided with this article and extend it for your own purposes.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist