Jump to content

Subclassing?


dantay9
 Share

Recommended Posts

I am trying to make a little app, just for fun and to learn a bit more about lower level programming.

When I click the middle mouse button, the window the mouse is over is sent to the back of the z order.

When I hold the middle mouse, the window is sent to the front of the z order.

All of this is working. I am having a little trouble with scrolling. If the mouse is over a control that can be scrolled, when I use the mouse wheel, that control is scrolled, even if it isn't on an active window. That part works. But when I have an active window with an edit control as well as the mouse being over a nonactive edit control, they both scroll. I think this is because they are sharing the command. From what I have read, Zedna's subclassing will fix this. The part of the code that will need fixed is the

$WM_MOUSEWHEEL case in the mouse hook function.

Here is a picture of the setup when the problem scrolling occurs.

I took a look at Zedna's posts, but didn't understand exactly how subclassing worked or how to use it. Thanks in advance!

#include <Constants.au3>
#include <EditConstants.au3>
#include <ScrollBarConstants.au3>
#include <SendMessage.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <WindowsConstants.au3>

HotKeySet("{ESC}", "_Exit")
Global Const $WM_MOUSEWHEEL = 0x020A ;wheel up/down
;~ Global Const $WM_MBUTTONDBLCLK = 0x0209 ;wheel clicks
Global Const $User32 = DllOpen("User32.dll")
Global $hKey_Proc, $hM_Hook, $TimeSinceLastDown, $WindowToFront, $MouseUpSent = False, $CanScroll = False

SetWindowsHook() ;set low level mouse hook
$MGP = MouseGetPos()

While 1
    If $MGP[0] <> MouseGetPos(0) And $MGP[1] <> MouseGetPos(1) Then ;if mouse moved
        $MGP = MouseGetPos() ;get mouse pos
        $Window = WinAPI_WindowFromPoint($MGP[0], $MGP[1]) ;get window mouse is over
        If $Window <> WinGetHandle("[Active]") Then ;make sure window isn't active window
            $hOverControl = CtrlGetInfoFromPoint($Window, $MGP[0], $MGP[1], "Handle") ;get the handle of the control mouse is over
            $ControlStyle = "0x" & Hex(_WinAPI_GetWindowLong($hOverControl, $GWL_STYLE)) ;get control style from handle
            If CanScroll($ControlStyle) Then ;see if the control can be scrolled
                $CanScroll = True
            Else
                $CanScroll = False
            EndIf
        EndIf
    EndIf
    Sleep(100)
WEnd

Func WinAPI_WindowFromPoint($iX, $iY)
    Local $a_wfp, $a_ga

    $a_wfp = DllCall("user32.dll", "hwnd", "WindowFromPoint", "long", $iX, "long", $iY)
    If @error Then Return SetError(1, @error, 0)
    $a_ga = _WinAPI_GetAncestor($a_wfp[0], $GA_ROOT)
    If @error Then Return SetError(2, @error, 0)

    Return $a_ga
EndFunc ;==>WinAPI_WindowFromPoint

Func _Exit()
    Exit
EndFunc ;==>_Exit

#Region Mouse Hook
Func SetWindowsHook()
    Local $hM_Module

    $hKey_Proc = DllCallbackRegister("_Mouse_Proc", "int", "int;ptr;ptr")
    $hM_Module = DllCall("kernel32.dll", "hwnd", "GetModuleHandle", "ptr", 0)
    $hM_Hook = DllCall("user32.dll", "hwnd", "SetWindowsHookEx", "int", $WH_MOUSE_LL, "ptr", DllCallbackGetPtr($hKey_Proc), "hwnd", $hM_Module[0], "dword", 0)
EndFunc ;==>SetWindowsHook

Func _Mouse_Proc($nCode, $wParam, $lParam)

    If $nCode < 0 Then _WinAPI_CallNextHookEx($hM_Hook, $nCode, $wParam, $lParam)

    Switch $wParam
        Case $WM_MBUTTONDOWN
            $MouseUpSent = False
;~          If TimerDiff($TimeSinceLastDown) < 325 Then ;middle mouse button double click
;~              AdlibEnable("MiddleDoubleClick", 100) ;call the function after $WM_MBUTTONDOWN is passed on to system
;~          Else
;~          EndIf

            $TimeSinceLastDown = TimerInit() ;reset the timer
            CheckForMiddleUp()
            AdlibEnable("CheckForMiddleUp", 75)
        Case $WM_MBUTTONUP
            $MouseUpSent = True
        Case $WM_MOUSEWHEEL
            If $CanScroll = True Then
                $Info = DllStructCreate("int X;int Y;dword mouseData;dword flags;dword time;ulong_ptr dwExtraInfo", $lParam)
                $MouseData = DllStructGetData($Info, 3)
                If _WinAPI_HiWord($MouseData) > 0 Then
                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEUP)
                Else
                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEDOWN)
                EndIf
                Return 0
            EndIf
    EndSwitch

    Return _WinAPI_CallNextHookEx($hM_Hook, $nCode, $wParam, $lParam)
EndFunc ;==>_Mouse_Proc

Func OnAutoItExit()
    WinSetOnTop($WindowToFront, "", 0)
    DllCall("user32.dll", "int", "UnhookWindowsHookEx", "hwnd", $hM_Hook[0])
    $hM_Hook[0] = 0
    DllCallbackFree($hKey_Proc)
    $hKey_Proc = 0
EndFunc ;==>OnAutoItExit
#EndRegion Mouse Hook

Func CheckForMiddleUp()
    If $MouseUpSent = False And TimerDiff($TimeSinceLastDown) > 100 Then ;middle mouse down held
        ConsoleWrite("Activate Window" & @CRLF)
        AdlibDisable()
        WinSetOnTop($WindowToFront, "", 0)
        $WindowToFront = WinAPI_WindowFromPoint(MouseGetPos(0), MouseGetPos(1))
        WinSetOnTop($WindowToFront, "", 1)
    ElseIf $MouseUpSent = True Then ;single middle mouse click
        ConsoleWrite("Send To Back" & @CRLF)
        AdlibDisable()
        Local $WFP = WinAPI_WindowFromPoint(MouseGetPos(0), MouseGetPos(1))
        Local $aResult = DllCall($User32, "hwnd", "FindWindow", "str", "Progman", "str", "Program Manager")

        _WinAPI_SetWindowPos($WFP, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOSIZE, $SWP_NOACTIVATE))
        DllCall($User32, "int", "SetForegroundWindow", "hwnd", $aResult[0]) ;activate the window
    EndIf
EndFunc ;==>CheckForMiddleUp

Func CtrlGetInfoFromPoint($hWin, $iX, $iY, $Mode)
    Local $sClassList = WinGetClassList($hWin)
    Local $sSplitClass = StringSplit(StringTrimRight($sClassList, 1), @LF)
    Local $nCount, $ClientCoords, $tPoint

    ;convert screen coords to client coords
    $tPoint = DllStructCreate($tagPOINT)
    DllStructSetData($tPoint, "X", $iX)
    DllStructSetData($tPoint, "Y", $iY)
    _WinAPI_ScreenToClient($hWin, $tPoint)

    ;set x and y to client coords
    $iX = DllStructGetData($tPoint, "X")
    $iY = DllStructGetData($tPoint, "Y")

    For $iCount = UBound($sSplitClass) - 1 To 1 Step -1
        $nCount = 0
        While 1
            $nCount += 1
            Local $aCPos = ControlGetPos($hWin, "", $sSplitClass[$iCount] & $nCount)
            If @error Then ExitLoop
            If $iX >= $aCPos[0] And $iX <= ($aCPos[0] + $aCPos[2]) _
                    And $iY >= $aCPos[1] And $iY <= ($aCPos[1] + $aCPos[3]) Then
                If $sSplitClass[$iCount] <> "" Then
                    Switch $Mode
                        Case "NN"
                            Return $sSplitClass[$iCount] & $nCount
                        Case "Handle"
                            Return ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]")
                        Case "ID"
                            Return _WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_ID)
;~ Return Dec(StringTrimLeft(_WinAPI_GetDlgCtrlID(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]")), 2))
                        Case "Style"
                            Return "0x" & Hex(_WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_STYLE))
                        Case "ExStyle"
                            Return "0x" & Hex(_WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_EXSTYLE))
                        Case Else
                            Return $sSplitClass[$iCount]
                    EndSwitch
                EndIf
            EndIf
        WEnd
    Next
    Return ""
EndFunc ;==>CtrlGetInfoFromPoint

Func CanScroll($Style)
    Return (BitAND($Style, $WS_VSCROLL) Or BitAND($Style, $WS_HSCROLL))
EndFunc ;==>CanScroll
Edited by dantay9
Link to comment
Share on other sites

You hook procedure never return to the rest of the chain, it should be:

Func _Mouse_Proc($nCode, $wParam, $lParam)

    If $nCode < 0 Then _WinAPI_CallNextHookEx($hM_Hook[0], $nCode, $wParam, $lParam)

    Switch $wParam
        Case $WM_MBUTTONDOWN
            $MouseUpSent = False
;~          If TimerDiff($TimeSinceLastDown) < 325 Then ;middle mouse button double click
;~              AdlibEnable("MiddleDoubleClick", 100) ;call the function after $WM_MBUTTONDOWN is passed on to system
;~          Else
;~          EndIf

            $TimeSinceLastDown = TimerInit() ;reset the timer
            CheckForMiddleUp()
            AdlibEnable("CheckForMiddleUp", 75)
        Case $WM_MBUTTONUP
            $MouseUpSent = True
        Case $WM_MOUSEWHEEL
            If $CanScroll = True Then
                $Info = DllStructCreate("int X;int Y;dword mouseData;dword flags;dword time;ulong_ptr dwExtraInfo", $lParam)
                $MouseData = DllStructGetData($Info, 3)
                If _WinAPI_HiWord($MouseData) > 0 Then
                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEUP)
                Else
                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEDOWN)
                EndIf
                Return 0
            EndIf
    EndSwitch

    Return _WinAPI_CallNextHookEx($hM_Hook[0], $nCode, $wParam, $lParam)
EndFunc ;==>_Mouse_Proc

..or set $hM_Hook = $hM_Hook[0] after you're calling DllCall().

Subclassing your window's control is something else than subclassing some other program's control, it's much more complex. Look here, it's an example of how to install a hook procedure that monitors messages posted to a message loop of an application, you can see which window this message is for and whether to intercept the message or let the window receive this message. You must install this hook using a dll.

Link to comment
Share on other sites

OK. That makes sense. Thanks for the help with the hook, but it still doesn't work. This problem doesn't happen when neither of the windows are active. Could the scroll message activated by my mouse wheel being scrolled be arriving at the active window before it is passed on to my program?

Edited by dantay9
Link to comment
Share on other sites

Anyone else? I looked at Authenticity's link, but I don't know how to code that. I don't know VB code. A sample code in autoit that displays intercepting an event before the active window would be helpful. Or maybe there is another way to achieve what I am looking for.

Link to comment
Share on other sites

...Could the scroll message activated by my mouse wheel being scrolled be arriving at the active window before it is passed on to my program?

Nope, take this for example:

Case $WM_MOUSEWHEEL
    Return 1

...now no window in the system is getting notified about the event. What it can be is that another application registers a hook procedure later or after you register yours, this way, the other application gets notified before your hook.

#include <Constants.au3>
#include <EditConstants.au3>
#include <ScrollBarConstants.au3>
#include <SendMessage.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <WindowsConstants.au3>

If Not IsDeclared("WM_MBUTTONDOWN") Then Global Const $WM_MBUTTONDOWN = 0x0207
If Not IsDeclared("WM_MBUTTONUP") Then Global Const $WM_MBUTTONUP = 0x0208

HotKeySet("{ESC}", "_Exit")
Global Const $WM_MOUSEWHEEL = 0x020A ;wheel up/down
;~ Global Const $WM_MBUTTONDBLCLK = 0x0209 ;wheel clicks
Global Const $User32 = DllOpen("User32.dll")
Global $hKey_Proc, $hM_Hook, $TimeSinceLastDown, $WindowToFront, $MouseUpSent = False, $CanScroll = False

SetWindowsHook() ;set low level mouse hook
$MGP = MouseGetPos()

While 1
    Sleep(100)
WEnd

Func WinAPI_WindowFromPoint($iX, $iY)
    Local $a_wfp, $a_ga

    $a_wfp = DllCall("user32.dll", "hwnd", "WindowFromPoint", "long", $iX, "long", $iY)
    If @error Then Return SetError(1, @error, 0)
;~     $a_ga = _WinAPI_GetAncestor($a_wfp[0], $GA_ROOT)
;~     If @error Then Return SetError(2, @error, 0)

    Return $a_wfp[0]
EndFunc ;==>WinAPI_WindowFromPoint

Func _Exit()
    Exit
EndFunc ;==>_Exit

#Region Mouse Hook
Func SetWindowsHook()
    Local $hM_Module

    $hKey_Proc = DllCallbackRegister("_Mouse_Proc", "int", "int;ptr;ptr")
    $hM_Module = DllCall("kernel32.dll", "hwnd", "GetModuleHandle", "ptr", 0)
    $hM_Hook = DllCall("user32.dll", "hwnd", "SetWindowsHookEx", "int", $WH_MOUSE_LL, "ptr", DllCallbackGetPtr($hKey_Proc), "hwnd", $hM_Module[0], "dword", 0)
    $hM_Hook = $hM_Hook[0]
EndFunc ;==>SetWindowsHook

Func _Mouse_Proc($nCode, $wParam, $lParam)
    Local $hWindow, $hOverControl
    Local $iX, $iY
    Local $MouseData

    If $nCode < 0 Then _WinAPI_CallNextHookEx($hM_Hook, $nCode, $wParam, $lParam)

    Switch $wParam
        Case $WM_MBUTTONDOWN
            $MouseUpSent = False
;~          If TimerDiff($TimeSinceLastDown) < 325 Then ;middle mouse button double click
;~              AdlibEnable("MiddleDoubleClick", 100) ;call the function after $WM_MBUTTONDOWN is passed on to system
;~          Else
;~          EndIf

            $TimeSinceLastDown = TimerInit() ;reset the timer
            CheckForMiddleUp()
            AdlibEnable("CheckForMiddleUp", 75)
        Case $WM_MBUTTONUP
            $MouseUpSent = True
        Case $WM_MOUSEWHEEL
            $Info = DllStructCreate("int X;int Y;dword mouseData;dword flags;dword time;dword dwExtraInfo", $lParam)
            $iX = DllStructGetData($Info, "X")
            $iY = DllStructGetData($Info, "Y")
            $MouseData = DllStructGetData($Info, "mouseData")
            $hOverControl = WinAPI_WindowFromPoint($iX, $iY) ;get window mouse is over

            If CanScroll(_WinAPI_GetWindowLong($hOverControl, $GWL_STYLE)) Then ;see if the control can be scrolled
                ConsoleWrite("Yes" & @CRLF)
;~              If _WinAPI_HiWord($MouseData) > 0 Then
;~                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEUP)
;~                 Else
;~                    _SendMessage($hOverControl, $EM_SCROLL, $SB_LINEDOWN)
;~             EndIf
                _SendMessage($hOverControl, $WM_MOUSEWHEEL, $MouseData, BitOR(BitShift($iY, -16), $iX))
               Return 1
            EndIf
    EndSwitch

    Return _WinAPI_CallNextHookEx($hM_Hook, $nCode, $wParam, $lParam)
EndFunc ;==>_Mouse_Proc

Func OnAutoItExit()
    WinSetOnTop($WindowToFront, "", 0)
    DllCall("user32.dll", "int", "UnhookWindowsHookEx", "hwnd", $hM_Hook)
    DllCallbackFree($hKey_Proc)
EndFunc ;==>OnAutoItExit
#EndRegion Mouse Hook

Func CheckForMiddleUp()
    If $MouseUpSent = False And TimerDiff($TimeSinceLastDown) > 100 Then ;middle mouse down held
        ConsoleWrite("Activate Window" & @CRLF)
        AdlibDisable()
        WinSetOnTop($WindowToFront, "", 0)
        $WindowToFront = WinAPI_WindowFromPoint(MouseGetPos(0), MouseGetPos(1))
        WinSetOnTop($WindowToFront, "", 1)
    ElseIf $MouseUpSent = True Then ;single middle mouse click
        ConsoleWrite("Send To Back" & @CRLF)
        AdlibDisable()
        Local $WFP = WinAPI_WindowFromPoint(MouseGetPos(0), MouseGetPos(1))
        Local $aResult = DllCall($User32, "hwnd", "FindWindow", "str", "Progman", "str", "Program Manager")

        _WinAPI_SetWindowPos($WFP, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOSIZE, $SWP_NOACTIVATE))
        DllCall($User32, "int", "SetForegroundWindow", "hwnd", $aResult[0]) ;activate the window
    EndIf
EndFunc ;==>CheckForMiddleUp

Func CtrlGetInfoFromPoint($hWin, $iX, $iY, $Mode)
    Local $sClassList = WinGetClassList($hWin)
    Local $sSplitClass = StringSplit(StringTrimRight($sClassList, 1), @LF)
    Local $nCount, $ClientCoords, $tPoint

    ;convert screen coords to client coords
    $tPoint = DllStructCreate($tagPOINT)
    DllStructSetData($tPoint, "X", $iX)
    DllStructSetData($tPoint, "Y", $iY)
    _WinAPI_ScreenToClient($hWin, $tPoint)

    ;set x and y to client coords
    $iX = DllStructGetData($tPoint, "X")
    $iY = DllStructGetData($tPoint, "Y")

    For $iCount = UBound($sSplitClass) - 1 To 1 Step -1
        $nCount = 0
        While 1
            $nCount += 1
            Local $aCPos = ControlGetPos($hWin, "", $sSplitClass[$iCount] & $nCount)
            If @error Then ExitLoop
            If $iX >= $aCPos[0] And $iX <= ($aCPos[0] + $aCPos[2]) _
                    And $iY >= $aCPos[1] And $iY <= ($aCPos[1] + $aCPos[3]) Then
                If $sSplitClass[$iCount] <> "" Then
                    Switch $Mode
                        Case "NN"
                            Return $sSplitClass[$iCount] & $nCount
                        Case "Handle"
                            Return ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]")
                        Case "ID"
                            Return _WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_ID)
;~ Return Dec(StringTrimLeft(_WinAPI_GetDlgCtrlID(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]")), 2))
                        Case "Style"
                            Return "0x" & Hex(_WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_STYLE))
                        Case "ExStyle"
                            Return "0x" & Hex(_WinAPI_GetWindowLong(ControlGetHandle($hWin, "", "[CLASS:" & $sSplitClass[$iCount] & "]"), $GWL_EXSTYLE))
                        Case Else
                            Return $sSplitClass[$iCount]
                    EndSwitch
                EndIf
            EndIf
        WEnd
    Next
    Return ""
EndFunc ;==>CtrlGetInfoFromPoint

Func CanScroll($Style)
    Return (BitAND($Style, $WS_VSCROLL) Or BitAND($Style, $WS_HSCROLL))
EndFunc ;==>CanScroll

Hope this is what you're trying to do.

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...