Win32 Tips & Tricks


9 minute read  • 

win32

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.

  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.

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-&gt;dwDrawStage == CDDS_PREPAINT)
    {
        return CDRF_NOTIFYPOSTPAINT;
    }
    else if(lpcd-&gt;dwDrawStage == CDDS_POSTPAINT &amp;&amp; lpcd-&gt;dwItemSpec != 0)
    {
    
        SetRect(&amp;rect, 5, 2, 8, 38);
        DrawEdge(lpcd-&gt;hdc, &amp;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 &lt; GetMenuItemCount(hmenu); i++)
    {
        GetMenuItemRect(hwnd, hmenu, i, &amp;rect);
        UnionRect(pmenurect, pmenurect, &amp;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

  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.

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).