tags:

views:

166

answers:

2

Hoping for a quick answer (which SO seems to be pretty good for)...

I just ran a performance analysis with VS2010 on my app, and it turns out that I'm spending about 20% of my time in the Control.set_Text(string) function, as I'm updating labels in quite a few places in my app.

The window has a timer object (Forms timer, not Threading timer) that has a timer1_Tick callback, which updates one label every tick (to give a stop-watch sort of effect), and updates about 15 labels once each second.

Does anyone have quick suggestions for how to reduce the amount of time spent updating text on a form, other than increasing the update interval? Are there other structures or functions I should be using?

+5  A: 

I ran into this issue myself, and ended up creating my own simple Label control.

.Net's Label control is a surprisingly complicated beast, and is therefore slower than we'd like.

You can make a class that inherits Control, call SetStyle in the constructor to make it double-buffered and user-painted, then override the OnPaint method to call e.Graphics.DrawString and draw the Text property.
Finally, override Text or TextChanged and call Invalidate.

As long as you don't need AutoSize, this will be substantially faster than the standard Label control.

Here is my implementation: (Currently in use in production)

///<summary>A simple but extremely fast control.</summary>
///<remarks>Believe it or not, a regular label isn't fast enough, even double-buffered.</remarks>
class FastLabel : Control {
    public FastLabel() {
        SetStyle(ControlStyles.AllPaintingInWmPaint
               | ControlStyles.CacheText
               | ControlStyles.OptimizedDoubleBuffer
               | ControlStyles.ResizeRedraw
               | ControlStyles.UserPaint, true);
    }
    protected override void OnTextChanged(EventArgs e) { base.OnTextChanged(e); Invalidate(); }
    protected override void OnFontChanged(EventArgs e) { base.OnFontChanged(e); Invalidate(); }

    static readonly StringFormat format = new StringFormat {
        Alignment = StringAlignment.Center,
        LineAlignment = StringAlignment.Center
    };
    protected override void OnPaint(PaintEventArgs e) {
        e.Graphics.DrawString(Text, Font, SystemBrushes.ControlText, ClientRectangle, format);
    }
}

If you don't want centering, you can get rid of, or change, the StringFormat.

SLaks
This is pretty close to what I need, but I noticed a couple things - my font looks funny now, and all the labels need to be resized to accommodate that change. Also, now I'm spending 30% of my time in the DrawString function, which doesn't seem to have sped up my form's responsiveness. Any thoughts?
awshepard
I'm not sure why that would happen. What exactly does the font look like?
SLaks
The letters and letter spacing look slightly wider - in both cases it's MS Sans Serif 8.25 pt, but the FastLabel is just wider.
awshepard
+2  A: 

Well, assigning the Text property takes about 0.6 nanoseconds, give or take. It is the side effects that are expensive. You've probably got the AutoSize property turned on for the labels. So it needs to create the font handle, render the text in a temporary device context, measure the resulting string, taking account for side-effects due to TrueType hinting that artificially stretches the letter shape so it coincides with a pixel on the monitor, also adjusting for the modified ABC metrics to make ClearType work. Then tell the native Windows control that it needs to change its window size. Which will produce paint events for the label as well as the container control that's the parent of the label control. Which goes through the same routine again, now actually drawing pixels. Which, when Aero is enabled, changes bytes in a memory device context which needs to be blitted to the video device driver that actually updates the video memory so that the user can see the result.

Yeah, that takes time.

Speed it up by writing your own code instead of Windows Forms doing it for you. Override the OnPaint event, use TextRender.DrawText to draw the labels. Quick win there, it is easily 50 times faster. Minus the point-and-click convenience of the designer though.

Hans Passant
I tried using this methodology quickly, but I'm guessing I need to do a bit more than just create a simple `class MyFastLabel : Label` that overrides the OnPaint method? With only that function and the TextRenderer.DrawText() method, anything that's a MyFastLabel doesn't show up. What else should be included?
awshepard
I don't think that inheriting label will provide any speed benefits, except for double-buffering.
SLaks
@slaks: you completely missed the point. A form or a panel can draw anything that a label would.
Hans Passant
@Hans: I was referring to his comment.
SLaks