RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Creating High-Performance Animated Controls : Page 3

Controls with subtle animated effects can spice up a WinForms UI, but they can also significantly degrade application performance. By caching the rendered states of your controls, you can dramatically reduce the performance penalty.

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 = 
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)
      CachedImages = new 
      CachedValue = new 
      for (int i=0; i<this.OriginalImages.Length; i++)
         { CachedValue[i]=-1; } 
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] == 
         //The cached value matches the
         //current value, therefore you can
         //just draw a cached frame instead
         //of re-rendering.   
            ,0,0,Width, Height);
         //You used the cached image
         //So you're done      
      Image thisFrame = 
      Graphics imgG = 
      //DRAW IMAGE ON thisFrame
         0,0,this.Width, this.Height);
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.

David Talbot is the vice president of development for Data Systems International, a company that develops case-management software for the social services industry. His experience ranges from license-plate recognition using neural networks to television set-top boxes to highly scalable Web applications. He is also the author of Applied ADO.NET.
Email AuthorEmail Author
Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date