Introduction
On the upcoming Windows Vista, each frame window has a cool shadow by default. Microsoft recently released the new Windows Live Messenger, which has an option to make frame windows of it shadowed. This strongly indicates that there is some way to bring shadows to application windows on operation systems other than Windows Vista. In this article, we try to accomplish this in a way similar to what WLM does.
Background
When I came up with such a problem, the first thing I did was search the web to see if someone had already done it. Unfortunately, almost all of what I have found -- some of which is on CodeProject -- is on how to make child windows shadowed, such as controls. The main idea is to draw the shadow on the parent window. The key point, however, is that if you want to make a topmost window a shadow, the shadow should be drawn on areas that do not belong to your application. Drawing on other applications' windows may mess up their screen display. Even worse, what if those windows are changed or moved?
Then I found this article written in Chinese, which suggested an excellent way to solve the drawing issue. The author created three small child windows for the frame window: on the right side, the bottom side, and the bottom-right corner each, to draw the shadow on. The shadow is drawn with alpha blending on the image of the underlying windows. When the frame window is moved or resized, the child shadow windows are also moved or resized and repainted.
However, this solution has two main shortcomings:
- If you click on the shadow, not the underlying window but the shadow window would get the focus. It is common to expect that these areas actually belong to the underlying window and are just with a shadow shaded on them.
- The shadow will look wrong when the underlying windows are moved or changed. The author has set up a timer to make the shadow periodically repaint with the newest image of the underlying windows, which makes it look better, but the problem is not solved.
Then, fortunately, I found that the layered windows supported from Windows 2000 can solve both of the above problems. Layered windows can be "transparent" to mouse actions and can be automatically alpha blended with the actual image of the underlying windows. It is even easier than the method mentioned above, for you don't actually have to worry about how to draw alpha blended. You only have to set the color -- pre-multiplied, of course -- and the alpha value for each pixel.
With those enlightenments, I observed the WLM window using Spy++. I found that this is the very method that WML uses to make its windows shadowed. There is a layered window with the class name SysShadow for each shadowed window of WML. SysShadow is actually provided by the Windows system to make certain windows shadowed, such as menus. It is easy to utilize your frame window with a SysShadow shadow window by adding a CS_DROPSHADOW
class style to the window class. Again, however, this solution has two shortcomings:
- The appearance of that shadow is not controllable or at least I didn't find some way to do so.
- That shadow is controlled by a system option. Try to uncheck the "Show shadows under menus" option in "Visual effects" of system properties; then the shadow of WML disappears!
As a result, I decided to write my own code.
Using the code
It is easy to utilize your frame window with a shadow using my code. CWndShadow
can be used in both MFC/ATL and non-MFC/ATL applications.
- Add WndShadow.h and WndShadow.cpp to your project. To anywhere
CWndShadow
is used, add:
#include "WndShadow.h"
- Call
CWndShadow::Initialize()
before any creation of a shadow. This function needs the handle to the instance of the application as a parameter. This is passed to the application as a parameter of WinMain()
. If using MFC, this can be retrieved by calling AfxGetInstanceHandle()
. Here is an example for MFC:
BOOL CYourApp::InitInstance()
{
CWndShadow::Initialize(AfxGetInstanceHandle());
}
- Declare an instance of
CWndShadow
for each frame window that you want to have a shadow:
CWndShadow WndShadow;
Remember, this object should not be destructed before the frame window is destroyed. For example, you can make it global or, if using MFC, a member of the MFC class for that window will do.
- Call the
CWndShadow
object's Create()
with the handle to the frame window in order to create a shadow. If using MFC, remember that Create()
must be called after the frame window has actually been created. For instance, in OnCreate()
for a regular window or in OnInitDialog()
for a dialog. The MFC code may look like this:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
m_Shadow.Create(GetSafeHwnd());
return 0;
}
Now your frame window should have the shadow. Enjoy.
On Windows Vista, frame windows shall already have a drop shadow when Aero is enabled or, to be more precise, when the desktop composition is enabled. The desktop composition is responsible for several Vista visual effects. This includes, but is not limited to: the glass window frame -- either translucent or opaque -- the aurora and the drop shadow. When there is already a shadow, my shadow is expected to be hidden. So, I make the shadow Aero aware with the help of the DwmIsCompositionEnabled()
API and the WM_DWMCOMPOSITIONCHANGED
window message. When Vista Aero is enabled, the shadow of my code will be disabled automatically.
Advanced control of the shadow
Some parameters can be set to control the appearance of the shadow. Here is a full list of them. I have to stress that the ranges of these parameters have not been critically tested. The shadow may not function well if they are not set around "regular" values.
bool CWndShadow::SetColor(COLORREF NewColor);
sets the color of the shadow.
bool CWndShadow::SetSize(int NewSize);
sets the relative size of the shadow window. The size is relative to the parent window, but not the absolute value. The size value is added on each of the four sides of the parent window. That is to say, if the parent window is 100*100 in size and SetSize()
is called with a value 2, the shadow window will be 104*104 in size. This parameter can be set negative.
bool CWndShadow::SetSharpness(unsigned int NewSharpness);
, the sharpness value, controls the width of the gradient translucent areas around the border of the shadow. If this is set to 0, then the shadow will have the sharpest border.
bool CWndShadow::SetHardness(unsigned int NewHardness);
sets the darkness of the shadow. The greater this value is, the darker the shadow is.
bool CWndShadow::SetPosition(int NewXOffset, int NewYOffset);
sets the relative position of the shadow window. The origins are the center of the parent window and the center of the shadow window. If the XOffset
is set positive, then the shadow will be moved right. If YOffset
is set positive, then the shadow will be moved down.
Points of interest
My code was originally written within Visual C++ 2005. When I tried to test it in Visual C++ 6.0, I met several problems. Most of them were because of the old version of the Platform SDK shipped with VC++ 6, and some because of its incompatibility with the C++ standard. As the latest version of SDK I downloaded from Microsoft does not support VC++ 6, I decided to modify the original code to make it work in VC6. The two main points are as follows:
- The
UpdateLayeredWindow()
Windows API and some correlative constants are not defined in the old SDK. Here, I use a trick similar to the one described in Quick and Dirty Window Transparency.
- I declared the shadow parameter variables as
INT8
and UINT8
, which are identical to char
and unsigned char
, but look more like integer values. Unfortunately, they are also not defined in the old SDK. So at last, I had to declare them as char
and unsigned char
, which may look confusing sometimes.
My code also involves some Vista-specific APIs and window messages to make my code Aero awareness. This unfortunately requires the Vista SDK installed to compile. Even worse, if these APIs are hard coded in the code, the application will crash on Windows versions prior to Vista. So, I also use the trick similar to point 1 above to integrate these Vista stuffs into my code.
Future works
It seems to be difficult to simulate the window shadow in Windows Vista using the current CWndShadow
. There are two main reasons:
- Windows Vista -- as well as WLM -- uses a non-linear gradient function around the border of the shadow.
- Windows Vista uses a different model to generate the shadow, while mine is similar to WLM. And shadows generated by the Vista model seem to have to be larger than the parent window in size.
I'm considering adding support for a non-gradient border function and both of those two shadow models in future versions of CWndShadow
.
Known issues
- My
CWndShadow
uses the parent window region to calculate the shape of the shadow. It seems that under certain conditions of window sizing, the parent window region may not be correctly updated, and so some part of the shadow may look wrong until the sizing is finished, i.e., the mouse button is up.
- If compiled in VC++ 6, my code will get several C4786 warnings because of the use of
std::map
. This is a known issue of the VC++ 6 compiler and should not make anything wrong to the execution of the code.
History
- Version 0.3, 2007-06-14
- The shadow is made Windows Vista Aero aware.
- Fixed a bug that causes the shadow to appear abnormally on Windows Vista.
- Fixed a bug that causes the shadow to appear abnormally if the parent window is initially minimized or maximized.
- Version 0.2, 2006-11-23
- Fixed a critical issue that may make the shadow fail to work under certain conditions, e.g., on Win2K, on Win XP/2003 without the visual theme enabled, or when the frame window does not have a caption bar.
- Version 0.1, 2006-11-10