Later, resubmitted as a tip from Fred: viewtopic.php?f=12&t=7424&hilit=real+Se ... oundWindow
Later argued by others: viewtopic.php?f=3&t=45556
I on the other hand would say, the "Real SetForegroundWindow()" function has its place, but it is not for beginners to decide where or how to use it. I'm not a beginner (not a Guru either) and I did find a legitimate place for it. Used "wisely" it can be used "safely". The following is an attempt to illustrate that."This function performs a Real SetForegroundWindow() task, but don't ever use it. It is bad!"
Code: Select all
; The following demo was assembled by Randy Walker from assorted snippets.
; Revised for use in PB ver 4.51
; Test it, alter it or use it in whole or in part however you like.
; Just be warned -- anything "you" break here forward is not my fault :)
; Of course, feel free to comment on this effort any way you like.
If InitMouse() = 0
End
EndIf
;- IMPORTANT - READ THIS - IMPORTANT
;/ IMPORTANT - READ THIS - IMPORTANT
; "Window queue management" in general is provided for by the operating system.
; You will encounter undesirable window activity if you do not honor the design
; of the operating system. Any window you create is "your" business so you can
; pretty much change foreground on "your" windows all you want using the native
; PB commands (OpenWindow, RunProgram, HidwWindow, SetActiveWindow, etc).
;
; Any window you did not create "belongs to someone else" so you MUST HONOR the
; operating system design to avoid stepping on toes. Best way I have found to
; do that is: Do not change foreground on any external app unless you plan to
; restore it (and thus, the OS window queue) before the user has any say so in
; in the matter. This demo is designed to illustrate 1 way to do exactly that.
;
; In my "real world" application, I have a problem with Skype chat windows that
; are in the foreground -- a new message will not "bonk". The chat window will
; "bonk" on a new message if the chat window is not in foreground. What I want
; to do is make sure I do not have any Skype chat windows in foreground when I
; turn away from my computer, so I can hear the "bonk" on a new message. So the
; objective in my app is basicly the same as illustrated here in this cut down
; demo. My real world app is a clock, hence the fancy Landing Pad window.
;
; This demo will:
; 1. Monitor mouse and keyboard for inactivity exceeding a threshold value.
; 2. Validate "a specific window" is foreground after exceeding the threshold.
; 3. Switch foreground away ONLY after validating 1 and 2 above.
; 4. Put everything back as it was the instant the user touches computer.
;
; Critical components in this demo to restore order to the windows queue are:
; A. Get the handle of the external window before you steal foreground.
; B. Insure you can restore external window before user can intervene.
; C. Mouse is placed on my window so I can monitor it and insure item B above.
; Do that and you should have no issues, although you may find other strategies.
;
; Note: You will notice I hide the mouse when I activate foreground swap.
; The only reason for hiding the mouse is to avoid user disorientation.
; DEMO INSTRUCTIONS:
; Launch this program, select a delay value from 5 to 99 seconds and press enter.
; Open the "Windows Calculator" program so that it is in foreground.
; Watch the Calculator window but do not touch the keyboard or mouse during your
; specified delay time.
; After delay and foreground is removed from Calculator, move mouse or press any
; key to restore the calculator to foreground.
Structure LASTINPUTINFO ; requred by GetLastInputInfo API to determine last activity.
cbSize.l
dwTime.l
EndStructure
Global lippi.LASTINPUTINFO ; Wow! Who understands this stuff??!
lippi\cbSize = SizeOf(LASTINPUTINFO) ; Compensating for "dumb" API I guess??
Global mPntr.POINT ; Used to restore mouse position -- << DO NOT REUSE
Global CursorPosition.POINT ; can reuse
Global wSpec.RECT ; can reuse
Global TrumpLimit.l, stolenWndw, dsktp.l ,quit.l, wasMinimized.l
; TrumpLimit.l is used to store user defined inactivity threshold
Procedure ReallySetForegroundWindow(Window)
hWnd = WindowID(Window)
; If the window is in a minimized state, maximize now
If GetWindowLong_(hWnd, #gwl_style) & #WS_MINIMIZE
ShowWindow_(hWnd, #SW_MAXIMIZE)
UpdateWindow_(hWnd)
EndIf
; Check To see If we are the foreground thread
foregroundThreadID = GetWindowThreadProcessId_(GetForegroundWindow_(), 0)
ourThreadID = GetCurrentThreadId_()
; If not, attach our thread's 'input' to the foreground thread's
If (foregroundThreadID <> ourThreadID)
AttachThreadInput_(foregroundThreadID, ourThreadID, #True);
EndIf
; Bring our window To the foreground
SetForegroundWindow_(hWnd)
; If we attached our thread, detach it now
If (foregroundThreadID <> ourThreadID)
AttachThreadInput_(foregroundThreadID, ourThreadID, #False)
EndIf
; Force our window To redraw
InvalidateRect_(hWnd, #Null, #True)
EndProcedure
Procedure ActivityCheck() ;/ Providing for steps 1 2 3 as described above in "READ THIS" section
If stolenWndw = #False ; Don't bother checking foreground window if it was already stolen.
If TrumpLimit > 0 ; zero means disabled, else check against number of seconds as specified by user setting.
GetLastInputInfo_(@lippi)
timeNow.l = ElapsedMilliseconds() ; get current timestamp value for comparison calculations
If ((timeNow.l - lippi\dwTime)/1000) > TrumpLimit ; << compare inactivity time against user setting.
hTopWin = GetForegroundWindow_() ; Will foreground be "the" Windows Calculator???
classname$ = Space(1024) ; use window classname to verify foreground "is" the calculator window.
GetClassName_(hTopWin, @classname$, 1024) ; API to capture classname into buffer.
;-You can swap the following if statements here if you use Skype and want to demo with that instead.
;If classname$="TskMultiChatForm.UnicodeClass" Or classname$="TConversationForm.UnicodeClass"
If classname$="SciCalc" ; only Windows' "Calculator" window per say uses this "SciCalc" classname!!!
If GetWindowState(1) = #PB_Window_Minimize ;/ DO NOT STEAL FOCUS WHILE LANDING PAD IS MINIMIZED!!!!
SetWindowState(1,#PB_Window_Normal) ;/ CRITICAL PART OF HONORING WINDOW QUEUE MANAGEMENT!!!!
wasMinimized = #True ;/ CRITICAL PART OF HONORING WINDOW QUEUE MANAGEMENT!!!!
EndIf ;/ DO NOT STEAL FOCUS WHILE LANDING PAD IS MINIMIZED!!!!
GetCursorPos_(mPntr.POINT) ; Store mouse position so you can return it to original position.
stolenWndw = hTopWin ; store the calculator handle so you can restore it to foreground later.
ReallySetForegroundWindow(1) ; force ONLY the qualifying calculator out of the foreground.
GetWindowRect_(WindowID(1),wSpec) ; Find your "landing pad" window to reposition the mouse.
SetCursorPos_(wSpec\left+60,wSpec\top+62) ; Place mouse on "landing pad" window to monitor new mouse activity.
ShowCursor_(0) ; hide the mouse so user does not become disoriented seeing the mouse jump around.
EndIf ; mouse position and visibility are restored later when user returns with new input activity.
EndIf
EndIf
EndIf
EndProcedure
Procedure InitImage()
img = CreateImage(1,126,133)
If img = 0
MessageRequester("ERROR","Cant create image!",#MB_ICONERROR):End
EndIf
LoadFont(1,"Verdana",8,#PB_Font_Italic | #PB_Font_Bold)
If StartDrawing(ImageOutput(1))
Box(1,1,119,124,$80BBDF) ; Pretty gold framing :-0
Box(2,4,117,117,$A0E4F0) ; Pretty gold framing :-0
Box(4,6,113,113,$80BBDF) ; Pretty gold framing :-0
Box(6,8,109,109,$A0E4F0) ; Pretty gold framing :-0
Box(8,10,105,105,$80BBDF) ; Pretty gold framing :-0
Box(10,12,101,101,$A0E4F0) ; Pretty gold framing :-0
Box(12,14,97,97,$80BBDF) ; Pretty gold framing :-0
Box(14,16,93,93,$A0E4F0) ; Pretty gold framing :-0
LineXY( 1,127,121,127, $666666) ; Shadow line across bottom
ForeGround = GetSysColor_(#COLOR_BTNTEXT)
FrontColor(RGB(Red(ForeGround),Green(ForeGround),Blue(ForeGround)))
DrawingMode(1)
DrawingFont(FontID(1))
DrawText(20,26,"Landing Pad")
DrawText(22,46,"here allows")
DrawText(16,66,"monitoring of")
DrawText(14,86,"hidden mouse.")
StopDrawing()
EndIf
ProcedureReturn img
EndProcedure
Procedure configure()
Static secflag.l, alarmh$, alarmm$, alarms$, trump$
; figure out where to show the configuratin window according to current mouse position.
SM_CXscreen = GetSystemMetrics_(#SM_CXSCREEN)
SM_CYscreen = GetSystemMetrics_(#SM_CYSCREEN)
GetCursorPos_(@CursorPosition.POINT)
wx.l = CursorPosition\x-40 ; used to determine configuration window placement.
wy.l = CursorPosition\y-124 ; used to determine configuration window placement.
; Don't let creation of configuration window go off edge of screen.
If wx < 2
wx = 2
EndIf
If wx > SM_CXscreen - 115
wx = SM_CXscreen - 115
EndIf
If wy < 30
wy = 30
EndIf
If wy > SM_CYscreen - 130
wy = SM_CYscreen - 130
EndIf
;- Create the configuration window (now that we know where).
trump$ = Right("00"+Str(TrumpLimit),2) ; trump$ is used to receive user defined inactivity threshold
If OpenWindow(0,wx,wy,105,140," Inactivity Delay",#WS_DLGFRAME)
If CreateGadgetList(WindowID(0))
ButtonGadget (100, 10, 90, 35, 24, "OK")
StringGadget (107, 10, 10, 20, 18,trump$, #PB_String_Numeric|#ES_MULTILINE|#ES_AUTOVSCROLL|$10000000)
ButtonGadget (108, 50, 90, 48, 24, "ABORT")
TextGadget (109, 8, 16, 95, 65," seconds of user inactivity before calculator loses focus to the mouse landing pad.")
SendMessage_(GadgetID(107),#EM_SETLIMITTEXT,2,0)
EndIf
EndIf
SetActiveGadget(100)
q.l=#False
Repeat
oldgadget = GetActiveGadget()
Select WaitWindowEvent()
Case #PB_Event_Gadget
Gadget = EventGadget()
Select Gadget
Case 107 ; Highlight text upon first selection of this gadget
If oldgadget <> Gadget ; Catch Caret relocation
PostMessage_(GadgetID(Gadget),#EM_SETSEL,2,0) ; highlight
EndIf
Case 100 ; OK
q = #True
Case 108 ; Kill
quit = #True
Break
EndSelect
Case #PB_Event_Timer
ActivityCheck()
Case #WM_KEYDOWN
Select EventwParam()
Case #VK_RETURN
s$=GetGadgetText(oldgadget) ;- Stop the stupid CR/LF "DING" on StringGadget input
k.l=FindString(s$,Chr(13)+Chr(10),1)
If k.l<>0 ; Was Enter pressed on the StringGadget?
s$ = ReplaceString(s$,Chr(13)+Chr(10),"")
SetGadgetText(oldgadget,s$) ; Yes, so remove CR+LF.
WaitWindowEvent()
Repeat : _mess = WindowEvent() : Until _mess = 0
EndIf ;- Stupid CR/LF "ding" has been removed
Break
EndSelect
EndSelect
Until q.l
TrumpLimit = Val(GetGadgetText(107))
If (TrumpLimit > 0) And (TrumpLimit < 5) ; Allow values of zero to disable monitoring.
TrumpLimit = 5 ; do not accept obsured inactivity setting less than 5 seconds
EndIf
trump$=Right("00"+Str(TrumpLimit),2)
SetGadgetText(107,trump$)
CloseWindow(0)
EndProcedure
Procedure WinCallback(hWnd, uMsg, wParam, lParam)
; Windows fills the parameter automatically, which we will use in the callback...
If hWnd = WindowID(1)
Select uMsg
Case #WM_SYSCOMMAND ; Only effective if window 1 style excludes BorderLess|DLGFRAME.
If wParam=100 ; 100 is reference to value inside the AppendMenu_ line seen below.
configure()
EndIf
Case #WM_SIZE ; Only effective if window 1 style excludes BorderLess|DLGFRAME.
Select wParam
Case 100 ; from the GetSystemMenu user selection
configure()
Case #SIZE_MINIMIZED ;/ Track Landing Pad window status in case user minimizes manually.
wasMinimized = #True ;/ CRITICAL PART OF HONORING WINDOW QUEUE MANAGEMENT!!!!
Case #SIZE_RESTORED
wasMinimized = #False ;/ CRITICAL PART OF HONORING WINDOW QUEUE MANAGEMENT!!!!
EndSelect
EndSelect
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
TrumpLimit = 5 ;/ Zero value will disable the "force calculator off foreground" operations
configure() ; allow user to define delay before entering main program (delay range is 5 to 99 seconds)
;- /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\
;- ENTERING MAIN PROGRAM LOOP
;- \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/
If OpenWindow(1,3,3,123,129," ... Demo Landing Pad Window")
CreateGadgetList(WindowID(1))
ImageGadget(1,1,1,124,130,InitImage())
; Next 3 lines are only useful if window style excludes #PB_Window_BorderLess|#WS_DLGFRAME.
menubar.l=GetSystemMenu_(WindowID(1),0)
ModifyMenu_(menubar.l,#SC_CLOSE,#MF_BYCOMMAND,#SC_CLOSE,"CLOSE Inactivity Monitor") ; ,"Close 3amAlarm")
AppendMenu_(menubar.l,#MF_STRING,100,"Setting...") ; used in main loop under WM_SYSCOMMAND
SetWindowCallback(@WinCallback()) ; activate the callback to monitor system tray icon
; no idea how or why the following 4 lines allow window dragging (but I like it :-)
Frame3DGadget(5, 0, 0, 400, 300, "", #PB_Frame3D_Flat)
SetWindowLong_(GadgetID(1), #gwl_style, GetWindowLong_(GadgetID(1), #gwl_style) &~#SS_NOTIFY)
#BS_FLAT=$8000
SetWindowLong_(GadgetID(1),#gwl_style,GetWindowLong_(GadgetID(1),#gwl_style) |#BS_FLAT)
SetTimer_(WindowID(1),0,1000,0) ; Timer message triggers at 1000 milliseconds (1 second interval)
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
quit = #True
Case #WM_LBUTTONDBLCLK ; Double Click to Minimize landing pad window
ShowWindow_(WindowID(1),#SW_MINIMIZE)
Case #WM_RBUTTONDOWN ; Opens the options window for inacativity delay setting or exit.
configure()
Case #WM_LBUTTONDOWN ; Hold and drag to move landing pad.
SendMessage_(WindowID(1),#WM_NCLBUTTONDOWN, #HTCAPTION,0)
Case #PB_Event_CloseWindow ; Do not allow accidental close -- must use right click option!!!
; Quit = #True ; << Alternative action is possible here, but threatens reliability.
ShowWindow_(WindowID(1),#SW_MINIMIZE) ; (<< better to just minimize instead)
Case #PB_Event_Timer ; test trigger-setting against elapsed user inactivity.
ActivityCheck()
Default;/ Providing for steps 4 as described above in "READ THIS" section.
;/ Priority monitoring to catch new keyboard or mouse activity when user returns to computer.
If stolenWndw ; don't bother with restoration if focus was not stolen from Windows Calculator
GetLastInputInfo_(@lippi) ; Small time difference here means new user input at keyboard or mouse.
timeNow.l = ElapsedMilliseconds() ; get current timestamp value for comparison calculations.
If timeNow.l - lippi\dwTime < 4
SetForegroundWindow_(stolenWndw)
stolenWndw = #False ; reset our stolen window bookmarker to zero until next qualifying event.
SetCursorPos_(mPntr\x,mPntr\y) ; return mouse to original position before we stole window focus.
ShowCursor_(1) ; let mouse be visible in original position.
If wasMinimized
SetWindowState(1,#PB_Window_Minimize)
EndIf
EndIf
EndIf ;/ NOTE!!! >>> Landing pad window for mouse simply insures I catch any new mouse activity ! ! !
EndSelect ;/ Seemingly random forground window transitions will occur if you miss new mouse or key input.
Until quit
KillTimer_(WindowID(1),0)
EndIf