1

I've been struggling with this a lot and have looked at several stackoverflow posts that recommend this as the correct procedure:

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

In my code, I'm following this technique almost exactly. And yet, my code is not working and I'm a little confused to why. I'm wondering if I'm using the wrong procedure? To be clear, my desired effect is for the user to click my form and to access something underneath it. For example, I am running on top of visual studio. If I try to click the app, I click visual studio instead.

UPDATE:

When I call my code, one of two things happens (depending on where I call the setwindowlong method):

  1. The window does not draw
  2. The window draws, but is clickable

Option 1 happens when I run the code right after initializecomponent Option 2 happens when I run it before initializecomponent

Here is the complete code to draw my form before anything else:

    [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("user32.dll")]
    static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

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


        //Get height of taskbar to exclude it, then bind to lower right of screen
        int nTaskBarHeight = Screen.PrimaryScreen.Bounds.Bottom -Screen.PrimaryScreen.WorkingArea.Bottom;
        Rectangle workingArea = Screen.GetWorkingArea(this);
        this.Location = new Point(Screen.PrimaryScreen.Bounds.Right - this.Size.Width, workingArea.Bottom - Size.Height + nTaskBarHeight);
        this.TopMost = true;

        this.FormBorderStyle = FormBorderStyle.None;
        this.ControlBox = false;
        this.Text = string.Empty;
        this.ShowInTaskbar = false;


        PopulatePhoneQueueData();
    }
Community
  • 1
  • 1
Darkenor
  • 4,349
  • 8
  • 40
  • 67
  • Not related to the question but just wondering what happens if a user has his taskbar on the left/right/top rather than the bottom? – bendataclear Sep 05 '13 at 15:23
  • How doesn't it work? You should describe the details. – King King Sep 05 '13 at 15:31
  • Check if `Handle` is already created. If I remember correctly, the windows handle is not created yet in the Form's constructor. – tia Sep 05 '13 at 15:49
  • From the docs: "The CreateControl method forces a handle to be created for the control and its child controls. This method is used when you need a handle immediately for manipulation of the control or its children; simply calling a control's constructor does not create the Handle. CreateControl does not create a control handle if the control's Visible property is false. You can either call the CreateHandle method or access the Handle property to create the control's handle regardless of the control's visibility, but in this case, no window handles are created for the control's children." – Ben Voigt Sep 05 '13 at 16:26
  • Also, from the Handle property docs: "The value of the Handle property is a Windows HWND. If the handle has not yet been created, referencing this property will force the handle to be created." – Ben Voigt Sep 05 '13 at 16:26
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Sep 05 '13 at 16:39

2 Answers2

2

We have WS_EX_TRANSPARENT = 0x20 which will make your form totally transparent.This extended style is required to make a click through window. So we would have some way to show your window normally (otherwise it's transparent and that's why you thought it wasn't drawn), we do this by using the win32 api function SetLayeredWindowAttributes (as declared in your code) or simply set the Opacity property of your form. By the way, you should override the CreateParams to initialize the extended style without declaring and using the methods GetWindowLong and SetWindowLong. Here is the code which should work (at least solve your problem: the window isn't drawn):

public frmPhoneQueueViewer()
{
    InitializeComponent();
    //The default Opacity = 1 won't show your form
    Opacity = 0.2f; //or even Opacity = 0.999 if you like
    //....
    //....
}
protected override CreateParams CreateParams {
    get {
         CreateParams cp = base.CreateParams;
         //WS_EX_LAYERED = 0x80000  WS_EX_TRANSPARENT = 0x20
         cp.ExStyle |= 0x80000 | 0x20;                
         return cp;
    }
}

NOTE: There is an interesting thing here which I've discovered. If you override the CreateParams as the code above, Opacity=1 won't show your form (totally transparent), you must change the Opacity to another value, for example 0.2 to make it partially transparent, even Opacity=0.9999 will show your form (looks like 100% opacity). However if you use some bool flag to prevent initializing the styles in CreateParams and apply the styles later using UpdateStyles(), your form will show OK when the Opacity=1, the code looks like this:

public frmPhoneQueueViewer()
{
    InitializeComponent();
    //The default Opacity = 1 will show your form normally
    Load += (s,e) => {
        appliedStyles = true;
        UpdateStyles();//Call this to apply the styles to make your window able to click through.
    };
    //....
    //....
}
bool appliedStyles;
protected override CreateParams CreateParams {
    get {
         CreateParams cp = base.CreateParams;
         //WS_EX_LAYERED = 0x80000  WS_EX_TRANSPARENT = 0x20
         if(appliedStyles) cp.ExStyle |= 0x80000 | 0x20;                
         return cp;
    }
}
King King
  • 61,710
  • 16
  • 105
  • 130
0

The right way to set additional window styles is by overriding the CreateParams getter.

That way the styles will be present from creation. SetWindowLong may set them too late to be 100% effective.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720