Introduction
One of the newer features of Windows is the ability to use what's known as compositing to draw Windows onto the desktop. Put simply, this makes it possible to make a window transparent, so that the background can be seen through the window. While this can be a visual distraction, it can also be used occasionally to provide useful feedback to the user. In Windows 2000 and XP window transparency is used during some kinds of drag and drop operations to render icons as they are dragged across the screen; These operating systems also use window transparency to achieve menu fade effects.
This article aims to provide an introduction to using layered windows for transparency, as well as a simple function that allows transparency to be used on newer versions of Windows, without breaking compatibility with Windows98. Hopefully, with the technique and code presented in this article, transparency effects can be added to your application with only one or two new lines of code, and no alteration to existing drawing logic. Before I continue, let me extend my sincere thanks to other articles that provided the inspiration for this one.
Making a Window Transparent
The way Windows 2000 and XP support transparent windows is via an extended style bit, WS_EX_LAYERED
. When this bit is set, calling SetLayeredWindowAttributes
on a window causes Windows to redirect all window paint operations to an off-screen bitmap. Windows then can use this off-screen bitmap to draw the window onto the screen using transparency effects. (It's also possible to take more control over the layered window update process by calling UpdateLayeredWindow
, but that is beyond the scope of this article. For more details, see the Microsoft technical article Layered Windows: A New Way to Use Translucency and Transparency Effects in Windows Applications .)
The most straightforward approach to adding transparency to your windows is to simply set the style bit and call SetLayeredWindowAttributes
. The problem with this approach is that it will fail on Windows 98. An application written this way will attempt to link to SetLayeredWindowAttributes
at load time; On Windows98, since the API call does not exist, the linkage will fail, the program will not run, and a cryptic Windows error message will be displayed to your customers in a MessageBox
. A much safer approach is to use LoadLibrary
and GetProcAddress
to control the process of linking to the API manually. The code that does this is a common Windows idiom:
typedef DWORD (WINAPI *PSLWA)(HWND, DWORD, BYTE, DWORD);
static PSLWA pSetLayeredWindowAttributes = NULL;
static BOOL initialized = FALSE;
BOOL MakeWindowTransparent(HWND hWnd, unsigned char factor)
{
if (!initialized)
{
HMODULE hDLL = LoadLibrary ("user32");
pSetLayeredWindowAttributes =
(PSLWA) GetProcAddress(hDLL, "SetLayeredWindowAttributes");
initialized = TRUE;
}
}
This code programmatically attempts to link to SetLayeredWindowAttributes
, and if it fails, leaves pSetLayeredWindowAttributes
set to NULL. Otherwise, pSetLayeredWindowAttributes
is a pointer to the API function. This enables the API to be called, while avoiding the problems associated with linking to the API statically.
Using the MakeWindowTransparent
Function
The library code attached to this article is designed to be as easy to develop with as possible. After adding the files MakeWindowTransparent.cpp and MakeWindowTransparent.h to your project, you're almost all the way there. The only major sticking point is that MakeWindowTransparent.cpp relies on some Windows constants that are only defined in Windows 2000 and later versions of the operating system. To gain access to these constants, you must either make the following preprocessor definition before stdafx.h, or modify the corresponding definition already in stdafx.h. While neither of these are likely to break your build, both of them are global changes, and should be tested accordingly, no matter which one you pick.
#define _WIN32_WINNT 0x0500
Once this is done, all that remains to be done is include MakeWindowTransparent.h in your source files and call MakeWindowTransparent
on the windows that are to be made transparent. For the sake of convenience, there are two definitions of the function, one takes an MFC CWnd
, one takes a standard Windows window handle:
bool MakeWindowTransparent(HWND hWnd, BYTE factor);
bool MakeWindowTransparent(CWnd *w, BYTE factor);
The other parameter to MakeWindowTransparent
, factor
, determines how transparent the window will become. A value of 0
will make the window completely transparent, a value of 0xFF
will make the window completely opaque. It's also worth pointing out that there's no restriction on calling MakeWindowTransparent
for a given window more than once. This can be done to vary the transparency of a window as a program executes.
The return value of MakeWindowTransparent
is true if the function succeeded, and false if it failed. Failure can be reported when the function is called on versions of Windows that do not support layered windows or transparency. In the event that the call to make the window transparent fails, one way to gracefully degrade might be the API function SetWindowRgn
It can be used on earlier versions of windows to set a window's shape to be non-rectangular. I've used it successfully in the past to good effect.
Working Without MFC
Despite its support of MFC's CWnd, this little library does not have any real dependencies on MFC. In non-MFC applications, this function is all you need:
typedef DWORD (WINAPI *PSLWA)(HWND, DWORD, BYTE, DWORD);
static PSLWA pSetLayeredWindowAttributes = NULL;
static BOOL initialized = FALSE;
BOOL MakeWindowTransparent(HWND hWnd, unsigned char factor)
{
if (!initialized)
{
HMODULE hDLL = LoadLibrary ("user32");
pSetLayeredWindowAttributes =
(PSLWA) GetProcAddress(hDLL, "SetLayeredWindowAttributes");
initialized = TRUE;
}
if (pSetLayeredWindowAttributes == NULL)
return FALSE;
SetLastError(0);
SetWindowLong(hWnd,
GWL_EXSTYLE ,
GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
if (GetLastError())
return FALSE;
return pSetLayeredWindowAttributes (hWnd,
RGB(255,255,255),
factor,
LWA_COLORKEY|LWA_ALPHA);
}
But I don't want to modify _WIN32_WINNT
!
If the thought of making a global change to your Windows header includes makes you queasy, it can be avoided by making the definitions yourself in MakeWindowTransparent.cpp:
#define WS_EX_LAYERED 0x00080000
#define LWA_COLORKEY 0x00000001
#define LWA_ALPHA 0x00000002
Obviously, re-declaring Windows API constants is really bad form, but it is a way around making such a far reaching change to your build, if you don't have the time to do it right.
Conclusion
Window transparency is a powerful effect when used with discretion. If the transparent window is too complex, as in a terminal window, it can create serious readability problems for your customers. That said, transparency can provide useful visual feedback as well as a more modern style. Not only is it used successfully in Windows 2000 and XP, it's also well supported in Windows Forms, Mac OS X, and will be a key part of Avalon, the graphics system used in upcoming versions of Windows. I hope that this article, and the associated code, is helpful in bringing this capability to your existing MFC and Win32 applications.