Jump to content

Click Through Window


Recommended Posts

I must believe that this question has been posted before, but my eyes are about to explode from reading all the results of my forum search. Please bear with me...

My ultimate goal is replicating what I would call the "mouse spotlight" that I have seen on many online software-training videos (a 'la recent video tutorials for Visual Studio 2005). This spotlight is a transparent shape (usually round) that follows the mouse cursor. In addition, any mouse click messages get sent "through" to the controls/windows underneath. Maybe it's best to think of it as a very obvious shadow.

Having said that, I've tinkered a bit with creating a rounded gui that re-adjusts its position whenever it registers a mouse move event on the form itself. Hopefully this prototype is more descriptive:

Opt("GUICoordMode",1)

    Dim $x
    Dim $y
    Dim $WinInfo
    Dim $MouseInfo

    $MouseInfo = MouseGetPos()

    $Form1 = GUICreate("AForm1", 118, 100, $MouseInfo[0]-59, $MouseInfo[1]-50, BitOR($WS_BORDER,$WS_CLIPSIBLINGS, $WS_EX_LAYERED, $WS_EX_TRANSPARENT), 0)
    WinSetTrans($Form1,"",150)
    GUISetState()
    GuiRoundCorners($Form1,10,10,270,270);<----- thanks gaFrost

    While 1
        $msg = GUIGetMsg(1)

        Select

            Case $msg[0] = $GUI_EVENT_MOUSEMOVE
                $WinInfo = WinGetPos($Form1)
                $MouseInfo = MouseGetPos()                                      

                $x = ($WinInfo[2]/2)
                $y = ($WinInfo[3]/2)
                WinMove($Form1,"",$MouseInfo[0]-$x,$MouseInfo[1]-$y)

            Case $msg[0] = $GUI_EVENT_CLOSE
                Exit

        EndSelect

    WEnd

;===============================================================================
;
; Function Name:    GuiRoundCorners()
; Description:    Rounds the corners of a window
; Parameter(s):     $h_win
;                   $i_x1
;                   $i_y1
;                   $i_x3
;                   $i_y3
; Requirement(s):   AutoIt3
; Return Value(s):
; Author(s):        gaFrost
;
;===============================================================================
Func GuiRoundCorners($h_win, $i_x1, $i_y1, $i_x3, $i_y3)
   Dim $pos, $ret, $ret2
   $pos = WinGetPos($h_win)
   $ret = DllCall("gdi32.dll", "long", "CreateRoundRectRgn", "long", $i_x1, "long", $i_y1, "long", $pos[2], "long", $pos[3], "long", $i_x3, "long", $i_y3)
   If $ret[0] Then
      $ret2 = DllCall("user32.dll", "long", "SetWindowRgn", "hwnd", $h_win, "long", $ret[0], "int", 1)
      If $ret2[0] Then
         Return 1
      Else
         Return 0
      EndIf
   Else
      Return 0
   EndIf
EndFunc ;==>_GuiRoundCorners

The caveat here is that mouse button events are not being allowed to "fall through" the window. I'm pretty sure that having the window under constant focus doesn't help either. MSDN documentation on Win 32 Window Classes and Layered Windows were promising initially, but the devil has come out in the details.

So the question is: could this shadowing/spotlight behavior possibly work with the logic above? If not, any suggestions? This community seems to be very supportive, so I look forward to hearing from some of you.

Link to comment
Share on other sites

You could always put a pixel hole at the cursor. I've made things like this for flash games that hide the cursor.

The shape looked like

[][][][][]

[][][][][]

[][]--[][]

[][][][][]

[][][][][]

Where [] = pixel, -- = hole.

Window regions are fun.

Otherwise, you could find the control directly below the shadow and send a Control Click.

Link to comment
Share on other sites

Dear Mr. Icekirby1,

I'd love to try your suggestion. Do you have any prototypes or code snippets that would showcase this?

I must admit that if my life depended on placing a pixel hole in a form, the world would be less one idiot.

Most posts I've found seemed to assume this knowledge beforehand. Please help me stay alive another day.

Sincerely,

zfisherdrums

You could always put a pixel hole at the cursor. I've made things like this for flash games that hide the cursor.

The shape looked like

[][][][][]

[][][][][]

[][]--[][]

[][][][][]

[][][][][]

Where [] = pixel, -- = hole.

Window regions are fun.

Otherwise, you could find the control directly below the shadow and send a Control Click.

Link to comment
Share on other sites

Here you go. Just change the size, location, etc. to your liking. Also, add some error checking just in case.

#include <GUIConstants.au3>
    Opt("GUICoordMode",1)

    Dim $x
    Dim $y
    Dim $WinInfo
    Dim $MouseInfo
    
    Global $size = 200

    $MouseInfo = MouseGetPos()

    $Form1 = GUICreate("AForm1", $size, $size, $MouseInfo[0]-59, $MouseInfo[1]-50, $WS_POPUP, 0)
    WinSetTrans($Form1,"",150)
    GUISetState()
    GuiRoundHole($Form1,0,0,$size,$size);<----- thanks gaFrost

    While 1
        $msg = GUIGetMsg(1)

        Select

            Case $msg[0] = $GUI_EVENT_MOUSEMOVE
                $WinInfo = WinGetPos($Form1)
                $MouseInfo = MouseGetPos()                                        

                $x = ($WinInfo[2]/2)
                $y = ($WinInfo[3]/2)
                WinMove($Form1,"",$MouseInfo[0]-$x+1,$MouseInfo[1]-$y+1)

            Case $msg[0] = $GUI_EVENT_CLOSE
                Exit

        EndSelect

    WEnd

;===============================================================================
;
; Function Name:    GuiRoundHole()
; Description:      Rounds the corners of a window
; Parameter(s):        $h_win
;                    $i_x1
;                    $i_y1
;                    $i_x3
;                    $i_y3
; Requirement(s):   AutoIt3
; Return Value(s):
; Author(s):        gaFrost
;
;===============================================================================
Func GuiRoundHole($h_win, $i_x1, $i_y1, $i_x3, $i_y3)
   Local $halfx = Int(($i_x3 - $i_x1) / 2)
   Local $halfy = Int(($i_y3 - $i_y1) / 2)
   Local $holesize = 3
   Local $outer_rgn = DllCall("gdi32.dll", "long", "CreateEllipticRgn", "long", $i_x1, "long", $i_y1, "long", $i_x3 , "long", $i_y3)
   Local $inner_rgn = DllCall("gdi32.dll", "long", "CreateEllipticRgn", "long", $i_x1+$halfx-$holesize, "long", $i_y1+$halfy-$holesize, "long", $i_x1+$halfx+$holesize, "long", $i_y1+$halfy+$holesize)
   If $outer_rgn[0] and $inner_rgn[0] Then
      Local $combined_rgn = DllCall("gdi32.dll", "long", "CreateEllipticRgn", "long", 0, "long", 0, "long", 0, "long", 0)
      DllCall("gdi32.dll", "long", "CombineRgn", "long", $combined_rgn[0], "long", $outer_rgn[0], "long", $inner_rgn[0], "int", 4)
      Local $ret3 = DllCall("user32.dll", "long", "SetWindowRgn", "hwnd", $h_win, "long", $combined_rgn[0], "int", 1)
      If $ret3[0] Then
         Return 1
      Else
         Return 0
      EndIf
   Else
      Return 0
   EndIf
EndFunc ;==>_GuiRoundHole
Link to comment
Share on other sites

First, thank you all for your help and superior talent with this "weird" design.

b8bboi, I used your code and it really helped me understand IceKirby1's suggestion about the window regions. After some initial tinkering with lining up the region and adjusting the size of the hole, I could see how it works. The only problem I'm running into is the mouse events are not passed "through" 100% of the time. If this behavior is not as apparent on your system, I'd be willing to bet it is the death rattle of this HP Pavillion Laptop. But being that's it will be used for a tutorial that will distributed, I'd be willing to bet it would happen to some other poor sap.

A while back, I found an example on the codeproject that created a click through window in VB.net, but having it follow the mouse proved problematic. Pilot error. At this point I'm beginning to think that if this is the wrong direction, then I might try something like this:

; create alpha-blended, semi-transparent form that overlays application under test

; capture mouse movements and render GDI shape in the form to follow

; pass mouse event to control located underneath the form/ application under test

But somehow, this seems like a lot of overhead. Like pulling an aircraft carrier into port just to fix a bolt.

If anyone can help me out with programatically determining the control under cursor, I'd be grateful.

Although something tells me that its probably already on the forum. Feel free to read me the riot act.

Finally, if you've read this far, the example I'm trying to emulate can be observed here.

Look specifically at the picture used for Lesson 2.

Link to comment
Share on other sites

You can hide the "spotlight" and do a mouse click on the area underneath.

Try this. Double-click doesn't work. You'll have to modify the code to make it work.

#include <GUIConstants.au3>

    Opt("GUICoordMode",1)

    Dim $x
    Dim $y
    Dim $WinInfo
    Dim $MouseInfo
   
    Global $size = 200

    $MouseInfo = MouseGetPos()

    Global $Form1 = GUICreate("AForm1", $size, $size, $MouseInfo[0]-59, $MouseInfo[1]-50, $WS_POPUP, 0)
    Global $label = GUICtrlCreateLabel("", 0, 0, $size, $size)
    WinSetTrans($Form1,"",150)
    GUISetState()
    GuiRound($Form1,0,0,$size,$size);<----- thanks gaFrost

    While 1
        $msg = GUIGetMsg(1)

        Select

            Case $msg[0] = $GUI_EVENT_MOUSEMOVE
                $WinInfo = WinGetPos($Form1)
                $MouseInfo = MouseGetPos()                                       

                $x = ($WinInfo[2]/2)
                $y = ($WinInfo[3]/2)
                WinMove($Form1,"",$MouseInfo[0]-$x+1,$MouseInfo[1]-$y+1)

        Case $msg[0] = $label
        MouseClicked()

            Case $msg[0] = $GUI_EVENT_CLOSE
                Exit

        EndSelect

    WEnd

Func MouseClicked()
    Local $mousepos = MouseGetPos()
    GUISetState(@SW_HIDE, $Form1)
    Sleep(100)
    MouseClick("", $mousepos[0], $mousepos[1], 1, 1)
    GUISetState(@SW_SHOW, $Form1)

EndFunc

;===============================================================================
;
; Function Name:    GuiRoundHole()
; Description:      Rounds the corners of a window
; Parameter(s):        $h_win
;                    $i_x1
;                    $i_y1
;                    $i_x3
;                    $i_y3
; Requirement(s):   AutoIt3
; Return Value(s):
; Author(s):        gaFrost
;
;===============================================================================
Func GuiRound($h_win, $i_x1, $i_y1, $i_x3, $i_y3)
   Local $halfx = Int(($i_x3 - $i_x1) / 2)
   Local $halfy = Int(($i_y3 - $i_y1) / 2)
   Local $holesize = 3
   Local $outer_rgn = DllCall("gdi32.dll", "long", "CreateEllipticRgn", "long", $i_x1, "long", $i_y1, "long", $i_x3 , "long", $i_y3)
   If $outer_rgn[0] Then
      Local $ret3 = DllCall("user32.dll", "long", "SetWindowRgn", "hwnd", $h_win, "long", $outer_rgn[0], "int", 1)
      If $ret3[0] Then
         Return 1
      Else
         Return 0
      EndIf
   Else
      Return 0
   EndIf
EndFunc ;==>_GuiRoundHole
Link to comment
Share on other sites

This works. You were almost there, but you just have to put the window styles in the right places.

HotKeySet ("+{ESC}", "IWannaQuit")
Global $Width = 100, $Height = 100, $Left = MouseGetPos (0) - $Width/2, $Top = MouseGetPos (1) - $Height/2

$ClickWin = GUICreate ("Click Through Me", $Width, $Height, $Left, $Top, $WS_POPUP, BitOR ($WS_EX_TRANSPARENT, $WS_EX_LAYERED, $WS_EX_TOPMOST))
WinSetTrans ($ClickWin, "", 150)
GUISetState ()

While 1
    Sleep (10)
    TheMouseMoved()
WEnd

Func TheMouseMoved()
    $WinPos = WinGetPos ($ClickWin)
    WinMove ($ClickWin, "", MouseGetPos (0) - $Width/2, MouseGetPos (1) - $Height/2)
EndFunc

Func IWannaQuit()
    Exit
EndFunc
Link to comment
Share on other sites

Dear IceKirby1, greenmachine, b8bboi, and slightly-abnormal;

I could not have done this without you all. Thanks for a great re-introduction into posting. I've been admittedly shy on these because I've seen how ugly some individuals can be. Your help restores my faith in e-humanity.

I'm officially closing this thread. The final code is placed here in hopes that it will help others.

#include <GUIConstants.au3>

HotKeySet ("+{ESC}", "IWannaQuit")

Global $size = 100 
Global $Left = MouseGetPos (0) - $size / 2
Global $Top = MouseGetPos (1) - $size / 2

$MouseShadow = GUICreate ("Click Through Me", $size, $size, $Left, $Top, $WS_POPUP, _
                    BitOR ($WS_EX_TRANSPARENT, $WS_EX_LAYERED, $WS_EX_TOPMOST))
Global $label = GUICtrlCreateLabel("", 0, 0, $size, $size)
WinSetTrans ($MouseShadow, "", 150)
GUISetBkColor(0xFFFF00, $MouseShadow)
GuiRoundCorners($MouseShadow,10,10,270,270)
GUISetState ()

While 1

    Sleep (10)
    TheMouseMoved() 

WEnd

Func TheMouseMoved()
    $WinPos = WinGetPos ($MouseShadow)
    WinMove ($MouseShadow, "", MouseGetPos (0) - $size/2, MouseGetPos (1) - $size/2)
EndFunc

Func IWannaQuit()
    Exit
EndFunc


;===============================================================================
;
; Function Name: GuiRoundCorners()
; Description: Rounds the corners of a window
; Parameter(s): $h_win
; $i_x1
; $i_y1
; $i_x3
; $i_y3
; Requirement(s): AutoIt3
; Return Value(s):
; Author(s): gaFrost
;
;===============================================================================
Func GuiRoundCorners($h_win, $i_x1, $i_y1, $i_x3, $i_y3)
    Dim $pos, $ret, $ret2
    $pos = WinGetPos($h_win)
    $ret = DllCall("gdi32.dll", "long", "CreateRoundRectRgn", "long", $i_x1, "long", $i_y1, "long", $pos[2], "long", $pos[3], "long", $i_x3, "long", $i_y3)
    If $ret[0] Then
        $ret2 = DllCall("user32.dll", "long", "SetWindowRgn", "hwnd", $h_win, "long", $ret[0], "int", 1)
        If $ret2[0] Then
            Return 1
        Else
            Return 0
        EndIf
    Else
        Return 0
    EndIf
EndFunc;==>_GuiRoundCorners
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...