1

I'm trying to make a program for my late night star gazing, I need my laptop screen to be only red, so I want to make a program that acts as a red filter. It would cover the whole screen and be transparent + red. The user can click through it, it would be just like putting a piece of transparent red plastic in-front of the screen.

So far I have a form that sizes itself to what ever your screen size is, and moves itself to the upper left corner. It is slightly transparent and red.

I need to make all clicks on the form pass through, as I will eventually make the form transparent and red, but I don't want the user to be able to interact with it.

Program is called "Red_Filter"

Public Class Form1

Dim Screens As Array
Dim TotalWidth As Integer
Dim TotalHeight As Integer
Dim Heights As List(Of Integer) = New List(Of Integer)

'Load / Close
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Screens = Screen.AllScreens

    For I As Integer = 0 To UBound(Screens)

        TotalWidth += Screens(I).Bounds.Width
        Heights.Add(Screens(I).Bounds.Height)

    Next

    TotalHeight = Heights.Max()

    Me.Width = TotalWidth
    Me.Height = TotalWidth

    Me.Location = New Point(0, 0)

    Me.BackColor = Color.Red
    Me.Opacity = 0.5
    Me.TopMost = True

    'Make it click through
    SetWindowLong(Me.Handle, GWL_EXSTYLE, WS_EX_TRANSPARENT)
End Sub


'Click Through Functionality
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Const GWL_EXSTYLE = -20
Const WS_EX_TRANSPARENT = &H20

End Class

This is what I have so far, the part after "'Click Through Functionality" I found online, however, it gives me this error:

A call to PInvoke function 'Red Filter!Red_Filter.Form1::SetWindowLong' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

I do not know how the code I found online works, but the error happens in the last line of the form's load event.

Does anyone know how to make a form click-through?

tshepang
  • 12,111
  • 21
  • 91
  • 136
Postman
  • 517
  • 2
  • 8
  • 17
  • Correction - you need to get the Int64 value from IntPtr, so try this: `SetWindowLong(Me.Handle.ToInt64(), GWL_EXSTYLE, WS_EX_TRANSPARENT)`. – Tim Aug 18 '13 at 01:40
  • No luck with that, sadly. Same error is returned. – Postman Aug 18 '13 at 01:44
  • My comments were a bit confusing (and I deleted one of them). Take a look at my answer below and see if that helps. – Tim Aug 18 '13 at 01:49

5 Answers5

3

I ripped code from this codeproject post: http://www.codeproject.com/Articles/12877/Transparent-Click-Through-Forms

Here's a complex version with all the comment-ey goodness:

Imports System.Runtime.InteropServices

Public Class Form1

Private InitialStyle As Integer
Dim PercentVisible As Decimal

Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ' Grab the Extended Style information
    ' for this window and store it.
    InitialStyle = GetWindowLong(Me.Handle, GWL.ExStyle)
    PercentVisible = 0.8

    ' Set this window to Transparent
    ' (to the mouse that is!)

    ' This creates a new Extended Style
    ' for our window, which takes effect
    ' immediately upon being set, that
    ' combines the initial style of our window
    ' (saved in Form.Load) and adds the ability
    ' to be Transparent to the mouse.
    ' Both Layered and Transparent must be
    ' turned on for this to work AND have
    '  the window render properly!
    SetWindowLong(Me.Handle, GWL.ExStyle, InitialStyle Or WS_EX.Layered Or WS_EX.Transparent)

    ' Don't forget to set the Alpha
    ' for the window or else you won't be able
    ' to see the window! Possible values
    ' are 0 (visibly transparent)
    ' to 255 (visibly opaque). I'll set
    ' it to 70% visible here for show.
    ' The second parameter is 0, because
    ' we're not using a ColorKey!
    SetLayeredWindowAttributes(Me.Handle, 0, 255 * PercentVisible, LWA.Alpha)

    ' Just for giggles, set this window
    ' to stay on top of all others so we
    ' can see what's happening.
    Me.TopMost = True
    Me.BackColor = Color.Red
End Sub

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

And here's the simplified version I'm using that makes more sense to me:

Imports System.Runtime.InteropServices

Public Class Form1

Private InitialStyle As Integer
Dim PercentVisible As Decimal

Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    InitialStyle = GetWindowLong(Me.Handle, -20)
    PercentVisible = 0.8

    SetWindowLong(Me.Handle, -20, InitialStyle Or &H80000 Or &H20)

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

    Me.BackColor = Color.Red
    Me.TopMost = True
End Sub

<DllImport("user32.dll", EntryPoint:="GetWindowLong")> Public Shared Function GetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer) As Integer
End Function

<DllImport("user32.dll", EntryPoint:="SetWindowLong")> Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) 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 Integer) As Boolean
End Function

End Class
Postman
  • 517
  • 2
  • 8
  • 17
1

Try this:

    Protected Overrides ReadOnly Property CreateParams() As CreateParams
    Get
        Const WS_EX_TRANSPARENT As Integer = &H20 'Check if it can
        Dim params As CreateParams = MyBase.CreateParams
        params.ExStyle = params.ExStyle Or WS_EX_TRANSPARENT
        Return params 'return
    End Get
End Property
0

Using VS 2012, I created a simple program without your Screens object, like this:

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        Me.BackColor = Color.Red
        Me.Opacity = 0.5
        Me.TopMost = True

        SetWindowLong(Me.Handle.ToInt64(), GWL_EXSTYLE, WS_EX_TRANSPARENT)

    End Sub

    Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Const GWL_EXSTYLE = -20
    Const WS_EX_TRANSPARENT = &H20
End Class

Note the call to SetWindowLong:

SetWindowLong(Me.Handle.ToInt64(), GWL_EXSTYLE, WS_EX_TRANSPARENT)

Here's a screen shot of the program running:

enter image description here

EDITED TO ADD

Note that you can get this same effect without calling SetWindowLong.

Tim
  • 28,212
  • 8
  • 63
  • 76
  • I'm wondering how you didn't get the error, I am, with the same code. – Postman Aug 18 '13 at 01:52
  • Hmm....don't know on that one. I'm running Windows 7 64-bit - are you on a 32-bit OS by chance? – Tim Aug 18 '13 at 01:53
  • Windows 8 pro, 64 bit. VS 2012 just like you. Edit: to confirm, with the solution above, you can click through the window and access the form behind? And it's just me that's having this issue? – Postman Aug 18 '13 at 01:54
  • Hmmm....ok. Give me a bit and I'll try it on my Win8 machine and see if I get that issue. Also, it doesn't allow click-through to underlying windows/controls. – Tim Aug 18 '13 at 01:56
  • Ah, the SetWindowLong() was supposed to allow click-through behavior, I found it online somewhere, I don't know how it works. So I need another way to do that. Edit: In my program, I have the window borderless, it appears as if the SetWindowLong() actually just changes the opacity of the window border. – Postman Aug 18 '13 at 01:58
  • While I go off to my Win8 box, take a look at this answer: http://stackoverflow.com/a/11152658/745969 – Tim Aug 18 '13 at 02:00
  • Ok, but it doesn't allow clicks to pass through, so I guess the code I had doesn't really do what I want it to anyways. It's weird though that it doesn't work on my system. (neither did the solution in the answer you suggested) – Postman Aug 18 '13 at 02:09
  • Here's another answer that might help: http://stackoverflow.com/a/1524047/745969, plus a CodeProject link (this one is in VB.NET) that might help: http://www.codeproject.com/Articles/12877/Transparent-Click-Through-Forms – Tim Aug 18 '13 at 02:10
  • I was able to use the code project code to make it work, thanks for the link! – Postman Aug 18 '13 at 02:15
  • No problem - happy to help. I'd suggest you post your final solution (if you're willing to) and then auto-accept it (after the time delay) - that way future users can get useful help and you get the credit for it. :) – Tim Aug 18 '13 at 02:16
  • 1
    I was already planning to, I just have to edit it down to a cleaner format that's easier for noobs like me from the future to understand! – Postman Aug 18 '13 at 02:28
  • Awesome - I look forward to seeing it. – Tim Aug 18 '13 at 02:29
0

Add this after Class:

    Private Const WS_EX_TRANSPARENT As Integer = &H20

    Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or WS_EX_TRANSPARENT
            Return cp
        End Get
    End Property

Then run it. It should work. Also it dose not work on non-transparent forms from what i know.

-1

A little variant for a ghost form (You can see through it and click through it!), with the most recent Declare syntax. Thanx to Postman for the useful post.

Imports System.Runtime.InteropServices

Public Class GhostForm

    Private InitialStyle As Integer
    Private PercentVisible As Decimal

    Declare Function GetWindowLong Lib "user32.dll" Alias "GetWindowLongA" (ByVal hwnd As System.IntPtr, ByVal nIndex As Integer) As Integer
    Declare Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" (ByVal hwnd As System.IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer
    Declare Function SetLayeredWindowAttributes Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal crKey As Integer, ByVal alpha As Byte, ByVal dwFlags As Integer) As Boolean

    Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        InitialStyle = GetWindowLong(Me.Handle, -20)
        PercentVisible = 0.5
        SetWindowLong(Me.Handle, -20, InitialStyle Or &H80000 Or &H20)
        SetLayeredWindowAttributes(Me.Handle, 0, 255 * PercentVisible, &H2)
        Me.BackColor = Color.Green
        Me.TopMost = True

    End Sub
End Class
BR1COP
  • 321
  • 2
  • 5