[PB4] Timed MessageRequester

Share your advanced PureBasic knowledge/code with the community.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

[PB4] Timed MessageRequester

Post by kenmo »

Yeah, so, a topic in Coding Questions inspired me to write this: a MessageRequester with a timeout (in milliseconds). If it does time out, it returns 0. I managed to work it into a single procedure (using an unusual thread approach) but you still need to declare a few global variables. I don't think there's a way around that. Oh well.

Code: Select all

; Timed Message Requester by kenmo

; Required declarations
Global TMR_Title.s, TMR_Text.s, TMR_Flags.l, MRP_Return.l

; The procedure
Procedure.l TimedMessageRequester(Title.s, Text.s, Timeout.l, Flags.l = #Null)
	Protected Thread.l, StartTime.l, Confirmed.l
	TMR_Title = Title : TMR_Text = Text
	TMR_Flags = Flags : MRP_Return = #Null
	
	Thread = CreateThread(?MessageRequesterProc, 0)
	If Thread
		Confirmed = #False : Time = ElapsedMilliseconds()
		Repeat
			If IsThread(Thread) = #False
				Confirmed = #True : Break
			EndIf
		Until ElapsedMilliseconds() - Time > Timeout
		If Confirmed
			ProcedureReturn MRP_Return
		Else
			KillThread(Thread) : ProcedureReturn #Null
		EndIf
	Else
		ProcedureReturn #Null
	EndIf
	
	MessageRequesterProc:
		MRP_Return = MessageRequester(TMR_Title, TMR_Text, TMR_Flags)
	Return
EndProcedure

; Example
Select TimedMessageRequester("Confirm", "Is this procedure quite nifty?", 5000, #PB_MessageRequester_YesNo|#MB_ICONQUESTION)
	Case #PB_MessageRequester_Yes
		Debug "Yes, it is!"
	Case #PB_MessageRequester_No
		Debug "No, it ain't!"
	Case #Null
		Debug "... You didn't answer in 5 seconds."
	Default
		Debug "You should never encounter this!"
EndSelect
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

That's nice!
I like logic, hence I dislike humans but love computers.
Phoenix
Enthusiast
Enthusiast
Posts: 141
Joined: Sun Sep 04, 2005 2:25 am

Re: [PB4] Timed MessageRequester

Post by Phoenix »

Why is there a Return without Gosub there???? Won't that cause a stack problem????
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Post by kenmo »

The procedure never reaches the Return. It's there so you can call the MessageRequesterProc section of code as if it is its own procedure. That's so I could contain it all in one procedure. It's a pretty unorthodox method, but it's been shown to work fine.

For example:

Code: Select all

CallFunctionFast(?Test)
End

Test:
MessageRequester("", "One")
Return
MessageRequester("", "Two")
Not typical, but as far as I can tell it is safe.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Post by kenmo »

Here's the "normal" (safer?) way:

Code: Select all

; The procedures
Procedure MessageRequesterProc()
	MRP_Return = MessageRequester(TMR_Title, TMR_Text, TMR_Flags)
EndProcedure
Procedure.l TimedMessageRequester(Title.s, Text.s, Timeout.l, Flags.l = #Null)
	Protected Thread.l, StartTime.l, Confirmed.l, Time.l
	TMR_Title = Title : TMR_Text = Text
	TMR_Flags = Flags : MRP_Return = #Null
	
	Thread = CreateThread(@MessageRequesterProc(), 0)
	If Thread
		Confirmed = #False : Time = ElapsedMilliseconds()
		Repeat
			If IsThread(Thread) = #False
				Confirmed = #True : Break
			EndIf
		Until ElapsedMilliseconds() - Time > Timeout
		If Confirmed
			ProcedureReturn MRP_Return
		Else
			KillThread(Thread) : ProcedureReturn #Null
		EndIf
	Else
		ProcedureReturn #Null
	EndIf
EndProcedure
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

kenmo wrote:Here's the "normal" (safer?) way:
I don't know if it's needed or not, maybe an expert have an opinion on this?
I like logic, hence I dislike humans but love computers.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

What do you guys think of this approach? You start one thread at program start that watches all messageboxes, and you create one global variable that contains the current timeout. Then you just set the timeout and call the messagebox. The thread will catch it and manage it:

Code: Select all

Global MsgTm.l 

Procedure MessageTimer(param) 
  Repeat 
    Repeat 
      hWnd = FindWindow_("#32770",#Null) 
      Delay(50) 
    Until hwnd <> 0 Or MsgTm = 0
    If MsgTm
      msgrect.rect 
      GetClientRect_(hwnd, @msgrect)
      SetWindowPos_(hwnd,0,0,0,msgrect\right-msgrect\left,msgrect\bottom-msgrect\top+60,#SWP_NOMOVE|#SWP_NOZORDER)
      MyText=CreateWindowEx_(#WS_EX_LEFT,"Static","",#WS_CHILD|#SS_CENTER,0,msgrect\bottom,msgrect\right-msgrect\left,20,hwnd,0,GetModuleHandle_(0),0)
      SendMessage_(MyText, #WM_SETFONT, GetStockObject_(#DEFAULT_GUI_FONT), 0)
      ShowWindow_(MyText,#SW_SHOW)
      starttime=ElapsedMilliseconds() 
      SetWindowText_(MyText,"Waiting for "+Str(MsgTm)+" seconds...")   
      cc=0
      While cc <= MsgTm And IsWindowVisible_(hwnd) 
        If ElapsedMilliseconds()-starttime >= 1000
          cc+1
          starttime=ElapsedMilliseconds()
          SetWindowText_(MyText,"Waiting for "+Str(MsgTm-cc)+" seconds...")                        
        EndIf
        SendMessage_(hwnd,#WM_CTLCOLORSTATIC,0,MyText)
        Delay(1)
      Wend 
      SetParent_(MyText, 0)
      DestroyWindow_(MyText)
      SendMessage_(hwnd, #WM_CLOSE,0,0) 
      hwnd=0
    EndIf
    Delay(50) 
  ForEver   
EndProcedure 

CreateThread(@MessageTimer(),0) 

MsgTm = 5    
MessageRequester("Notice","This won't wait forever! There's a requester manager watching it! ",#PB_MessageRequester_YesNoCancel|#MB_ICONINFORMATION )
MsgTm = 10     
MessageRequester("Notice","Neither will this one! ",#PB_MessageRequester_YesNoCancel|#MB_ICONINFORMATION )
MsgTm = 0 ; use 0 for no timeout      
MessageRequester("Notice","This one isn't managed, it'll wait forever ",#PB_MessageRequester_YesNoCancel|#MB_ICONINFORMATION  )

End 
Last edited by netmaestro on Sat Jul 01, 2006 12:21 pm, edited 4 times in total.
BERESHEIT
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

But who uses timeout on a messagerequester anyway? :lol:
I like logic, hence I dislike humans but love computers.
eJan
Enthusiast
Enthusiast
Posts: 366
Joined: Sun May 21, 2006 11:22 pm
Location: Sankt Veit am Flaum

Post by eJan »

Thanks kenmo & netmaestro! :wink:
Used before PB in AutoIt: http://www.autoitscript.com/autoit3/doc ... MsgBox.htm in combination with Backup Magic.

Code: Select all

If lbdt_ProcessIt_FindProcess("bmagic.exe") ; LBDT ProcessIt Library
  lbdt_ProcessIt_KillProcess("bmagic.exe")
  
  Select TimedMessageRequester("Confirm", "Backup completed." + #CRLF$ + "Shutdown Computer?", 10000, #PB_MessageRequester_YesNo|#MB_ICONQUESTION) 
    Case #PB_MessageRequester_Yes 
      ShutDown(0)
    Case #PB_MessageRequester_No 
      End 
    Case #Null 
      ShutDown(0) 
  EndSelect
EndIf
Phoenix
Enthusiast
Enthusiast
Posts: 141
Joined: Sun Sep 04, 2005 2:25 am

Post by Phoenix »

Joakim Christiansen wrote:But who uses timeout on a messagerequester anyway? :lol:
What's so funny???? Could be a MessageRequester like "Click Cancel to abort scan" and if nothing is clicked then the scan should start....
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

I have something in the announcements section called ManagedMessageBox. I'd appreciate any testing you could give it, thanks!
BERESHEIT
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Post by breeze4me »

Another MessageRequester using hook and thread.
Only tested on XP pro with PB v4.
Enjoy. :D

Code: Select all

Structure mbData
  hHook.l
  hMBox.l
  x.l
  y.l
  flags.l
  time.l
  hInstance.l
  hThreadId.l
EndStructure

Global mb.mbData

Procedure tm(p)
  Protected i, c, hDef, res
  Debug "Timer Activated"
  Repeat
    With mb
      If \hMBox ;if exist MessageBox then..
;         ;1. Update remained time both messagebox title and textgadget.
;         buf=AllocateMemory(200)
;         GetWindowText_(\hMBox, buf, 200)
;         tmp$=PeekS(buf)
;         For i=\time To 1 Step -1
;           If IsGadget(999)
;             PokeS(buf, tmp$+" - Select in "+Str(i)+" sec.")
;             SetWindowText_(\hMBox, buf)
;             SetGadgetText(999, "Select in "+Str(i)+" sec.")
;           Else
;             FreeMemory(buf)
;             Break 2
;           EndIf
;           Delay(1000)
;         Next
;         FreeMemory(buf)
        
        ;2. Only update textgadget's time.
        For i=\time To 1 Step -1
          If IsGadget(999)
            SetGadgetText(999, "Select in "+Str(i)+" sec.")
          Else
            Break 2
          EndIf
          Delay(1000)
        Next
        
        Debug "Time over"
        
        If \flags & #MB_DEFBUTTON3
          c=2
        ElseIf \flags & #MB_DEFBUTTON2
          c=1
        Else
          c=0
        EndIf
        ;find default button's handle.
        For i=0 To c
          hDef = GetNextDlgTabItem_(\hMBox, hDef, 0)
        Next
        ;get the value of default button.
        res = GetDlgCtrlID_(hDef)
        EndDialog_(\hMBox, res)
        Break
      EndIf
    EndWith
    Delay(1)
  ForEver
  Debug "Timer Deactivated"
EndProcedure

Procedure MBoxHookProcAddTxt(uCode, wParam, lParam)
  Protected r.RECT
  If uCode = #HCBT_ACTIVATE
    UnhookWindowsHookEx_(mb\hHook)
    GetClientRect_(wParam, @r)
    CreateGadgetList(wParam)
    TextGadget(999, 0, r\bottom-r\top-20, r\right-r\left, 20, "",#PB_Text_Center)
  EndIf
  ProcedureReturn #False
EndProcedure

Procedure MBoxHookProc(uCode, wParam, lParam)
  Protected *tmp1.CBT_CREATEWND, *tmp2.CREATESTRUCT
  If uCode = #HCBT_CREATEWND
    With mb
      UnhookWindowsHookEx_(\hHook)
      \hMBox = wParam
      *tmp1 = lParam
      *tmp2 = *tmp1\lpcs
      ;set new x, y values.
      If \x >= 0 : *tmp2\x = \x : EndIf
      If \y >= 0 : *tmp2\y = \y : EndIf
      If \time > 0
        *tmp2\cy + 20
        \hHook = SetWindowsHookEx_(#WH_CBT, @MBoxHookProcAddTxt(), \hInstance, \hThreadId)
      EndIf
    EndWith
    ProcedureReturn #False
  EndIf
EndProcedure

Procedure MBox(hParentWnd, Title$, Text$, DelayTime=0, x=-1, y=-1, Flags=0)
;Procedure MBox(Title$, Text$, DelayTime=0, x=-1, y=-1, Flags=0) ;Use with below "MessageRequester(...)".
  Protected tm, res
  With mb
    \hMBox = 0
    \x=x : \y=y : \flags=Flags
    \time = DelayTime
    ;Flags | #MB_APPLMODAL
    \hInstance = GetModuleHandle_(0)
    \hThreadId = GetCurrentThreadId_()
    \hHook = SetWindowsHookEx_(#WH_CBT, @MBoxHookProc(), \hInstance, \hThreadId)
    If DelayTime > 0
      tm = CreateThread(@tm(),0)  ;start timer.
    EndIf
    ;res = MessageRequester(Title$, Text$, Flags) ;without hParentWnd param.
    res = MessageBox_(hParentWnd, Text$, Title$, Flags)
    \hMBox = 0
  EndWith
  ;Delay(1)
  If tm And IsThread(tm)
    KillThread(tm)
  EndIf
  ProcedureReturn res
EndProcedure

Procedure SelBtn(res)
; #IDOK = 1
; #IDCANCEL = 2
; #IDABORT = 3
; #IDRETRY = 4
; #IDIGNORE = 5
; #IDYES = 6
; #IDNO = 7
  Select res
    Case #IDOK : msg$="OK"
    Case #IDCANCEL : msg$="Cancel"
    Case #IDABORT : msg$="Abort"
    Case #IDRETRY : msg$="Retry"
    Case #IDIGNORE : msg$="Ignore"
    Case #IDYES : msg$="Yes"
    Case #IDNO : msg$="No"
  EndSelect
  MessageRequester("Selected", msg$)
EndProcedure

hwnd=OpenWindow(0,100,100,400,300,"MBox Test")

res=MBox(hwnd, "test0", "Normal")
SelBtn(res)

res=MBox(hwnd, "test1", "Esc to cancel", 6, 10, -1, #MB_ICONASTERISK|#MB_YESNOCANCEL|#MB_DEFBUTTON3)
SelBtn(res)

res=MBox(hwnd, "test2", "waiting...", 4, -1, 100)
SelBtn(res)

StickyWindow(0,1)

res=MBox(hwnd, "test3", "Esc to cancel", 0, 50, 300, #MB_ICONERROR|#MB_DEFBUTTON3|#MB_OKCANCEL)
SelBtn(res)

res=MBox(hwnd, "test4", "Esc to cancel", 4, -1, -1, #MB_ICONERROR|#MB_DEFBUTTON3|#MB_OKCANCEL)
SelBtn(res)

res=MBox(hwnd, "test5", "waiting...", 4, -1, -1, #MB_ICONEXCLAMATION|#MB_DEFBUTTON1|#MB_ABORTRETRYIGNORE)
SelBtn(res)

Repeat
Until WaitWindowEvent()=#WM_CLOSE
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Post by breeze4me »

Another one, using hook and timer.
Tested on XP pro.

Code: Select all

Structure mbData
  hHook.l
  x.l
  y.l
  flags.l
  time.l
  TxtGadget.l
  oldWndProc.l
EndStructure

Global mb.mbData

Procedure MBWndProc(hWnd, uMsg, wParam, lParam)
  Protected i, c, hDef, val, res
  Protected wi.WINDOWINFO, x, y, w, h, fl=#SWP_NOZORDER|#SWP_NOMOVE

  With mb
    If uMsg = #WM_TIMER ;And wParam = 1
      If \time > 0
        SetGadgetText(\TxtGadget, "Select in "+Str(\time)+" sec.")
        \time - 1
      Else
        Debug "Time over"
        If \flags & #MB_DEFBUTTON3
          c=2
        ElseIf \flags & #MB_DEFBUTTON2
          c=1
        Else
          c=0
        EndIf
        For i=0 To c
          hDef = GetNextDlgTabItem_(hWnd, hDef, 0)
        Next
        val = GetDlgCtrlID_(hDef)
        EndDialog_(hWnd, val)
      EndIf
      ;uMsg = #WM_NULL ;For message override. Without this, #WM_TIMER fired only once.
      ProcedureReturn ;or use this.
    EndIf
    
    If uMsg = #WM_INITDIALOG
      wi\cbSize = SizeOf(WINDOWINFO)
      GetWindowInfo_(hWnd, @wi)
      w = wi\rcWindow\right - wi\rcWindow\left
      h = wi\rcWindow\bottom - wi\rcWindow\top
      x = wi\rcWindow\left
      y = wi\rcWindow\top
      If \x >= 0 : x= \x : fl= fl & (~#SWP_NOMOVE) : EndIf
      If \y >= 0 : y= \y : fl= fl & (~#SWP_NOMOVE) : EndIf
      
      If \time > 0
        h + 20
        CreateGadgetList(hWnd)
        \TxtGadget=TextGadget(#PB_Any, 0, wi\rcClient\bottom-wi\rcClient\top, w, 20,"Select in "+Str(\time)+" sec.",#PB_Text_Center)
        Debug "Set timer"
        \time - 1
        SetTimer_(hWnd, 1, 1000, 0)
      Else
        fl = fl | #SWP_NOSIZE
        Debug "Remove callback"
        SetWindowLong_(hWnd, #GWL_WNDPROC, \oldWndProc)
      EndIf
      SetWindowPos_(hWnd, 0, x, y, w, h, fl)
    EndIf
    
    If uMsg = #WM_DESTROY
      Debug "Kill timer"
      KillTimer_(hWnd, 1)
      If IsGadget(\TxtGadget)
        Debug "freed"
        FreeGadget(\TxtGadget)
      EndIf
    EndIf
    res = CallWindowProc_(\oldWndProc, hWnd, uMsg, wParam, lParam)
  EndWith
  ProcedureReturn res
EndProcedure

Procedure.l MBHookProc(nCode, wParam, lParam)
  Protected *p.CWPSTRUCT
  With mb
    If nCode = #HC_ACTION
      *p = lParam
      If *p\message = #WM_INITDIALOG
        UnhookWindowsHookEx_(\hHook)
        \oldWndProc=SetWindowLong_(*p\hwnd, #GWL_WNDPROC, @MBWndProc())
        ProcedureReturn #False
      EndIf
    EndIf
    ;ProcedureReturn CallNextHookEx_(\hHook, nCode, wParam, lParam)
  EndWith
EndProcedure

Procedure MBox(hParentWnd, Title$, Text$, DelayTime=0, x=-1, y=-1, Flags=0)
  Protected hThreadId, res
  With mb
    \x=x : \y=y : \flags=Flags : \time=DelayTime
    hThreadId = GetCurrentThreadId_()
    \hHook = SetWindowsHookEx_(#WH_CALLWNDPROC, @MBHookProc(), 0, hThreadId)
    res = MessageBox_(hParentWnd, Text$, Title$, Flags)
  EndWith
  ProcedureReturn res
EndProcedure


;-------------------
;- Test
;-------------------

Procedure SelBtn(res)
  Protected msg$
  Select res
    Case #IDOK : msg$="OK"
    Case #IDCANCEL : msg$="Cancel"
    Case #IDABORT : msg$="Abort"
    Case #IDRETRY : msg$="Retry"
    Case #IDIGNORE : msg$="Ignore"
    Case #IDYES : msg$="Yes"
    Case #IDNO : msg$="No"
  EndSelect
  MessageRequester("Selected", msg$)
EndProcedure

hwnd=OpenWindow(0,100,100,400,300,"MBox Test")

res=MBox(hwnd, "test0", "Normal")
SelBtn(res)

res=MBox(hwnd, "test1", "Esc to cancel", 6, 10, -1, #MB_ICONASTERISK|#MB_YESNOCANCEL|#MB_DEFBUTTON3)
SelBtn(res)

res=MBox(hwnd, "test2", "waiting...", 4, -1, 100)
SelBtn(res)

StickyWindow(0,1)

res=MBox(hwnd, "test3", "Esc to cancel", 0, 50, 300, #MB_ICONERROR|#MB_DEFBUTTON3|#MB_OKCANCEL)
SelBtn(res)

res=MBox(hwnd, "test4", "Esc to cancel", 4, -1, -1, #MB_ICONERROR|#MB_DEFBUTTON3|#MB_OKCANCEL)
SelBtn(res)

res=MBox(hwnd, "test5", "waiting...", 4, -1, -1, #MB_ICONEXCLAMATION|#MB_DEFBUTTON1|#MB_ABORTRETRYIGNORE)
SelBtn(res)
User avatar
skywalk
Addict
Addict
Posts: 4221
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: [PB4] Timed MessageRequester

Post by skywalk »

Thanks kenmo.
kenmo wrote:There are a handful of similar solutions here:

http://www.purebasic.fr/english/viewtop ... erequester
BTW, your "safe" method doesn't work. :?
The single procedure with the label is OK, except for a warning:

[12:12:57] Waiting for executable to start...
[12:12:57] Executable type: Windows - x86 (32bit)
[12:12:57] Executable started.
[12:12:57] [WARNING] Line: 11
[12:12:57] [WARNING] The specified '@ProcedureName()' is not a Procedure pointer.
[12:13:02] The Program execution has finished.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Mohawk70
Enthusiast
Enthusiast
Posts: 404
Joined: Thu May 11, 2006 1:04 am
Location: Florida, USA

Re: [PB4] Timed MessageRequester

Post by Mohawk70 »

How about forcing a delay before any of the buttons can be clicked ? Any idea how to accomplish that ?
HP Z800 Workstation
CPU : Dual Xeon 5690 3.46GHz
RAM : 96GB RAM ( 8GB x 12 )
PSU : 1100W
GPU : NVIDIA RTX 3050 8GB
STORAGE : 9TB
(4) 2TB Seagate IronWolf Pro HDD
(1) 1TB Samsung 870 EVO SSD
Post Reply