Win32 Tips & Tricks
9 minute read •
Introduction
This page presents a few win32 programming tips that I have picked up whilst learning to program Windows.
Filling areas with a solid colour
The ExtTextOut
API call is the fastest and simplest way to fill a rectangular area with a solid colour. Most people are aware of the FillRect
API call. The disadvantage of this function is that it requires the user to supply a handle to a brush, which also means that you must look after this brush yourself. PatBlt
is another fast way to fill an area with a brush, but has the same inconveniences as FillRect
. By using the ETO_OPAQUE
flag in the call to ExtTextOut
, and supplying a zero-length string to display, you can quickly and easily fill a rectangular area using the current background colour.
void PaintRect(HDC hdc, RECT *rect, COLORREF colour)
{
COLORREF oldcr = SetBkColor(hdc, colour);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, rect, "", 0, 0);
SetBkColor(hdc, oldcr);
}
Dragging a window by its client area
The simplest way to drag a window by its client area (or drag a window if it has no title bar), is to handle the WM_NCHITTEST
message that is sent to every window when a mouse event occurs. By returning a value of HTCAPTION
from the WM_NCHITTEST
message handler, you can fool windows into thinking that the mouse is over a caption bar, and the window can be dragged around with the mouse. It is best to only allow dragging by the client area of a window - otherwise, the window borders would become unusable. To achieve this, call the default window procedure, and check if its return value is HTCLIENT
- if it is, then return HTCAPTION
instead. Otherwise, just let the default behaviour take place.
UINT uHitTest;
...
case WM_NCHITTEST:
uHitTest = DefWindowProc(hwnd, WM_NCHITTEST, wParam, lParam);
if(uHitTest == HTCLIENT)
return HTCAPTION;
else
return uHitTest;
Get the mouse position at any time
The current mouse position can always be retrieved with a call to GetCursorPos
. However, the API call GetMessagePos
returns the mouse position at the time when the last message was posted.
Automatically include a library from a source file (Visual C)
Including the following command in any source file will include a library search record in the resulting object file, which when it comes to link time, will ensure that the specified file will always be included.
#pragma comment( lib, "filename.lib" );
Calculate the point size of a font
The LOGFONT
structure specifies the a fontsize in logical units, which is fairly useless in alot of cases. Alot of the time “points” are useful for displaying the size of a font in a user-interface. Use the following formula to convert from logical units to points, when using the MM_TEXT
mapping mode.
int pointsize = MulDiv(logheight, 72, GetDeviceCaps(hdc, LOGPIXELSY));
To convert from points back to logical device coordinates, use
int logheight = -MulDiv(pointsize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
Prevent a window from being resized
There are three ways to prevent a window from being resized.
- Make sure the window does not have the
WS_THICKFRAME
style set, as this allows the user to resize the window. - Handle the
WM_GETMINMAXINFO
message. - Handle the
WM_SIZING
message.
Change the colours of an EDIT control
The best way to change the colours of an edit control is to handle the WM_CTLCOLOREDIT
message in the parent window of the edit control. When you receive this message, you will have the device context in wParam
. You can use SetTextColor
and SetBkColor
on this device context to set the colours. Lastly, you must return a handle to a brush (which you must assume ownership of), which is used to paint the edit control’s background.
case WM_CTLCOLOREDIT:
hdc = (HDC)wParam;
SetTextColor(hdc, RGB(255,0,0)); // red
SetBkColor(hdc, RGB(255,255,0)); // yellow
return GetSysColorBrush(COLOR_3DHILIGHT); // hilight colour
Center a window relative to its parent
Here’s a quick and easy method to center any window relative to its parent window.
BOOL CenterWindow(HWND hwnd)
{
HWND hwndParent;
RECT rect, rectP;
int width, height;
int screenwidth, screenheight;
int x, y;
//make the window relative to its parent
hwndParent = GetParent(hwnd);
GetWindowRect(hwnd, &rect);
GetWindowRect(hwndParent, &rectP);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
x = ((rectP.right-rectP.left) - width) / 2 + rectP.left;
y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top;
screenwidth = GetSystemMetrics(SM_CXSCREEN);
screenheight = GetSystemMetrics(SM_CYSCREEN);
//make sure that the dialog box never moves outside of//the screen
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x + width > screenwidth) x = screenwidth - width;
if(y + height > screenheight) y = screenheight - height;
MoveWindow(hwnd, x, y, width, height, FALSE);
return TRUE;
}
Prevent a Rebar’s text labels from flickering
A rebar control is usually contained within the top portion of a main window. Whenever the main window’s size changes, the rebar must also be resized to fit. The obvious way to do this is to handle WM_SIZE
, and use MoveWindow(0, 0, width, rebar_height)
to size the rebar exactly. However, this causes the text labels on a rebar control to flicker. To prevent this from happening, size the rebar control to the cover total size of the main window’s client area. The rebar has some special sizing logic inside it which stretches the rebar across its parent’s width, but keeps its height constant, even though you told it to be stretched vertically as well. Believe it or not, this works: In the main window’s WM_SIZE
handler:
case WM_SIZE:
MoveWindow(hwndRebar, 0, 0, LOWORD(lParam), HIWORD(lParam));
...
Create non-rectangular windows
The SetWindowRgn
API can be used to give any window a non-rectangular shape. A region must be created, using any of the Region functions, such as CreateRectRgn
or CreateEllipticRgn
. The region coordinates must be window-relative.
HRGN hrgn;
...
hrgn = CreateEllipticRgn(0, 0, 100, 100);
SetWindowRgn(hwnd, hrgn, TRUE);
Update the text in a status bar
Here’s a useful function to set the text of any status bar pane using a printf
-like call
#include <stdarg.h>
/* style can be SBT_NOBORDERS etc */
void SetStatusBarText(HWND hwndSB, int part, unsigned style, char *fmt, ...)
{
char tmpbuf[128];
va_list argp;
va_start(argp, fmt);
_vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, argp);
va_end(argp);
//cannot use PostMessage, as the panel type is not set correctly
SendMessage(hwndSB, SB_SETTEXT, (WPARAM)part | style, (LPARAM)(LPSTR)tmpbuf);
}
Display the Customize dialog for a toolbar
The standard windows customization dialog for toolbars can be displayed by sending the toolbar the TB_CUSTOMIZE
message.
SendMessage(hwndToolbar, TB_CUSTOMIZE, 0, 0);
You must remember to handle the TBN_QUERYDELETE
and TBN_QUERYINSERT
notifications, and return non-zero for both of these. Without them, the customize dialog will appear very briefly and then vanish.
Resize a window without having to move it
The easiest way to resize or move a window is to use the MoveWindow
API. There is another function though, SetWindowPos
, which can be used just to resize a window, and to keep it in the same place, by specifying the appropriate flags.
void SizeWindow(HWND hwnd, int width, int height)
{
SetWindowPos(hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
Give a Rebar control a double gripper
Does anyone else prefer the rebar controls that Internet Explorer 3 had? The ones with the double-grippers? There is an easy way to give the newer rebar controls (single-grippers) a new look, by using the custom draw feature that all common controls support.
You must create the rebar’s bands with the RBBS_GRIPPERALWAYS
style (to display the single vertical grip bar). You must also set each rebar band’s header size to around 10 pixels to allow extra room for the addtional gripper.
REBARBANDINFO rbBand;
...
rbBand.cbSize = sizeof(REBARBANDINFO);
rbBand.fMask = RBBIM_STYLE | RBBIM_HEADERSIZE /* include the others here */;
rbBand.fStyle = RBBS_NOVERT | RBBS_CHILDEDGE | RBBS_GRIPPERALWAYS;
rbBand.cxHeader = 10;
Handle the WM_NOTIFY
message in the rebar control’s parent window. When you receive a NM_CUSTOMDRAW
notification for your rebar, use the following function to draw the extra gripper:
LRESULT RebarCustomDraw(NMCUSTOMDRAW *lpcd)
{
RECT rect;
if(lpcd->dwDrawStage == CDDS_PREPAINT)
{
return CDRF_NOTIFYPOSTPAINT;
}
else if(lpcd->dwDrawStage == CDDS_POSTPAINT && lpcd->dwItemSpec != 0)
{
SetRect(&rect, 5, 2, 8, 38);
DrawEdge(lpcd->hdc, &rect, BDR_RAISEDINNER, BF_RECT|BF_LEFT|BF_RIGHT);
return 0;
}
return CDRF_DODEFAULT;
}
Calculate the size of a menu bar
It is straight-forward to calculate the size of a menu bar, or even a drop-down menu. You can use the GetMenuItemRect
function (available in all 32bit versions of Windows) to work out the size of each item, and then use a loop to add all of the individual items together to work out the size of the whole menu.
BOOL GetMenuRect(HWND hwnd, HMENU hmenu, RECT *pmenurect)
{
RECT rect;
int i;
SetRect(pmenurect, 0, 0, 0, 0);
for(i = 0; i < GetMenuItemCount(hmenu); i++)
{
GetMenuItemRect(hwnd, hmenu, i, &rect);
UnionRect(pmenurect, pmenurect, &rect);
}
return TRUE;
}
Share data between multiple processes (Windows NT)
The simplest way to share data between multiple instances of the same program is to create a new section in the executable. You must use a some form of protected access to the variable to prevent threading problems.
// Instruct compiler to put g_SharedVariable in its own section,
// so it can be shared amongst multiple instances
#pragma data_seg("Shared")
LONG g_SharedVariable = -1;
#pragma data_seg()
#pragma comment(linker, "/section:Shared,rws")
Detect when the mouse has left a window
There are four ways to detect when the mouse has left a window
- Use the
TrackMouseEvent
API and theWM_MOUSELEAVE
message (Win98/NT4+) - By using
SetCapture
API. When the window first receives aWM_MOUSEMOVE
message, set the mouse capture. When the mouse leaves the window, Windows will send the window one lastWM_MOUSEMOVE
(the coordinates will be outside the window’s client area). You can use this fact to detect when the mouse has left a window. - By using a Timer. When the mouse enters a window, set a timer going with a small interval (10ms, say). When the timer expires, check if the mouse is still in the window. If it is, then let the timer keep going. Otherwise, the mouse has left the window, and the timer can be stopped.
- By using a mouse hook. When the mouse enters a window, install a mouse hook to monitor all mouse events. By checking for
WM_MOUSEMOVE
messages, you can check when the mouse has left a window and remove the hook appropriately.
Undocumented flags for GetDCEx
(Curtesy of Feng Yuan) GetDCEx
can be used to retrieve the device context for a window during processing of WM_NCPAINT
. The documentation states that this is achieved by using
GetDCEx(hwnd, hrgn, DCX_WINDOW | DCX_INTERSECTRGN);
However, this call never works, because there is an undocumented flag to include which is not mentioned anywhere.
GetDCEx(hwnd, hrgn, DCX_WINDOW | DCX_INTERSECTRGN | 0x10000);
Generate messages during compile time
It is possible to produce messages in the compilation window when a certain line of source code is compiled. This can be very useful, as you can leave reminders or warnings that a certain section of code may need reviewing. Use the following #pragma statement to create a message to yourself whenever the source file is compiled:
#pragma MESSAGE(Add error checking here later);
This is the macro itself. Place this in a header file and include it in any source file that you want to include this message capability for.
#define chMSG(x) #x
#define chMSG2(x) chMSG(x)
#define MESSAGE(desc) message( __FILE__"(" chMSG2( __LINE__ ) ") : message : " #desc)
Undocumented Visual C stuff
You can display x number of elements in the watch window, by specifying a number after the variable name.e.g. plist, 15 will display 15 elements of the array pointed to by plist (assuming plist is a pointer).