WinForms ProgressBar with Text
I know everyone has been dying for me to do another programming post, so voila – you’re welcome!
I was working on a program for scheduling Zoom webinars and meetings (more on that in another post) and even though the program is just for me, I figured I’d set up a progress display. This was absolutely not an avoidance tactic from what I was really supposed to be doing.
The built-in WinForms ProgressBar is decent, except that it is just a bar–it doesn’t have any numeric feedback. I originally put a label positioned just above the bar to display a numeric count, but it was ugly. Since this was a UI with an important client base (still just me), I figured I would search the Interwebs for a control to do this.
Alas, the Interwebs failed me :-o. Some people were drawing their own (and so not getting the fancy built-in flash effect), and some people were doing drawing outside the control (which wouldn’t persist). I did find one control that worked, but it required unsafe code (unsafe as far as the compiler was concerned, not one that was, say, a tripping hazard). It was also overly complex.
So I ended up throwing together my own, and I figured I’d make it available in case anyone else might need it.
It has four text modes. In addition to the percent display shown above, you can have it show a count:
You can also just specify your own text:
And the final exciting mode is None – in case you don’t want to display any text. Arguably not really needed since you could just use a regular ProgressBar (or set it to manual with blank text), but seemed like it should be there for completeness.
The class is called ProgressBarEx, and only has a handful of custom properties:
- DisplayType – An enum: None, Percent, Count, Manual. It defaults to Percent.
- ManualText – If the DisplayType is Manual, the text to display.
- TextColor – The color for the text. Couldn’t really use ForeColor because that is technically the color of the bar, although it is ignored if VisualStyles is enabled (so virtually always).
- Font – Okay, not really a custom property, but ProgressBar hides the property (because it doesn’t actually have any text), so I had to re-expose it in case you wanted to change it to Comic Sans or something.
You can download the code here. It is licensed in whichever way lets you do whatever you want with it. MIT or fishing or whatever.
There are only a few bits of code of interest. First is to give the control an opportunity to paint extra stuff. The ProgressBar WinForms control is really just a wrapper of the Windows control, so you can’t just OwnerDraw it. Instead, you need to catch the WM_PAINT message:
public const int WM_PAINT = 0xF; protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == WM_PAINT) AdditionalPaint(m); }
The AdditionalPaint() method does the actual work of painting the text:
private void AdditionalPaint(Message m) { if (DisplayType == TextDisplayType.None) return; string text = GetDisplayText(); using (Graphics g = Graphics.FromHwnd(Handle)) { Rectangle rect = new Rectangle(0, 0, Width, Height); StringFormat format = new StringFormat(StringFormatFlags.NoWrap); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; using (Brush textBrush = new SolidBrush(TextColor)) { g.DrawString(text, Font, textBrush, rect, format); } } }
Pretty straightforward. GetDisplayText() generates the text appropriately based on the current mode. Then we just create a Graphics object from the ProgressBar’s Handle and draw a centered string. Since the bar has already painted itself, this text appears on top of the bar and, because we are catching the paint event, if something overlays the bar, the text will be repainted (this was an issue with several of the examples I found).
The only other thing of note is dealing with flicker. For those who know me, I am obsessive about avoiding flicker, and the first version of this was a bit flickery. You can’t just set the .NET DoubleBuffer style flag, since the bulk of the painting is being done by a low-level Windows control. Fortunately, low-level Windows has its own equivalent which you can specify when the control is created:
public const int WS_EX_COMPOSITED = 0x2000_000; protected override CreateParams CreateParams { get { CreateParams parms = base.CreateParams; // Force control to double-buffer painting parms.ExStyle |= WS_EX_COMPOSITED; return parms; } }
Note the groovy use of the new C# 7.0 digit separator character for the constant!
Well, I may not be done with my actual tasks, but I am making progress.
Jeez, wot a nerd!
You’re just jealous because your progress bar is boring and textless!