Have you ever wondered how to make a form ignore the mouse so the clicks get sent to the application, or whatever else, is behind your form? Perhaps you want to create an application that overlays a window with one of your own, but you wanted your window to be "transparent" to the mouse. It's pretty easy if you know the correct attributes and how to apply them to your form! The trick lies in "Layered Windows". I'll discuss how these Layered Windows work, and how to use them to pull off a "click-through" window in your Windows Forms applications.
With the introduction of Windows 2000, the User Interface (UI) got a much needed face-lift through prettier graphics and ease-to-use improvements. You may have noticed some of these improvements, like the little shadow that now lies under the mouse pointer, fade-in and -out menus and ToolTips, alpha-blended dragging of objects around the Shell (Explorer), non-rectangular window shapes, and visually transparent (or alpha-blended) windows, to name a few. All these UI effects, and more, were made possible with the introduction of a new extended window style, called Layered Windows, in Windows 2000 Beta 3.
When a window is created, it has a standard set of attributes attached to it, like its name, position, size, style, and window handle, among others. Most of these attributes can be set or reset at any time, either during or after the window is created, by calling the Win32 API functions
SetWindowLong. I'll show you how to use these functions to set the
WS_EX_TRANSPARENT window extended attributes to turn your .NET Framework Form windows into Layered Windows. I'll also show you how to use the
SetLayeredWindowAttribute function to change the opacity of the window to make them "see-through" as well as "click-through".
WS_EX_TRANSPARENT extended window style bit is poorly documented. Its definition in the documentation on MSDN says that all windows beneath this window will not be obscured by this window. It goes on to say that this window will also get the
Paint message (
WM_PAINT) only after all the sibling windows (in the same application) beneath it have received and processed their
Paint messages. What on earth does that mean? Well, it's not defined anywhere. That's the only description of what this bit does. It takes some trial and error to figure out what's really going on.
With some playing around, you can discover that windows, with the Transparency bit set, are invisible to mouse clicks! It's supposed to be not just anywhere you click on a form (window). Take the example of a window with a random blob shape, sort of like a cloud. Well, with normal, plain-old windows, every window is rectangular in shape. Everywhere you click between the top-left corner and bottom-right corner of a window falls within the bounds of that window. It's very easy to determine which window actually got clicked on. But, in our cloud-shaped window, there is no usable bounds to check where the mouse clicked. The window "frame" is a random line that can't be described in a simple mathematical formula. Without that simple formula, trying to figure out if the mouse click actually hit inside the cloud-shaped form can be a huge computational task! But there is an easier method.
A layered window, when drawn, is not drawn directly to the screen. It's drawn to an off-screen buffer and converted to a bitmap that uses an Alpha Channel. What's that? An Alpha channel basically describes which pixels in an image are visibly transparent and how much the background shows through those pixels. For example, in a 32-bit image, you can have Red, Green, and Blue color values, each 8-bits wide, that describe the color of a single pixel at any point in the image. You can also have an 8-bit value describing how much that pixel is visibly transparent. This says how much of the color that's behind that pixel, when drawn to the screen, comes through and affects the corresponding pixel in the image. When the window is finished drawing to this off-screen buffer, the completed buffer image is copied to the visible screen, pixel-by-pixel, remembering to take into account the alpha values at each pixel location.
Now, how does the Alpha channel help in determining if the form was hit by a mouse click or not? Well, in the off-screen buffer, the form still occupies a rectangular area of the screen. It just has an oddly-shaped, alpha-blended image inside it. Some parts of the image will have an alpha value of 255 (not visibly transparent at all) down to 0 (completely transparent). This is what gives our form its shape on the visible screen. The Alpha channel can be checked quite easily and quickly, just like in a rectangular form, to see if the mouse hit an area of the form that is visible or not. If the alpha value at the point that the mouse was clicked (mapped to the off-screen buffer) is less than 255, the form was missed and the mouse click goes to the window that is under our window! Simple, isn't it? But, that's not how it works in practice. Setting the Transparent bit turns the entire form invisible to the mouse, "no matter what the Alpha channel value says" is supposed to happen at the spot the mouse is clicked! It appears that a form is transparent, "all or nothing"!
A Transparent Window Example
In order to get a form to be "click-throughable", you have to modify the Extended Style attributes of the window that is your form. How do we do that? We'll start with a small wrapper library.
The following library is pretty standard, just quick-and-dirty stuff. No error checking and just the bare-bone things we need to get the job done. It just defines the constants and functions we need to call
SetLayeredWindowAttributes. You can find the same things on PInvoke.net, or derive it yourself from the Platform SDK documentation.
Public Class User32Wrappers
Public Enum GWL As Integer
ExStyle = -20
Public Enum WS_EX As Integer
Transparent = &H20
Layered = &H80000
Public Enum LWA As Integer
ColorKey = &H1
Alpha = &H2
<DllImport("user32.dll", EntryPoint:="GetWindowLong")> _
Public Shared Function GetWindowLong( _
ByVal hWnd As IntPtr, _
ByVal nIndex As GWL _
) As Integer
<DllImport("user32.dll", EntryPoint:="SetWindowLong")> _
Public Shared Function SetWindowLong( _
ByVal hWnd As IntPtr, _
ByVal nIndex As GWL, _
ByVal dwNewLong As WS_EX _
) As Integer
Public Shared Function SetLayeredWindowAttributes( _
ByVal hWnd As IntPtr, _
ByVal crKey As Integer, _
ByVal alpha As Byte, _
ByVal dwFlags As LWA _
) As Boolean
Truth be told, this trick is ridiculously easy to pull off. To make a form "click-through", all you need to do is:
- Grab the current value of the Extended Style attributes for the window you want to be invisible to the mouse. This requires a call to the Win32 API function
GetWindowLong(hWnd, nIndex). The two parameters are as follows:
hWnd - The handle to the window we want to get the attributes for
nIndex - The zero-based offset to the value to be retrieved. In our case,
GWL_EXSTYLE, or -20
- Modify the value returned by
GetWindowLong() and turn on the bits you want. This is done using a bit-wise
OR operation. We want to turn on the bits specified by
newValue = oldValue Or WS_EX_LAYERED Or WS_EX_TRANSPARENT
- Write the new value back to the window by calling
SetWindowLong(hWnd, nIndex, dwNewLong). The first two are exactly the same as the call to
GetWindowLong(). The third is, obviously, the new value we want written to the window.
- Now that the window is a Layered Window, we have to modify another attribute of the form, the Alpha attribute. If this is not done, the default value for Alpha will be 0, or visibly transparent, and we won't see our form at all! This is accomplished with a call to
SetLayeredWindowAttributes(hWnd, crKey, bAlpha, dfFlags).
hWnd - The handle to the window we want to set the attribute for
crRef - Specifies a transparency color key. All pixels with this color in the form will be transparent
bAlpha - Specifies the opacity of the window, 0 (transparent) through 255 (opaque)
dwFlags - Specifies an action to take
LWA_COLORKEY - Uses
crKey as the transparency color
LWA_ALPHA - Uses
bAlpha to determine the opacity of the window
In the following example, the entire form is going to have its Alpha changed, not just parts of it, so we won't be using a color key. Putting the above wrapper library to use:
Public Class Form1
Private _InitialStyle As Integer
Private Sub Form_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
_InitialStyle = GetWindowLong(Me.Handle, GWL.ExStyle)
Me.TopMost = True
Private Sub SetFormToTransparent()
SetWindowLong(Me.Handle, GWL.ExStyle, _
_InitialStyle Or WS_EX.Layered Or WS_EX.Transparent)
SetLayeredWindowAttributes(Me.Handle, 0, _
255 * 0.7, LWA.Alpha)
Private Sub SetFormToOpaque()
SetWindowLong(Me.Handle, GWL.ExStyle, _
_InitialStyle Or WS_EX.Layered)
0, 255, LWA.Alpha)
The example code here is pretty simple. To see what it does, all you do is start the app and see what happens when you try to click on the form. It's easiest to see when you have a text editor window open behind this example form. It operates in two modes, Transparent and Active, and starts up in Transparent mode. If you click the mouse anywhere in the window, the click falls through to the window behind it. To make the sample window Active, make sure the form has the focus by clicking on its icon in the TaskBar, then hold down the Shift and Ctrl keys. You should see the form's opacity change. Now, any mouse clicks on the form will not fall through and you'll be able to use the form's controls.
In order to close the sample, you'll have to right-click its icon on the TaskBar and click Close.
In my next article, I'll show you how to use this technique in an overlay window that attaches itself to a "host" window, follows it, and resizes itself to match the dimensions of the host. It'll also display some information over the top of the host window and, of course, be completely invisible to the mouse.
- Article v1.00 - Jan 29, 2006
This article was written expressly for The CodeProject. If you find this article, in part or in whole, on any other website, it is a blatent copyright violation. Please inform The CodeProject staff of this at email@example.com. Thank you.