Non-client operations. Part 1, Drawing.
The standard appearance of windows controls seems to change
from week to week. The grey beveled appearance that was part of the
original windows look and feel has been superseded by the visual studio style,
Windows XP style and now the new Microsoft office 2003 styles. Controls
have gone from 3D to a flat appearance and now back to a subtle 3D appearance
that uses advanced graphics to provide shading and shine on toolbars and
buttons.
Windows Forms controls provide a 3D or flat appearance as
standard and the framework provides no other method of changing the appearance
of controls and apparently do not enable users to customize their appearance
other than through handling the Paint event and drawing directly on the control
surface. This method can provide the desired results but is by no means
the easiest method simply because all drawing performed in the paint event
happens on the client area and so somebody customizing a control in this
manner has to account for sizes of borders or other embellishments when drawing
the functional part of the control.
Of course, the underlying windows system provides methods for
creating highly customized controls and so is necessary to go back to those
methods if Windows Forms controls are to be correctly managed and easy to use.
Most controls, contain two distinct areas. The client
area in which the interesting functionality of a control is contained and the
non-client area where things such as the title bar and sizable border is drawn.
Usually, the custom painting of a control is limited to the client area because
it's assumed that all of the controls in an application or on a form will have a
particular style and so the general appearance of the control is managed by the
system which draws on the non-client area and only the window which defines the
client area is passed to your code. To radically change the appearance of
a control you have to trap for non-client messages and inject your own graphics.
Specifying the client window area.
Whenever a control is created a message, WM_NCCALCSIZE, is
sent to the control which enables it to specify the size and position of its
client area in relation to its non-client area. This message has two
forms, the simple form requires that a single rectangle is used to specify the
size of the client area in relation to the window. This is the form used for
simple controls derived from Control. The second form uses a structure
containing three rectangles. The first rectangle contains the the proposed new
window coordinates of a window that has been moved or resized. The second
contains the coordinates of the window before it was moved or resized. The third
contains the coordinates of the window's client area before the window was moved
or resized. This is academic in the current context though because in practice
the second from of the message seems never to be used for simple controls.
The client area is specified by altering the rectangle to
suit the thickness of borders or the presence of a title bar. Figure 1
illustrates this.

Figure 1. The relationship between client and non-client
areas.
In the case of a control with a simple but fancier border you
could specify the client area by shrinking the rectangle by a certain amount.
Finding the non-client area.
Whenever the non-client area needs to be painted the system
sends a WM_NCPAINT message to the control. Your response must be to paint the
borders or title-bars or whatever else you consider as non-client stuff. This
would seem straightforward but there are some caveats. The painting is performed
on a different Device Context from that of the client area so you need to obtain
the correct DC and wrap it with a GDI+ Graphics object. This is done using
interop again to import the GetWindowDC method. The rectangle of the non-client
area is also obtained using interop and the GetWindowRect method. The window
rectangle coordinates are given in screen coordinates and so must be converted
to client coordinates. This causes another little complication because the
coordinates of the top and left of the window rectangle, when converted to
client coordinates, can be negative so the resulting rectangle needs to be
offset again to ensure that whatever is drawn is always in the bounds of the
window DC.
Customizing the message loop.
To trap messages such as WM_NCCALCSIZE and WM_NCPAINT you
have to create a custom WndProc override. This enables you to respond to the
messages you're interested in and pass all the ones you're not interested in to
the base class implementation. This listing
shows the custom WndProc for the sample control.
Painting the non-client area.
The sample code which accompanies this
article customizes the appearance of a control by painting a rectangular border
with rounded corners. A property in the control enables the user to choose the
radius of the corners so that controls may be more or less rounded.
Obviously, the greater the radius on the corner the smaller area there must be
in the client rectangle. The windows procedure code shown in the previous
listing calls the RoundRect method listed here to
draw the rectangular border.
Working with Interop.
The sample control imports three methods GetWindowRect,
GetWindowDC and ReleaseDC. The prototypes for these methods are shown in
this listing. In addition, the software uses the RECT
structure which is slightly different to the Rectangle object inasmuch as it
defines the top, left, right and bottom X and Y coordinates of a rectangle
rather than the rectangle location and size. This structure has to be marshaled
because it's passed as a pointer to a RECT by the Win32 code. The listing shows
the RECT structure definition also.
Summary.
This article has shown you how to make the distinction
between the client and non-client areas so that the two types of drawing can be
kept separate as the design of the windows system dictates. Defining the
relationship between client and non-client enables you to embellish controls in
a consistent manner that leaves client behaviours alone and enables you to
concentrate on control functionality rather than having to worry about
non-client appearances at the same time. Next, we'll explore non-client
mouse and button operations.
You can find the Source Code files here.
Read on..
Return to the main
index.