Transparent, Click-Through Forms






4.75/5 (44 votes)
How to make a form transparent to the mouse, or click-through, so that mouse clicks end up going to whatever is behind the transparent form.

Introduction
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.
Layered Windows
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 GetWindowLong
and SetWindowLong
. I'll show you how to use these functions to set the WS_EX_LAYERED
and 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".
Transparent Windows
The 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 Get/SetWindowLong
and SetLayeredWindowAttributes
. You can find the same things on PInvoke.net, or derive it yourself from the Platform SDK documentation.
Imports System.Runtime.InteropServices
Public Class User32Wrappers
Public Enum GWL As Integer
ExStyle = -20
End Enum
Public Enum WS_EX As Integer
Transparent = &H20
Layered = &H80000
End Enum
Public Enum LWA As Integer
ColorKey = &H1
Alpha = &H2
End Enum
<DllImport("user32.dll", EntryPoint:="GetWindowLong")> _
Public Shared Function GetWindowLong( _
ByVal hWnd As IntPtr, _
ByVal nIndex As GWL _
) As Integer
End Function
<DllImport("user32.dll", EntryPoint:="SetWindowLong")> _
Public Shared Function SetWindowLong( _
ByVal hWnd As IntPtr, _
ByVal nIndex As GWL, _
ByVal dwNewLong As WS_EX _
) As Integer
End Function
<DllImport("user32.dll", _
EntryPoint:="SetLayeredWindowAttributes")> _
Public Shared Function SetLayeredWindowAttributes( _
ByVal hWnd As IntPtr, _
ByVal crKey As Integer, _
ByVal alpha As Byte, _
ByVal dwFlags As LWA _
) As Boolean
End Function
End Class
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 fornIndex
- 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-wiseOR
operation. We want to turn on the bits specified byWS_EX_LAYERED
andWS_EX_TRANSPARENT
.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 toGetWindowLong()
. 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 forcrRef
- Specifies a transparency color key. All pixels with this color in the form will be transparentbAlpha
- Specifies the opacity of the window, 0 (transparent) through 255 (opaque)dwFlags
- Specifies an action to takeLWA_COLORKEY
- UsescrKey
as the transparency colorLWA_ALPHA
- UsesbAlpha
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:
Imports WindowLibrary.User32Wrappers
Public Class Form1
Private _InitialStyle As Integer
Private Sub Form_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
' Grab the Extended Style information
' for this window and store it.
_InitialStyle = GetWindowLong(Me.Handle, GWL.ExStyle)
' Set this window to Transparent
' (to the mouse that is!)
SetFormToTransparent()
' Just for giggles, set this window
' to stay on top of all others so we
' can see what's happening.
Me.TopMost = True
End Sub
Private Sub SetFormToTransparent()
' This creates a new Extended Style
' for our window, which takes effect
' immediately upon being set, that
' combines the initial style of our window
' (saved in Form.Load) and adds the ability
' to be Transparent to the mouse.
' Both Layered and Transparent must be
' turned on for this to work AND have
' the window render properly!
SetWindowLong(Me.Handle, GWL.ExStyle, _
_InitialStyle Or WS_EX.Layered Or WS_EX.Transparent)
' Don't forget to set the Alpha
' for the window or else you won't be able
' to see the window! Possible values
' are 0 (visibly transparent)
' to 255 (visibly opaque). I'll set
' it to 70% visible here for show.
' The second parameter is 0, because
' we're not using a ColorKey!
SetLayeredWindowAttributes(Me.Handle, 0, _
255 * 0.7, LWA.Alpha)
End Sub
Private Sub SetFormToOpaque()
' Turn off the Transparent Extended Style.
SetWindowLong(Me.Handle, GWL.ExStyle, _
_InitialStyle Or WS_EX.Layered)
' Set the Alpha back to 100% opaque.
SetLayeredWindowAttributes(Me.Handle, _
0, 255, LWA.Alpha)
End Sub
End Class
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.
References
- MSDN Magazine, C++ At Work: Layered Windows, Blending Images
- GetWindowLong Function
- SetWindowLong Function
- SetLayeredWindowAttributes Function
- Platform SDK: Windows API - Windows Data Types
History
- 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 webmaster@codeproject.com. Thank you.