|
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 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 WS_EX_LAYERED and WS_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 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:
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
_InitialStyle = GetWindowLong(Me.Handle, GWL.ExStyle)
SetFormToTransparent()
Me.TopMost = True
End Sub
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)
End Sub
Private Sub SetFormToOpaque()
SetWindowLong(Me.Handle, GWL.ExStyle, _
_InitialStyle Or WS_EX.Layered)
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
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 35 (Total in Forum: 35) (Refresh) | FirstPrevNext |
|
 |
|
|
Hey, this is excellent!
Anybody knows how to translate this code to use it in VC++ ?
thanks in advance, Marcelo.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Dear Dave Kreskowiak,
Thank you very much for your article. I want to capture both a windows that below click through window and click through window but I have fail. I found that if we use Alt+PrintScreen, we can capture both of window(normal window and click through window). Could you help me to do that?
Best regards and thank you very much, Nguyen Dinh Tu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It won't work because PrintScreen won't capture a Layered, transparent window. You'd have to look into a 3rd party screen capture package to do it. I don't use any so I don't know what will work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank for your answer,
I have tried following steps: - Run ClickThrough Window.exe - Run Microsoft Word, ClickThrough Window.exe is above Microsoft Word - Press Alt+PrintScreen The result is both WinWord and ClickThrough Window.exe are capture. I have tried above operation with some other samples(that capture window image) but no one can capture both of them as using Alt+PrintScreen keyboard. Then I guess that Windows has API that can capture click-through window above a normal window but I don't know that API. Please help me if you have any information.
Thank you very much, Nguyen Dinh Tu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
PrintScreen wil capture the entire Desktop image, after all windows are drawn. Alt-PrintScreen uses different capture code to grab just the window image, which does not work well with Layered Windows, especially with an opacity set. Like I said, you have to use different capture software to grab the window image.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Dave,
Did you ever get around this doing this :-
"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"
Im in search of just an article/example!
Regards,
James.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Partially. I stopped to get married! 
I still have the project. But life has gotten so busy I just haven't had the time to finish it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Dave,
I need precisely this functionality also. So far, I have created a mouse-transparent, semi-opaque, always-on-top window in VS C++ 2005 Express using managed code. The next step I need to do is to attach the overlay window to the host so that it keeps the same appearance. Until you get around to writing the code for that, can you at least give us an overview of what you had in mind? Would it be logical to try to override the maximize, minimize, resize, etc. methods of the host window and have them also affect the overlay window? Or did you plan on doing something else?
Thanks, Charvak
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Charvak Karpe wrote: The next step I need to do is to attach the overlay window to the host so that it keeps the same appearance. Until you get around to writing the code for that, can you at least give us an overview of what you had in mind?
Well, the simple way is to get the handle of the window that you're going to overlay. Then, in a relatively tight timer loop, poll for the position and size of the target window, then match your window to those specs.
Another possibility is a WH_CBT Hook. Simply watch the message queue of the target window and look for window MOVE and SIZE messages, digging the parameters out of them, then matching them in your own window, message-for-message.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,
I was wondering how it would be possible to have my controller actives as none click trough elements and just have the form it self click trough capable, for instance my button_1 as none click trough element and by any chance would you provide some example. I would appreciate your help as I am a newbie in VB programming.
Thank You.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have no idea what you mean by "controllers".
If you're talking about some controls on a form, then no you can't do this. At least not using this technique. This only works for the entire form, and everything on it, or you can't do it using this technique.
Dave Kreskowiak Microsoft MVP Visual Developer - Visual Basic 2006, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi! This is somehow interesting for me. I'm trying to implement a click-through invisible layer over the explorer and let the user place some controls onto it. Those controls shall be visible and not click through.
As you told formerly: This is not possible? How would this then be accomplished?
Is it possible to use new dialog form objects and link their position on the transparent layer?
Cheers
Vic
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, since the entire form, and everything on it, is click-through, you have to implement something, like a key combination, that turns this on or off. You can NOT make just the form click through and not it's controls. It's all or nothing.
victor.sauermann wrote: Is it possible to use new dialog form objects and link their position on the transparent layer?
What? If I get what you're saying, not it's not. A control cannot be hosted on multiple forms at the same time.
Dave Kreskowiak Microsoft MVP Visual Developer - Visual Basic 2006, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You can make portions of you Form click-through using Regions, but those portions will also be completely invisible. If that's what you want, try this:
Region region = new Region(); region.MakeInfinity(); region.Subtract(new Rectangle(...)); region.Subtract(new Rectangle(...)); this.Region = region; //the "this" keyword here refers to the Form instance.
Now this is from memory so there are probably mistakes, but it should get you started. What this code will do is remove the two (or as many as you want) rectangles from the form entirely. They will be invisible as well as click-through. If you subtracted every part of your form that was not one of the controls you want to remain visible, that should get you what you want.
-Scott
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Dave, your trick is simple and nice, thanks for that! 
I would like make other things transparent, like Picture box or buttons inside my form, you can give me a idea about that?, my idea is graph in diferents Picturebox, and make only some graphs transparent, or put 'transparent' text in front of my graphics.
Thanks a lot Dave, my best regards!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
No, I can't. Visual transparency is a bitch to get working and control, especially when you put a transparent control over another control. All you can do is experiment to get it correct. Transparency works in some controls, but not others. It also works differently between some controls that support it. And, it'll affect what you can and cannot do to custom paint on a transparent control.
Dave Kreskowiak Microsoft MVP - Visual Basic
|
| Sign In·View Thread·PermaLink | 3.00/5 (1 vote) |
|
|
|
 |
|
|
Does anyone have a source where I might find the Hex literals for the windows constants?
I do a lot of development in powerbuilder and would need to pass the long numeric values to the external windows functions.
Thanks in advance for any help.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In the Windows Platform SDK C header files (*.h).
With Visual Studio.NET 2003, they're in C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include.
RageInTheMachine9532 "...a pungent, ghastly, stinky piece of cheese!" -- The Roaming Gnome
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Thank you!
Hopefully, they'll be more of 'em soon!
RageInTheMachine9532 "...a pungent, ghastly, stinky piece of cheese!" -- The Roaming Gnome
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm curious if you can solve a little nut I ran into. . . I wrote an application for online poker players called PartyPlanner where I provide a control which hovers over a poker table.
The app positions itself over a target window to deliver a clickable handle. I use transparency (of the visual kind) to make the rest of the area see-through to the underlying window, but I can add controls which hover in front.
The trouble is with the TopMost feature. While that keeps my control in front of the target window, it also comes in front of any other windows. If the user opens a browser and goes full-screen, they still see my clickable handle, which is now out of place. Or if they overlap windows; my form controls always come in front of all windows.
To solve this, I didn't make my forms topmost. Instead, I read the windows and z-order, and make sure my app is in front of the target window only (using SetWindowPos). . . this means that other windows can by placed without interference. HOWEVER, it means that when the user clicks my target window, the target comes to the front, and my control disappears for a moment until the z-order refresh pulls it back up.
To check out my app, called PartyPlanner, go to my wiki: http://www.overcards.com/wiki/moin.cgi/PartyPlanner
To test the app, you'll need the Party Poker game client running; if you don't have it, you can download it from Party Poker here: http://www.partypoker.com/index.htm?wm=2571299 (if you use this link, I will get credit for an affiliate referral -- pays for that wiki)
I'm not spamming -- I think this type of programming is fascinating and would love to hear what folks think about my app; I learned a lot here at codeproject and might turn my app into an article.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I'm not about to install Party Poker (I'm already on PokerStars.)
Just off the top of my head I can see problems with using TopMost. The method I'm going to be investigating in another article or two is to implement a SysMsgHook and look for WM_PAINT messages being sent to window I'm putting an overlay on. This way the overlay can paint itself after the parent window paints itself. This should take care of the problem your having and eliminate the problems with TopMost.
RageInTheMachine9532 "...a pungent, ghastly, stinky piece of cheese!" -- The Roaming Gnome
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't see why it's a concern; Party and Stars coexist peacefully.
How about this: http://www.overcards.com/downloads/MockParty.zip
It's a mock app I wrote that lets me test without being online. You can then try PartyPlanner without installing Party.
I might extend support to other sites; I'd be curious to know your thoughts about the features I've put in place and the techniques used to implement them.
I like the idea of capturing the messages for a repaint. . . I'm already capturing messages to my own form to be able to constrain the form's aspect ratio when the user re-sizes the window. . . I needed to match Party's re-size constraints. (I don't think Stars supports re-sizing the window, but other functions could be useful for Stars players.) I haven't played with SysMsgHook and grabbing messages to windows outside my own app, though. . .
Is that also how you planned to implement making your transparent form track the position of a target window -- parsing for messages that represent a location change? Capturing WM_MOVE? That would be smoother than what I've done before, which required using a GetWindowPlacement on a timer to check window positions, and then manually comparing them.
Thinking about it, I don't see how capturing for a repaint helps me; if the underlying app repaints, my app is unaffected (my app is already in front.) The only time there's a visual problem is when the user clicks on the underlying app, activating it, which brings it to the front. . . perhaps I can monitor all WM_ACTIVATE messages; when the underlying app is activated, I make my form TopMost, and when the underlying app is deactivated, I take my form off of TopMost.
I'm dying to see you next article; thanks for the input already.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It's not getting Party Poker to co-exist, but rather a time issue. I've got so little of it it's not funny. I've got a wedding comming up in a few months, so LOTS of time is going to putting that together!
Well, as far as monitoring for WM_PAINT messages, this will let you paint directly on top of the target window, if needed. But you don't really need it to paint on your own form on top of the target.
You will have to monitor for WM_MOVE/WM_MOVING and WM_SIZE/WM_SIZING messages though, no matter what method you use.
Nice! You've already figured out how to keep your form TopMost when the target window is active!
RageInTheMachine9532 "...a pungent, ghastly, stinky piece of cheese!" -- The Roaming Gnome
|
| | | | | |