36

This is some code that I picked up which I tried to implement. Its purpose is to create a form layer which is transparent, full screen, borderless, clickthrough, and always on top of other windows. It then lets you draw using directx over the top of it remaining otherwise transparent.

The parts that don't work are the click-through part, and the directx render. When I run it I basically have an invisible force field in front of all other windows and have to alt-tab around to visual studio to quickly press ALT F5 and end the debug (so at least the always on top and transparency works). I have been trying to figure out why those parts don't work, but my newbie c# skills fail me. hopefully someone can spot why and provide a modification.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Globalization;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Threading;


namespace MinimapSpy
{
public partial class Form1 : Form
{

    private Margins marg;

    //this is used to specify the boundaries of the transparent area
    internal struct Margins
    {
        public int Left, Right, Top, Bottom;
    }

    [DllImport("user32.dll", SetLastError = true)]

    private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]

    static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    [DllImport("user32.dll")]

    static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

    public const int GWL_EXSTYLE = -20;

    public const int WS_EX_LAYERED = 0x80000;

    public const int WS_EX_TRANSPARENT = 0x20;

    public const int LWA_ALPHA = 0x2;

    public const int LWA_COLORKEY = 0x1;

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

    private Device device = null;



    public Form1()
    {

        //Make the window's border completely transparant
        SetWindowLong(this.Handle, GWL_EXSTYLE,
                (IntPtr)(GetWindowLong(this.Handle, GWL_EXSTYLE) ^ WS_EX_LAYERED ^ WS_EX_TRANSPARENT));

        //Set the Alpha on the Whole Window to 255 (solid)
        SetLayeredWindowAttributes(this.Handle, 0, 255, LWA_ALPHA);

        //Init DirectX
        //This initializes the DirectX device. It needs to be done once.
        //The alpha channel in the backbuffer is critical.
        PresentParameters presentParameters = new PresentParameters();
        presentParameters.Windowed = true;
        presentParameters.SwapEffect = SwapEffect.Discard;
        presentParameters.BackBufferFormat = Format.A8R8G8B8;

        this.device = new Device(0, DeviceType.Hardware, this.Handle,
        CreateFlags.HardwareVertexProcessing, presentParameters);


        Thread dx = new Thread(new ThreadStart(this.dxThread));
        dx.IsBackground = true;
        dx.Start();  
        InitializeComponent();

    }

   protected override void OnPaint(PaintEventArgs e)
   {
        //Create a margin (the whole form)
      marg.Left = 0;
     marg.Top = 0;
      marg.Right = this.Width;
      marg.Bottom = this.Height;

        //Expand the Aero Glass Effect Border to the WHOLE form.
        // since we have already had the border invisible we now
        // have a completely invisible window - apart from the DirectX
        // renders NOT in black.
     DwmExtendFrameIntoClientArea(this.Handle, ref marg);  

  }
    private void Form1_Load(object sender, EventArgs e)
    {

    }
    private void dxThread()
    {
        while (true)
        {
            //Place your update logic here
            device.Clear(ClearFlags.Target, Color.FromArgb(0, 0, 0, 0), 1.0f, 0);
            device.RenderState.ZBufferEnable = false;
            device.RenderState.Lighting = false;
            device.RenderState.CullMode = Cull.None;
            device.Transform.Projection = Matrix.OrthoOffCenterLH(0, this.Width, this.Height, 0, 0, 1);
            device.BeginScene();

            //Place your rendering logic here

            device.EndScene();
            //device.Present();
        }

        this.device.Dispose();
        Application.Exit();
    }  

}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
user1166981
  • 1,738
  • 8
  • 26
  • 44
  • I dont think you can make it clickthrough in WinForms, you may need to enable/disable it on demand – fenix2222 Jun 18 '12 at 05:19
  • Can you try this: http://blogs.msdn.com/b/rickbrew/archive/2006/01/09/511003.aspx – fenix2222 Jun 18 '12 at 05:31
  • possible duplicate of [Topmost form, clicking "through" possible?](http://stackoverflow.com/questions/1524035/topmost-form-clicking-through-possible) – Emond Jun 18 '12 at 05:44
  • @Emo, its not, I am showing code that should work but doesn't and need help to figure out why – user1166981 Jun 18 '12 at 05:50
  • @fenix, thanks for finding that, but thats a modification of toolstrip which uses unmanaged code permission which I dont want to go into. – user1166981 Jun 18 '12 at 05:52
  • Are you using Managed DirectX??? It could be that that surface is capturing the clicks. – Emond Jun 18 '12 at 06:23
  • Yes, but only because its not my code and I don't know how to do the same things in detail otherwise. I have tried tearing the code apart, but looks like its out of my realm of understanding. – user1166981 Jun 18 '12 at 09:09
  • It would be cool to have invisible window with directdraw surface on top of everything. I hope someone will provide an answer, or at least hint to the solution. – Daniel Mošmondor Jun 21 '12 at 19:59
  • @ daniel, thanks for the +1, there are a lot of people looking for this- this code I posted is supposed to work as I have seen a program that works correctly based on this code, there is just something missing here for the clickthrough part and proper directx rendering. Hope to hear some responses. – user1166981 Jun 21 '12 at 21:48
  • @user1166981 I refined my code sample (it's using XNA, but it's easy to port to Managed DirectX if you still wan't to use that old stuff). And it works! :) – Jaska Jun 25 '12 at 18:53

4 Answers4

25

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);

    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jaska
  • 1,412
  • 1
  • 18
  • 39
  • 1
    The `TransparentKey` property of the form should be able to make a certain color on the form become transparent – Alvin Wong Jun 22 '12 at 08:37
  • Yes, but as I understood he wanted so that the form is clickthrough even on areas where the window renders something? – Jaska Jun 22 '12 at 08:46
  • 1
    This is only for GDI guys, as per the original code I was looking for directx drawing. I solved it in the end actually, so if either of you still want the points, just update your answers for directx so that an answer is archived for future users. I'll wait until after the 7 days then post my answer if not. Thank you. – user1166981 Jun 22 '12 at 18:31
  • Hey Jaska, I did in it managed directx in the end but your code looks good so I have awarded the bounty. If anyone else tears their hair out over this and Jaskas code doesn't work for some reason (as I havent tested it), then use this: http://stackoverflow.com/questions/1524035/topmost-form-clicking-through-possible?lq=1 thats what worked for me. – user1166981 Jun 29 '12 at 10:11
  • It can work well on MonoGame too, just remove this line `p.BackBufferFormat = SurfaceFormat.Vector4;`, see [here](https://github.com/j3soon/ClickThroughXNA) for the tested project. – J3soon Feb 25 '18 at 15:02
15

A little extension/modification to Jaska's code, which the form is transparent

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.TopMost = true; // make the form always on top
        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // hidden border
        this.WindowState = FormWindowState.Maximized; // maximized
        this.MinimizeBox = this.MaximizeBox = false; // not allowed to be minimized
        this.MinimumSize = this.MaximumSize = this.Size; // not allowed to be resized
        this.TransparencyKey = this.BackColor = Color.Red; // the color key to transparent, choose a color that you don't use
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            // Set the form click-through
            cp.ExStyle |= 0x80000 /* WS_EX_LAYERED */ | 0x20 /* WS_EX_TRANSPARENT */;
            return cp;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        // draw what you want
        e.Graphics.FillEllipse(Brushes.Blue, 30, 30, 100, 100);
    }
}
Alvin Wong
  • 12,210
  • 5
  • 51
  • 77
  • This is only for GDI guys, as per the original code I was looking for directx drawing. I solved it in the end actually, so if either of you still want the points, just update your answers for directx so that an answer is archived for future users. I'll wait until after the 7 days then post my answer if not. Thank you. – user1166981 Jun 22 '12 at 18:31
  • 1
    Yes, `CreateParams` is a much better way to control styles than with `SetWindowLong`. – Ben Voigt Sep 05 '13 at 16:31
  • @BenVoigt Yes. If anyone wants to know more, see: http://stackoverflow.com/q/13986363/1386111 – Alvin Wong Sep 06 '13 at 05:13
  • Great piece of code. Thank you. But is it also possible to overlay for example a full screen application like the Windows Media Player with this technique? – smedasn Nov 10 '20 at 15:06
1

Change your extended window style to only WS_EX_LAYERED, window style to only WS_POPUP (NO WS_SIZEBOX) and make sure to use DwmExtendFrameIntoClientArea with all -1's and this will produce transparent windows with layered support: downside is you need to bltbit with GDI from an offscreen directx rendering. Not optimal but it works. This gives mouse click throughs + directx rendering + transparency. Downside is you'll need to inform GDI anytime, pull the directx buffer (all of it or just the damaged portions) and write them to the screem with bltbit.

Setting the extended window style to WS_EX_COMPOSITED and DwmExtendedFrameIntoClientArea with all -1's (similar as above, WS_POPUP on the regular window style). This you can run directx from but no mouse clickthroughs. You can at this point define irregular paths for the hit mask and pass it to windows, its not perfect but if you know a general (non regular) area that can pass-through it'll work.

Still trying to find a true way of using opengl/directx on mac or windows platforms that can pass through mouse clicks with out having to do a bitblt to a legacy rendering system.

Trevor
  • 610
  • 7
  • 10
0

I have a simple way use TransparentKey property and a 1x1 pixel label with the color of Form TransparentKey. On Form and all control MouseMouse event. Set label position to Mouse location.

private void MoveHole()
{
    var newLocation = PointToClient(MousePosition);
    lblHole.Location = newLocation;
}