views:

2359

answers:

1

I've noticed that the bog-standard ProgressBar in .NET 2.0 (Winforms) does show up as the fancy animated glowing bar in Vista; however, using the ProgressBarRenderer (as one generally has to when trying to draw a progress bar in an owner-drawn list view, grid view, or other such control) just gives the visual style without the pretty animation.

I guess it was silly to expect that this would just magically work - I imagine the native control in Vista must have some sort of embedded timer or thread that obviously doesn't exist when drawing a static image. I did see that if you redraw a ProgressBar control several times in succession (using DrawToBitmap) that you can actually see the distinct stages of the animated glow, so I experimented with using a timer to keep redrawing automatically, but something's just not quite right about the look, and it also eats up a lot more CPU time than an actual ProgressBar does.

This seems to leave me with two sub-standard options: a) Use the ProgressBarRenderer and end up with a Vista "look" but no animation; or b) Use a timer to continually redraw several ProgressBars to bitmaps, and waste CPU cycles to get it looking better but still not perfect.

I was wondering if anybody has had experience embedding progress bars inside owner-drawn controls and might know of a better way than the two options above - something that can accurately reproduce the glow/glint without relying on timers and/or hammering the CPU.

+2  A: 

I had to pull some pretty crazy stunts to make this work. Unfortunately, MSFT didn't update the VisualStyleElement.ProgressBar class to add the parts that Vista added. And the constructor is private. And I had to guess a bit at the parts that produce the animation. I got fairly close with this code, it should give you something to experiment with:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Reflection;

namespace WindowsFormsApplication1 {
  public partial class Form1 : Form {
    VisualStyleElement pulseOverlay;
    VisualStyleElement moveOverlay;
    VisualStyleRenderer pulseRenderer;
    VisualStyleRenderer moveRenderer;
    Timer animator = new Timer();
    public Form1() {
      InitializeComponent();
      ConstructorInfo ci = typeof(VisualStyleElement).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
        null, new Type[] { typeof(string), typeof(int), typeof(int) }, null);
      pulseOverlay = (VisualStyleElement)ci.Invoke(new object[] { "PROGRESS", 7, 0 });
      moveOverlay = (VisualStyleElement)ci.Invoke(new object[] { "PROGRESS", 8, 0 });
      pulseRenderer = new VisualStyleRenderer(pulseOverlay);
      moveRenderer = new VisualStyleRenderer(moveOverlay);
      animator.Interval = 20;
      animator.Tick += new EventHandler(animator_Tick);
      animator.Enabled = true;
      this.DoubleBuffered = true;
    }
    void animator_Tick(object sender, EventArgs e) {
      Invalidate();
    }

    int xpos;
    protected override void OnPaint(PaintEventArgs e) {
      Rectangle rc = new Rectangle(10, 10, 100, 20);
      ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rc);
      rc = new Rectangle(10, 10, 50, 20);
      ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rc);
      xpos += 3;
      if (xpos >= 30) xpos = -150;  // Note: intentionally too far left
      rc = new Rectangle(xpos, 10, 50, 20);
      pulseRenderer.DrawBackground(e.Graphics, rc);
      moveRenderer.DrawBackground(e.Graphics, rc);
    }
  }

}
Hans Passant
It'll be interesting trying to integrate that into a list control - I'm not sure if it wastes more CPU cycles invalidating every single item or measuring all the fonts and other elements to try to figure out the exact bounds of the progress bar to invalidate. Looks like it will work though, thanks.
Aaronaught
This was pretty useful for me, but there's no need to use reflection to create new VisualStyleElements. You can use the static VisualStyleElement.CreateElement function for this.
Eric