Introduction
This section presents a few win32 programming tips that I have picked up whilst learning to program Windows. Please select a topic from the list below.
- Filling areas with a solid colour
- Dragging a window by its client area
- Get the mouse position at any time
- Automatically link in a library from a source-level command
- Calculate the point size of a font
- Prevent a window from being resized
- Change the colours of an Edit control
- Center a window relative to its parent
- Prevent a Rebar's text labels from flickering
- Create non-rectangular windows
- Update the text in a status bar
- Display the customize dialog for a toolbar
- Resize a window without having to move it as well
- Give a Rebar control a double-gripper
- Calculate the size of a menu bar
- Share data between multiple processes
- Detect when the mouse has left a window
- Undocumented flags for GetDCEx
- Generate messages during compile time
- Undocumented Visual C stuff
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);
}
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;
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.
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" );
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);
There are three ways to prevent a window from being resized.
1. Make sure the window does not have the WS_THICKFRAME style set, as this allows the user to resize the window.
2. Handle the WM_GETMINMAXINFO message.
3. Handle the WM_SIZING message.
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));
SetBkColor(hdc, RGB(255,255,0));
return GetSysColorBrush(COLOR_3DHILIGHT);
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;
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);
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;
}
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));
...
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);
Here's a useful function to set the text of any status bar pane using a printf-like call
#include <stdarg.h>
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);
SendMessage(hwndSB, SB_SETTEXT, (WPARAM)part | style, (LPARAM)(LPSTR)tmpbuf);
}
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.
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);
}
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 ;
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;
}
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;
}
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.
#pragma data_seg("Shared")
LONG g_SharedVariable = -1;
#pragma data_seg()
#pragma comment(linker, "/section:Shared,rws")
There are four ways to detect when the mouse has left a window
1. Use the TrackMouseEvent API and the WM_MOUSELEAVE message (Win98/NT4+)
2. By using SetCapture API. When the window first receives a WM_MOUSEMOVE message, set the mouse capture. When the mouse leaves the window, Windows will send the window one last WM_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.
3 . 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.
4 . 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.
(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);
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)
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).
Please send any comments or suggestions to:
james@catch22.net
Last modified: 10 October 2005 22:38:07