Click Through Transparency for Visual C# Window Forms

Click-through in C# Form

You can do this with SetWindowLong:

int initialStyle = GetWindowLong(this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);

There are a few magic numbers in here:

  • -20 – GWL_EXSTYLE

    Retrieves the extended window styles.

  • 0x80000 – WS_EX_LAYERED

    Creates a layered window.

  • 0x20 – WS_EX_TRANSPARENT

    Specifies that a window created with this style should not be painted until siblings beneath the window (that were created by the same thread) have been painted. The window appears transparent because the bits of underlying sibling windows have already been painted.

There are numerous articles all over the web on how to do this, such as this one.

Click through transparency for Visual C# Window Forms?

Creating a top level form that is transparent is very easy. Just make it fill the screen, or required area, and define it to have a TransparenyKey color and BackColor of the same value.

Getting it to ignore the mouse is simple enough, you just need to override the WndProc and tell the WM_HITTEST that all mouse positions are to be treated as transparent. Thus causing the mouse to interact with whatever happens to be underneath the window. Something like this...

    protected override void WndProc(ref Message m)
{
if (m.Msg == (int)WM_NCHITTEST)
m.Result = (IntPtr)HTTRANSPARENT;
else
base.WndProc(ref m);
}

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

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.

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;

C# WinForms transparent click-through control to paint on

Even a transparent canvas like this would have to redraw its background (which would include the things behind it). Perhaps there is some hack around this but every time I've run into problems with component flicker there has never been a really satisfying solution, including leveraging component (or even custom) double-buffering. When live graphical interaction like this becomes a necessary part of the application I've always found that the best option is to move to something which does graphics 'the right way'. WPF, XNA, or DirectX are probably the best answers to this problem. WPF also adds things like routed events which make this kind of one-event->many components paradigm much more straightforward to code.

Here is an example using the WPF Interoperability component in a winforms application :

1) Add a new ElementHost to your form (I called it elementHost1 here)

2) Add a new WPF UserControl to your project (I called it TreeCanvas)

XAML

<UserControl x:Class="WindowsFormsApplication1.TreeCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
<Canvas Name="Canvas1">
<TreeView Canvas.Left="0" Canvas.Top="0" Height="300" Name="TreeView1" Width="300" />
</Canvas>
</Grid>

Code behind for TreeCanvas needs nothing - the generated code with just InitializeComponent(); is all you need for now.

And your form code

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
//make a new TreeCanvas

private TreeCanvas MyTreeCanvas = new TreeCanvas();

public Form1()
{
InitializeComponent();
//attach the TreeCanvas component to the element host
this.Width = 400;
this.Height = 400;
elementHost1.Child = MyTreeCanvas;
elementHost1.Location = new System.Drawing.Point(30, 30);
elementHost1.Height = 300;
elementHost1.Width = 300;

// Just adding some random stuff to the treeview
int i = 0;
int j = 0;
for (i = 0; i <= 10; i++)
{
TreeViewItem nitm = new TreeViewItem();
nitm.Header = "Item " + Convert.ToString(i);
MyTreeCanvas.TreeView1.Items.Add(nitm);
for (j = 1; j <= 3; j++)
{
TreeViewItem itm = (TreeViewItem)MyTreeCanvas.TreeView1.Items[i];
itm.Items.Add("Item " + Convert.ToString(j));
}
}

//Draw a line on the canvas with the treeview
Line myLine = new Line();
myLine.Stroke = System.Windows.Media.Brushes.Red;
myLine.X1 = 1;
myLine.X2 = 50;
myLine.Y1 = 1;
myLine.Y2 = 300;
myLine.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
myLine.VerticalAlignment = VerticalAlignment.Center;
myLine.StrokeThickness = 2;
MyTreeCanvas.Canvas1.Children.Add(myLine);
}

}
}

This gives you a treeview inside a canvas, on top which you can paint on while still being able to click and operate the treeview beneath(including mouse scroll events, etc).

If you click DIRECTLY on a line the click will not go through, and likewise if the mouse is hovering DIRECTLY over a line on the canvas then things like scroll events will not go through but if you read up on Routed Events you can pretty easily wire them together from within the TreeCanvas class.



Related Topics



Leave a reply



Submit