One of the areas of .net development that doesn't get the attention I think it deserves is graphics. At the UI level, a lot of software is effectively just a bolting together of existing components that all draw themselves and so this topic is often considered a sideline. Maybe it is beyond the scope of a lot of programs, but occasionally it's very useful to know about the default drawing behaviour and how it can be altered.
Although it isn't hard to draw to a control's surface in Forms applications, the default configuration for the GDI+ system can limit the quality of the result – mainly for reasons of safety (speed, making sure something gets drawn and previously painted stuff gets erased, etc.). This is the first of three posts on graphics in Windows Forms applications. In this post, I'll discuss the ControlStyles flags held internally in objects derived from Control and how they can be set to help controls that draw themselves by overriding OnPaint(). In the next post I'll talk about painting lines and shapes (and in particular about smoothing) and in the last one, the discussion will move on to drawing text directly to the screen. On to the first topic…
System.Windows.Forms.Control has a private enum variable called controlSyle, which is of type System.Windows.Forms.ControlStyles. Amongst other things, the flags contained in this enum can alter the way the control is painted in several respects. In particular, you can use it to:
As I said, ControlStyles is a flag-style enum (i.e. the possible values are independent of one another and several can be set at once). The variable itself is private, but it's exposed using the getter / setter methods GetStyle() and SetStyle(). However, these are protected, so deriving from an object in the Control hierarchy is the only way to access them. This isn't a problem because deriving a new control from an existing one (or simply from Control itself) is really what you want to do any time you're changing behaviour like painting.
Don't forget when you're dealing with a derived control that the recommended way to access events is not to subscribe to them, but override the OnEvent() methods of the base class (OnPaint(), OnClick(), etc.). Whether you then call the base method is up to you, but as the base method actually raises the event, make sure there are no subscribers if you want to miss this out.
The flags that are of interest to this discussion are detailed in the table below:
UserPaint causes the Paint event to be raised when redraw is necessary. This flag will be set to false on controls the OS draws without input from the program. See the note below about this flag in combination with some of the others, but in general, if you're painting, you'll need this set to true.
AllPaintingInWmPaint simply says that the control wants to be responsible for the entire painting operation. What this means in practice is that the PaintBackground event is not raised when a WM_ERASEBKGND message is received from the Windows message pump. Running the (potentially lengthy) implementation of OnPaintBackground in Control is pointless if you are just going to replace the entire area with something else (a picture or some other graphic that fills the control). This flag can greatly reduce flickering on controls where OnPaint() always redraws every pixel of the invalidated region (or can easily be made to).
In fact, MSDN suggests that when this flag is true, PaintBackground is raised instead from the WM_PAINT Windows event, and you sometimes see a recommendation to override OnPaintBackground with an empty method specifically so the base method is never called. When I experimented with this, I found OnPaintBackground was never called if AllPaintingInWmPaint was set to true. Your mileage may vary, of course, and I'd be interested to hear any counter-examples. However, I can't see the point of making it possible to not call it from one Windows message just to call it from a subsequent one instead.
OptimizedDoubleBuffer means the PaintEventArgs passed to OnPaint() references the Graphics object of an off-screen surface and swaps that onto the display only when painting has been completed. This is another flicker-reducer and works especially well if the painting takes any length of time and / or is made up of layers (which the user can often see being constructed). Obviously, it does introduce an extra step into each Paint event and may not be necessary so should be experimented with rather than simply used all the time.
There is another member of the enum, "DoubleBuffer" which you may come across (previous to .net 2.0, it was all that existed). In current versions of the framework, it is hidden from Intellisense so you won't see it in the IDE. Despite this, there is a (presumably erroneous) note on the MSDN ControlStyles entry saying it is preferred over OptimizedDoubleBuffer.
MSDN says that UserPaint must be true for either of AllPaintingInWmPaint and OptimizedDoubleBuffer to work and that in order for OptimizedDoubleBuffer to work fully, AllPaintingInWmPaint should also be set to true when double buffering is desired.
ResizeRedraw is not really a way of improving display, but does provide a handy method for forcing the Paint event to be raised whenever a control's size has been changed. I've mentioned it here because you would normally set it together with the other flags if it is useful for your control.
I've seen a few contradicting examples and explanations in this area and the problems are exacerbated by the current (as of Feb 2011) MSDN literature. As well as contradiction of my findings with the PaintBackground event and the surprising recommendation of DoubleBuffer (i.e. the older flag, currently hidden from Intellisense in the IDE) over OptimizedDoubleBuffer, the ControlStyles page is ambiguous about UserPaint and DoubleBuffer (there is a statement that it should be set for the DoubleBuffer flag to work and another that says it is implied by the DoubleBuffer flag).
I thought it would be interesting to see which flags are set by default on some of the familiar controls. Here is the full set of flags, the numeric value of each within the enum and those set by default ('*' in the 'T' column) for a control derived from Panel:
I looked at the default flags for several different types of control and found varying results:
Lastly, it's worth just making a note about the best way of calling SetStyle(). The method declaration is:
which at first glance might lead you to suspect that only a single flag can be set at once and you may see discussions of setting these flags which list several calls to this method. However, because of the way the SetStyle method is implemented, you can combine flag values as you normally would (i.e. with the '|' OR operator) and set several at once, providing you need to set each to the same Boolean value:
Clearly, a single call is the more efficient way to change several flags at once.
Although it isn't hard to draw to a control's surface in Forms applications, the default configuration for the GDI+ system can limit the quality of the result – mainly for reasons of safety (speed, making sure something gets drawn and previously painted stuff gets erased, etc.). This is the first of three posts on graphics in Windows Forms applications. In this post, I'll discuss the ControlStyles flags held internally in objects derived from Control and how they can be set to help controls that draw themselves by overriding OnPaint(). In the next post I'll talk about painting lines and shapes (and in particular about smoothing) and in the last one, the discussion will move on to drawing text directly to the screen. On to the first topic…
System.Windows.Forms.Control has a private enum variable called controlSyle, which is of type System.Windows.Forms.ControlStyles. Amongst other things, the flags contained in this enum can alter the way the control is painted in several respects. In particular, you can use it to:
- Inform the Window Manager that you want to be in charge of drawing a control if you don't need it to do anything (which removes pointless and potentially time-consuming calls during painting)
- Use Double-buffering for complicated (i.e. time-consuming) drawing
- Ask for painting to be redone any time the control is resized
As I said, ControlStyles is a flag-style enum (i.e. the possible values are independent of one another and several can be set at once). The variable itself is private, but it's exposed using the getter / setter methods GetStyle() and SetStyle(). However, these are protected, so deriving from an object in the Control hierarchy is the only way to access them. This isn't a problem because deriving a new control from an existing one (or simply from Control itself) is really what you want to do any time you're changing behaviour like painting.
Don't forget when you're dealing with a derived control that the recommended way to access events is not to subscribe to them, but override the OnEvent() methods of the base class (OnPaint(), OnClick(), etc.). Whether you then call the base method is up to you, but as the base method actually raises the event, make sure there are no subscribers if you want to miss this out.
The flags that are of interest to this discussion are detailed in the table below:
Flag
|
Description
|
UserPaint
|
Cause the Paint event to be raised when
repainting is necessary
|
AllPaintingInWmPaint
|
Prevent the control from being erased by the OS
before Paint is raised
|
OptimizedDoubleBuffer
|
Construct control in buffer before outputting to
screen
|
ResizeRedraw
|
Repaint the control automatically if resized
|
UserPaint causes the Paint event to be raised when redraw is necessary. This flag will be set to false on controls the OS draws without input from the program. See the note below about this flag in combination with some of the others, but in general, if you're painting, you'll need this set to true.
AllPaintingInWmPaint simply says that the control wants to be responsible for the entire painting operation. What this means in practice is that the PaintBackground event is not raised when a WM_ERASEBKGND message is received from the Windows message pump. Running the (potentially lengthy) implementation of OnPaintBackground in Control is pointless if you are just going to replace the entire area with something else (a picture or some other graphic that fills the control). This flag can greatly reduce flickering on controls where OnPaint() always redraws every pixel of the invalidated region (or can easily be made to).
In fact, MSDN suggests that when this flag is true, PaintBackground is raised instead from the WM_PAINT Windows event, and you sometimes see a recommendation to override OnPaintBackground with an empty method specifically so the base method is never called. When I experimented with this, I found OnPaintBackground was never called if AllPaintingInWmPaint was set to true. Your mileage may vary, of course, and I'd be interested to hear any counter-examples. However, I can't see the point of making it possible to not call it from one Windows message just to call it from a subsequent one instead.
OptimizedDoubleBuffer means the PaintEventArgs passed to OnPaint() references the Graphics object of an off-screen surface and swaps that onto the display only when painting has been completed. This is another flicker-reducer and works especially well if the painting takes any length of time and / or is made up of layers (which the user can often see being constructed). Obviously, it does introduce an extra step into each Paint event and may not be necessary so should be experimented with rather than simply used all the time.
There is another member of the enum, "DoubleBuffer" which you may come across (previous to .net 2.0, it was all that existed). In current versions of the framework, it is hidden from Intellisense so you won't see it in the IDE. Despite this, there is a (presumably erroneous) note on the MSDN ControlStyles entry saying it is preferred over OptimizedDoubleBuffer.
MSDN says that UserPaint must be true for either of AllPaintingInWmPaint and OptimizedDoubleBuffer to work and that in order for OptimizedDoubleBuffer to work fully, AllPaintingInWmPaint should also be set to true when double buffering is desired.
ResizeRedraw is not really a way of improving display, but does provide a handy method for forcing the Paint event to be raised whenever a control's size has been changed. I've mentioned it here because you would normally set it together with the other flags if it is useful for your control.
I've seen a few contradicting examples and explanations in this area and the problems are exacerbated by the current (as of Feb 2011) MSDN literature. As well as contradiction of my findings with the PaintBackground event and the surprising recommendation of DoubleBuffer (i.e. the older flag, currently hidden from Intellisense in the IDE) over OptimizedDoubleBuffer, the ControlStyles page is ambiguous about UserPaint and DoubleBuffer (there is a statement that it should be set for the DoubleBuffer flag to work and another that says it is implied by the DoubleBuffer flag).
I thought it would be interesting to see which flags are set by default on some of the familiar controls. Here is the full set of flags, the numeric value of each within the enum and those set by default ('*' in the 'T' column) for a control derived from Panel:
Hex Binary T Name ========================================================================= 00000001 00000000000000000000000000000001: * ContainerControl 00000002 00000000000000000000000000000010: * UserPaint 00000004 00000000000000000000000000000100: Opaque 00000010 00000000000000000000000000010000: ResizeRedraw 00000020 00000000000000000000000000100000: FixedWidth 00000040 00000000000000000000000001000000: FixedHeight 00000100 00000000000000000000000100000000: * StandardClick 00000200 00000000000000000000001000000000: Selectable 00000400 00000000000000000000010000000000: UserMouse 00000800 00000000000000000000100000000000: * SupportsTransparentBackColor 00001000 00000000000000000001000000000000: * StandardDoubleClick 00002000 00000000000000000010000000000000: AllPaintingInWmPaint 00004000 00000000000000000100000000000000: CacheText 00008000 00000000000000001000000000000000: EnableNotifyMessage 00010000 00000000000000010000000000000000: DoubleBuffer 00020000 00000000000000100000000000000000: OptimizedDoubleBuffer 00040000 00000000000001000000000000000000: * UseTextForAccessibility
I looked at the default flags for several different types of control and found varying results:
Name Panel Form Button TextBox Control ============================================================== ContainerControl * * UserPaint * * * * Opaque * ResizeRedraw * FixedWidth FixedHeight * StandardClick * * * Selectable * * * * UserMouse * SupportsTransparentBackColor * * StandardDoubleClick * * * AllPaintingInWmPaint * * * CacheText * EnableNotifyMessage DoubleBuffer OptimizedDoubleBuffer * UseTextForAccessibility * * * *
Lastly, it's worth just making a note about the best way of calling SetStyle(). The method declaration is:
protected void SetStyle(ControlStyles flag, bool value)
which at first glance might lead you to suspect that only a single flag can be set at once and you may see discussions of setting these flags which list several calls to this method. However, because of the way the SetStyle method is implemented, you can combine flag values as you normally would (i.e. with the '|' OR operator) and set several at once, providing you need to set each to the same Boolean value:
SetStyle (ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer,
true);
Clearly, a single call is the more efficient way to change several flags at once.
No comments:
Post a Comment