Transparent Window Layer That Is Click-Through and Always Stays on Top

Transparent window layer that is click-through and always stays on top

Here's a refined full sample code for making a window topmost - click through - transparent (= alpha blended). The sample makes a rotating color wheel which is rendered with DirectX, or actually with XNA 4.0, because I believe Microsoft has discontinued developing the managed directx and favours XNA today.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Graphics;

namespace ClickThroughXNA
{
public partial class Form1 : Form
{
// Directx graphics device
GraphicsDevice dev = null;
BasicEffect effect = null;

// Wheel vertexes
VertexPositionColor[] v = new VertexPositionColor[100];

// Wheel rotation
float rot = 0;

public Form1()
{
InitializeComponent();

StartPosition = FormStartPosition.CenterScreen;
Size = new System.Drawing.Size(500, 500);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // no borders

TopMost = true; // make the form always on top
Visible = true; // Important! if this isn't set, then the form is not shown at all

// Set the form click-through
int initialStyle = GetWindowLong(this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);

// Create device presentation parameters
PresentationParameters p = new PresentationParameters();
p.IsFullScreen = false;
p.DeviceWindowHandle = this.Handle;
p.BackBufferFormat = SurfaceFormat.Vector4;
p.PresentationInterval = PresentInterval.One;

// Create XNA graphics device
dev = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, p);

// Init basic effect
effect = new BasicEffect(dev);

// Extend aero glass style on form init
OnResize(null);
}

protected override void OnResize(EventArgs e)
{
int[] margins = new int[] { 0, 0, Width, Height };

// Extend aero glass style to whole form
DwmExtendFrameIntoClientArea(this.Handle, ref margins);
}

protected override void OnPaintBackground(PaintEventArgs e)
{
// do nothing here to stop window normal background painting
}

protected override void OnPaint(PaintEventArgs e)
{
// Clear device with fully transparent black
dev.Clear(new Microsoft.Xna.Framework.Color(0, 0, 0, 0.0f));

// Rotate wheel a bit
rot+=0.1f;

// Make the wheel vertexes and colors for vertexes
for (int i = 0; i < v.Length; i++)
{
if (i % 3 == 1)
v[i].Position = new Microsoft.Xna.Framework.Vector3((float)Math.Sin((i + rot) * (Math.PI * 2f / (float)v.Length)), (float)Math.Cos((i + rot) * (Math.PI * 2f / (float)v.Length)), 0);
else if (i % 3 == 2)
v[i].Position = new Microsoft.Xna.Framework.Vector3((float)Math.Sin((i + 2 + rot) * (Math.PI * 2f / (float)v.Length)), (float)Math.Cos((i + 2 + rot) * (Math.PI * 2f / (float)v.Length)), 0);

v[i].Color = new Microsoft.Xna.Framework.Color(1 - (i / (float)v.Length), i / (float)v.Length, 0, i / (float)v.Length);
}

// Enable position colored vertex rendering
effect.VertexColorEnabled = true;
foreach (EffectPass pass in effect.CurrentTechnique.Passes) pass.Apply();

// Draw the primitives (the wheel)
dev.DrawUserPrimitives(PrimitiveType.TriangleList, v, 0, v.Length / 3, VertexPositionColor.VertexDeclaration);

// Present the device contents into form
dev.Present();

// Redraw immediatily
Invalidate();
}

[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref int[] pMargins);

}
}

Windows Forms: Pass clicks through a partially transparent always-on-top window

You can make a window, click-through by adding WS_EX_LAYERED and WS_EX_TRANSPARENT styles to its extended styles. Also to make it always on top set its TopMost to true and to make it semi-transparent use suitable Opacity value:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Opacity = 0.5;
this.TopMost = true;
}
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
const int GWL_EXSTYLE = -20;
const int WS_EX_LAYERED = 0x80000;
const int WS_EX_TRANSPARENT = 0x20;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var style = GetWindowLong(this.Handle, GWL_EXSTYLE);
SetWindowLong(this.Handle,GWL_EXSTYLE , style | WS_EX_LAYERED | WS_EX_TRANSPARENT);
}
}

Sample Result

Sample Image

Topmost form, clicking through possible?

You can use SetWindowLong to set the WS_EX_TRANSPARENT window style:

If the layered window has the WS_EX_TRANSPARENT extended window style, the shape of the layered window will be ignored and the mouse events will be passed to the other windows underneath the layered window.

CodeProject has this article detailing the technique. Though it's in VB.NET it should be easy to convert to C#.

I have used the following code in the past:

public enum GWL
{
ExStyle = -20
}

public enum WS_EX
{
Transparent = 0x20,
Layered = 0x80000
}

public enum LWA
{
ColorKey = 0x1,
Alpha = 0x2
}

[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
public static extern int GetWindowLong(IntPtr hWnd, GWL nIndex);

[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
public static extern int SetWindowLong(IntPtr hWnd, GWL nIndex, int dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, int crKey, byte alpha, LWA dwFlags);

protected override void OnShown(EventArgs e)
{
base.OnShown(e);
int wl = GetWindowLong(this.Handle, GWL.ExStyle);
wl = wl | 0x80000 | 0x20;
SetWindowLong(this.Handle, GWL.ExStyle, wl);
SetLayeredWindowAttributes(this.Handle, 0, 128, LWA.Alpha);
}

but it also was copied from somewhere else. The important lines here are in the OnShown method. Though I have to admit that the line

wl = wl | 0x80000 | 0x20;

is a little cryptic, setting the WS_EX_LAYERED and WS_EX_TRANSPARENT extended styles.

You can probably also set it like

wl = wl | WS_EX.Layered | WS_EX.Transparent;

Transparent Windows Form that can handle Click

If I understand your question correctly, you can Use TrancparencyKey

Set TrancparencyKey and BackColor properties both to same color like Color.Red.

Here is the screenshot of transparent form over visual studio:

Sample Image

Note:

  • When you use for example Color.Red every thing works fine and you can handle mouse Click. But the behavior is different for different colors, for example Color.Magenta the form can not capture the mouse Click.

Click-through Transparent window, no dragging allowed [C++]

The click-through part:

Indeed, WS_EX_TRANSPARENT by itself is a big lie; so I used WS_EX_COMPOSITED | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST instead.

I have control over the opacity using SetLayeredWindowAttributes(hWnd, 0, (255 * opacity) / 100, LWA_ALPHA); (quite unorthodox, but it works) and I also use

SetCapture(hWnd);
ShowCursor(false);

to grab the mouse focus as the top level window doesn't let go and hides the cursor.

I also tried to force the focus on the window adding WM_NCACTIVATE and WM_ACTIVEAPP:

case WM_MOUSEMOVE:
fprintf(stdout, "Mouse move [%d][%d]\n", GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
SetForegroundWindow(hWnd);
break;

case WM_LBUTTONDOWN:
printf("Mouse click\n");
SetForegroundWindow(hWnd);
break;

case WM_NCACTIVATE:
return false;

case WM_ACTIVATEAPP:
wActive = (bool)wParam;

if(wActive == false)
return 0;
else
return DefWindowProc(hWnd, message, wParam, lParam);

The dragging part:

In my particular case I wanted to 'poke' the window underneath (child window) without losing focus; unfortunately, any mouse click event will change the focus to that child window - a solution would be to:

  1. set a timer (SetTimer, WM_TIMER) and check whether your application lost focus or not
  2. set a hook to your window and reply to a WM_KILLFOCUS message with a WM_SETFOCUS message

click through transparent window - does not work on all computers

the following solves my problem thanks to Jimi's comments:

Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
If Not DesignMode Then parms.ExStyle = parms.ExStyle Or &H80000 Or &H20
Return parms
End Get
End Property
Private Sub frmDrawing_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim InitialStyle As Integer
'Dim PercentVisible As Decimal

InitialStyle = GetWindowLong(Me.Handle, -20)
'PercentVisible = 0.4
Dim parms As CreateParams = MyBase.CreateParams
'parms.ExStyle = parms.ExStyle Or &H80000 Or &H20
'SetWindowLong(Me.Handle, -20, InitialStyle Or &H80000 Or &H20)
'MsgBox(CStr(parms.ExStyle) & ":" & CStr(InitialStyle Or &H80000 Or &H20))
SetWindowLong(Me.Handle, -20, parms.ExStyle)

'SetLayeredWindowAttributes(Me.Handle, 0, 255 * PercentVisible, &H2)

Me.BackColor = System.Drawing.Color.LightBlue
Me.TransparencyKey = System.Drawing.Color.LightBlue

Me.TopMost = True
End Sub


Related Topics



Leave a reply



Submit