Detect Close button click on other windows

Just starting out? Need help? Post your questions and find answers here.
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Detect Close button click on other windows

Post by BarryG »

Hi, does anyone know how to detect a Close button click on a third-party window, to cancel the click and then do something to that window first instead? I want to make a window animate in a specific way before closing, so I need to detect when Close is clicked to cancel the #WM_CLOSE message to it, animate it, then send my own #WM_CLOSE message to do the actual close.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4661
Joined: Sun Apr 12, 2009 6:27 am

Re: Detect Close button click on other windows

Post by RASHAD »

1- Create a Transparent window with the size of the Close button
2- Create a watchdog Thread
3- Keep detecting the position of the third-party window(if you can :) )
4- keep moving the transparent window on top of the third-party window Close Button

Now you can know when the third-party Close button just clicked
From here do what you want to do then Close that Window

Good luck

# 2:
- You can use Low Level Mouse Hook to intercept Left & Right Mouse Button down & Button up
- Once it is detected raise a flag and get the Cursor position
- If the Cursor position within the Close Button area do what you want to do
- Else let it go
Egypt my love
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

Hi Rashad. Your first method is what I've currently got going, but it has the drawback that sometimes a user will click the Close button of an inactive background window, so my fake little window isn't going to be over it (it's only over the active window). I don't want to parse all open windows and stick a fake one over them all, hence this coding question.

The mouse hook idea sounds good, but I'm worried that it won't (or can't) intercept a Close click to an inactive window as well. I'll experiment and see.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4661
Joined: Sun Apr 12, 2009 6:27 am

Re: Detect Close button click on other windows

Post by RASHAD »

Hi BarryG
The Global Hook will do the job(I hope)
Go with it :D

Code: Select all

Global hhkLLMouse

Procedure MouseHook(nCode, wParam, lParam)
    *p.MOUSEHOOKSTRUCT = lParam
      Select wParam 
        Case #WM_LBUTTONDOWN , #WM_RBUTTONDOWN
          x = *p\pt\x
          y = *p\pt\y
          If x > 1700 And x < 1850
            ProcedureReturn 1
          Else
            Debug x
            Debug y
          EndIf 
      EndSelect
  ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)
EndProcedure

OpenWindow(0, 0, 0, 300, 100, "Global Hook", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)

hhkLLMouse = SetWindowsHookEx_(#WH_MOUSE_LL, @MouseHook(), GetModuleHandle_(0), 0)

Repeat
  Select WaitWindowEvent (10)
    Case #PB_Event_CloseWindow
      UnhookWindowsHookEx_(hhkLLMouse)
      Quit = 1

  EndSelect  
Until Quit = 1
 UnhookWindowsHookEx_(hhkLLMouse)
End
Egypt my love
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Detect Close button click on other windows

Post by netmaestro »

RASHAD: It's an unowned 3rd party window. Different ballgame.

Here's how I would do it:

A global hook is necessary if you want to tap into the event queue of an unowned window. This requires a hook procedure located in a compiled DLL. I know it's a pain but there really is no way around this.

Place the following 3 programs in the same folder.

Main Program:

Code: Select all

Global hook.l, hooklib, address.l, hwndTarget.l

Procedure WinProc(hWnd, msg, wParam, lParam)
  result = #PB_ProcessPureBasicEvents
  Select msg
    Case #WM_USER+100
      hwndTarget = lParam
      UnhookWindowsHookEx_(Hook)
      ; Stupid animation
      For i=1 To 10
        Delay(40)
        GetWindowRect_(hwndTarget, @wr.RECT)
        MoveWindow_(hwndTarget, wr\left-10, wr\top, wr\right-wr\left, wr\bottom-wr\top, #True)
        Delay(40)
        GetWindowRect_(hwndTarget, @wr.RECT)
        MoveWindow_(hwndTarget, wr\left+10, wr\top, wr\right-wr\left, wr\bottom-wr\top, #True)
      Next
      SendMessage_(hwndTarget, #WM_SYSCOMMAND, #SC_CLOSE, 0)
      While IsWindow_(hwndTarget)
        Delay(1)
      Wend
      Hook = SetWindowsHookEx_(#WH_CBT, address, LibraryID(hooklib), 0)      
  EndSelect
  ProcedureReturn result
EndProcedure

OpenWindow(0,0,0,320,240,"Calling Window",#PB_Window_SystemMenu)
SetWindowCallback(@WinProc())

hooklib = OpenLibrary(#PB_Any, "wh_winproc.dll")
If hooklib
  address = GetFunction(hooklib, "CBTProc")
  Hook = SetWindowsHookEx_(#WH_CBT, address, LibraryID(hooklib), 0)
Else
  MessageRequester("oops!", "Could not open hook dll... quitting.")
EndIf

Repeat
  EventID = WaitWindowEvent()
Until EventID = #PB_Event_CloseWindow

UnhookWindowsHookEx_(Hook)

End
Compiled DLL:

Code: Select all

ProcedureDLL AttachProcess(instance)
  Global Caller = FindWindow_(0, "Calling Window")
EndProcedure

ProcedureDLL CBTProc(nCode, wParam, lParam)
  hwndTarget = FindWindow_(0, "Testing123")
  If nCode = #HCBT_SYSCOMMAND
    If wParam = #SC_CLOSE 
      If hwndTarget = GetForegroundWindow_()
        PostMessage_(Caller, #WM_USER+100, 0, hwndTarget)
        ProcedureReturn 1
       EndIf
    EndIf
  Else
    ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)
  EndIf
EndProcedure
Unowned window to test with:

Code: Select all

OpenWindow(0,0,0,320,240,"Testing123",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
StickyWindow(0,1)

Repeat
  EventID = WaitWindowEvent()
Until EventID = #PB_Event_CloseWindow
1. Compile the second code as a shared dll
2. Run the third code
3. Run the first code
4. Close the window titled "Testing123".

The main code watches for the close of the test window using the hook proc in the dll. When the hook proc detects the close attempt, it signals the main program which then:
1. Disables the hook
2. Animates the test window (can't use Animate window here if you don't own the window)
3. Closes the test window
4. Reinstalls the hook for next time

Any questions, just ask. If I'm not around RASHAD or srod or someone will know what's up.
BERESHEIT
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

Thanks for pointing me in the right direction, Netmaestro. However, if I try to use another third-party window, like "Untitled - Notepad", it doesn't work. Yes, I've changed the window name in the DLL code.

Also, if I remove StickyWindow() from the "Testing123" window code, it doesn't close that window after animating, until I move the mouse on it. Not all windows will be sticky, but I should be able to work out a way to fix that.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Detect Close button click on other windows

Post by netmaestro »

To make it close in all cases, change the SendMessage in the mainprogram to PostMessage:

Code: Select all

PostMessage_(hwndTarget, #WM_SYSCOMMAND, #SC_CLOSE, 0)
Should fix that part anyway. For the calculator make sure you have a hwnd for it before proceeding further.
BERESHEIT
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

I changed your DLL code to the following so that no window title is needed to be checked, and it works great for the "Testing123" window, but not for any other windows. I don't know why, because it's definitely getting a valid hwnd for the "Testing123" window. Why would the hwnd of any other non-PureBasic window (Notepad) fail?

The hwnd for Notepad (not Calculator) is just hwndTarget, isn't it? That's what the "Testing123" window returns.

Code: Select all

ProcedureDLL AttachProcess(instance)
  Global Caller = FindWindow_(0, "Calling Window")
EndProcedure

ProcedureDLL CBTProc(nCode, wParam, lParam)
  hwndTarget = GetForegroundWindow_()
  If hwndTarget <> Caller And nCode = #HCBT_SYSCOMMAND And wParam = #SC_CLOSE
    PostMessage_(Caller, #WM_USER+100, 0, hwndTarget)
    ProcedureReturn 1
  Else
    ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)
  EndIf
EndProcedure
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Detect Close button click on other windows

Post by netmaestro »

If you're on a 64bit OS and running x86 PB the dll won't inject into the calculator. Beyond that, I don't know.
BERESHEIT
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

That's probably it, then, because that's the case. Your code works with all my PureBasic apps, but not any third-party apps. I'll keep trying. Thanks for your help!
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4661
Joined: Sun Apr 12, 2009 6:27 am

Re: Detect Close button click on other windows

Post by RASHAD »

Hi NM
Very glad to see you around :D
I hope you are doing good
Best wishes to you

I know that it is Third party application

@BarryG
I hope it will satisfy your needs

Code: Select all

Global hhkLLMouse,hwnd,tx,ty,flag

RunProgram("notepad.exe")
Repeat
  hwnd = FindWindow_(0,"Untitled - Notepad")
Until hwnd

Procedure MouseHook(nCode, wParam, lParam)
    *p.MOUSEHOOKSTRUCT = lParam
      Select wParam
        Case #WM_LBUTTONDOWN , #WM_RBUTTONDOWN
          x = *p\pt\x
          y = *p\pt\y
          If x > tx  And x < tx+50 And y > ty And y < ty+30
            flag = 1
            ProcedureReturn 1         
          Else
            ;Debug x
            ;Debug y
          EndIf
      EndSelect
  ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)
EndProcedure

Procedure watchdog(par)
  Repeat
    Delay(100)
    GetWindowRect_(hwnd,r.RECT)
    tx = r\right - 50
    ty = r\top
    If flag = 1
       flag = 0
      result = MessageRequester("GO","You are on Target",#PB_MessageRequester_YesNo)
      If result = #PB_MessageRequester_Yes
        SendMessage_(hwnd, #WM_SYSCOMMAND, #SC_CLOSE, 0)
        Quitt = 1
      EndIf
    EndIf
  Until Quitt = 1
EndProcedure

OpenWindow(0, 0, 0, 300, 100, "Global Hook", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)

thread = CreateThread(@watchdog(),30)

hhkLLMouse = SetWindowsHookEx_(#WH_MOUSE_LL, @MouseHook(), GetModuleHandle_(0), 0)

Repeat
  Select WaitWindowEvent (10)
    Case #PB_Event_CloseWindow
      UnhookWindowsHookEx_(hhkLLMouse)
      Quit = 1

  EndSelect 
Until Quit = 1
 UnhookWindowsHookEx_(hhkLLMouse)
End
Egypt my love
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

Doesn't work Rashad; it only appears to because our process launched Notepad. It won't work with existing Notepads.

Netmaestro's solution works but apparently not with my 32-bit app on my 64-bit PC. I don't really want to use the 64-bit version of PureBasic if I can help it, because it reduces my customer base (a lot still use 32-bit Windows, and even I do on an old laptop or two), as well as breaking a few procedures that I can't update to 64-bit code right now.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4661
Joined: Sun Apr 12, 2009 6:27 am

Re: Detect Close button click on other windows

Post by RASHAD »

Just comment

Code: Select all

   ;RunProgram("notepad.exe")
And it will work with existing running Notepad
Be sure you got the right handle
Because the Notepad Title Text depends on the opened file :wink:

Tested with PB 5.71 x86-x64 - Windows 10 x64
Last edited by RASHAD on Sun Jan 26, 2020 8:10 am, edited 1 time in total.
Egypt my love
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: Detect Close button click on other windows

Post by BarryG »

RASHAD wrote:it will work with existing running Notepad
Be sure you got the right handle
Oops, you're right! I didn't have hWnd set correctly. Thank you.
Post Reply