Page 1 of 1

MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 1:01 am
by oO0XX0Oo
Hi,

I haven't found a way to subclass the MessageRequester() and using a different font in it (I need a monospaced font!)
so I've created a normal window that looks like it.

We are supposed to have only one! Repeat .. Until loop regardless of how many windows we are using.

So my problem with my solution is, that it's using its own loop here. I could integrate it into a main loop that decides
per window what to do but I'd like to put this thing into a module to allow easier integration in small programs (that
don't necessarily use a window on its own)...

Additionally, will I run into problems as well because I'm using #PB_Any for all of my gadgets here, knowing that a larger
application with it's own window will get there gadget ids via an enumeration?

Code: Select all

#SQ = Chr(39)
#DCRLF = #CRLF$ + #CRLF$

; Possible icons:
; #IDI_ERROR, #IDI_EXCLAMATION, #IDI_QUESTION, #IDI_INFORMATION
Procedure MessageBox(title.s="", message.s="", width.i=0, height.i=0, icon.i=#IDI_ERROR, exit.b=#True)
  Protected.i minwidth = 460, minHeight = 325
  Protected.i hWnd, hButton, hContainer, hFont, hText
  Protected.b endLoop = #False

  ; Make sure we reach at least the minimum width & height
  If width < minwidth : width = minwidth : EndIf
  If height < minHeight : height = minHeight : EndIf

  hWnd = OpenWindow(#PB_Any, 0, 0, width, height, title, #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  If hWnd
    hButton = ButtonGadget(#PB_Any, width - 95, height - 35, 85, 25, "OK")

    hContainer = ContainerGadget(#PB_Any, 0, 0, width, height - 45)
      SetGadgetColor(hContainer, #PB_Gadget_BackColor, $FFFFFF)
      ImageGadget(#PB_Any, 25, 25, 20, 20, LoadIcon_(0, icon))

      hFont = LoadFont(#PB_Any, "Consolas", 8)
      If hFont
        SetGadgetFont(#PB_Default, FontID(hFont))
        hText = TextGadget(#PB_Any, 70, 25, width - 80, height - 70, message)
        SetGadgetColor(hText, #PB_Gadget_BackColor, $FFFFFF)
      EndIf
    CloseGadgetList()
    SetActiveGadget(hButton)

    StickyWindow(hWnd, #True)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Escape, 11)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Return, 12)

    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_Gadget, #PB_Event_CloseWindow
          endLoop = #True

        Case #PB_Event_Menu
          Select EventMenu()
            Case 11, 12 ; Escape or return key
              endLoop = #True
          EndSelect
      EndSelect
    Until endLoop = #True
  EndIf
  RemoveKeyboardShortcut(hWnd, #PB_Shortcut_All)

  If exit : End : EndIf

EndProcedure


message = #SQ + "Our app name" + #SQ + #DCRLF + " was started without parameters!" + #DCRLF +
          "Command line parameters" + #CRLF$ +
          "=======================" + #CRLF$ +
          "Required:" + #CRLF$ +
          "/file=''     | File that contains all items to rename" + #CRLF$ +
          "/editor=''   | Full path to the editor to start" + #DCRLF +
          "Optional:" + #CRLF$ +
          "/timeout=<x> | When to quit automatically [Default: 60]" + #CRLF$ +
          "/flags=<x>   | Binary combined value      [Default: 0]" + #CRLF$ +
          "               1 = Use a preview window" + #CRLF$ +
          "               2 = Allow only one instance" + #DCRLF +
          "This process will exit now!"
MessageBox("App name", message)

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 3:31 am
by TI-994A
oO0XX0Oo wrote:We are supposed to have only one! Repeat .. Until loop regardless of how many windows we are using.
That's purely a recommended guideline, because while a separate event loop is running, all other processes from the main event loop would be suspended; for example, a timer. Otherwise, it's perfectly valid and safe.
oO0XX0Oo wrote:...will I run into problems as well because I'm using #PB_Any for all of my gadgets all of my gadgets here, knowing that a larger application with it's own window will get there gadget ids via an enumeration?
Dynamic object assignments via the #PB_Any directive is the safest way as they are assigned by the compiler. However, it doesn't preclude the risk of clashing if manual object assignments deliberately use large numbers; which is highly discouraged, and disallowed by the debugger.
oO0XX0Oo wrote:I could integrate it into a main loop ... but I'd like to put this thing into a module to allow easier integration
You might consider implementing a custom callback for the message box, like so:

Code: Select all

#SQ = Chr(39)
#DCRLF = #CRLF$ + #CRLF$

Procedure MessageBoxHandler()
  Select Event()
      
    Case #PB_Event_CloseWindow
      CloseWindow(EventWindow())
      
    Case #PB_Event_Gadget
      CloseWindow(EventWindow())
     
    Case #PB_Event_Menu
      Select EventMenu()          
        Case 11, 12 ; Escape or return key
          CloseWindow(EventWindow())
      EndSelect
      
  EndSelect
EndProcedure

; Possible icons:
; #IDI_ERROR, #IDI_EXCLAMATION, #IDI_QUESTION, #IDI_INFORMATION
Procedure MessageBox(title.s="", message.s="", width.i=0, height.i=0, icon.i=#IDI_ERROR, exit.b=#True)
  Protected.i minwidth = 460, minHeight = 325
  Protected.i hWnd, hButton, hContainer, hFont, hText
  Protected.b endLoop = #False
  
  ; Make sure we reach at least the minimum width & height
  If width < minwidth : width = minwidth : EndIf
  If height < minHeight : height = minHeight : EndIf
  
  hWnd = OpenWindow(#PB_Any, 0, 0, width, height, title, #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  If hWnd
    hButton = ButtonGadget(#PB_Any, width - 95, height - 35, 85, 25, "OK")
    
    hContainer = ContainerGadget(#PB_Any, 0, 0, width, height - 45)
    SetGadgetColor(hContainer, #PB_Gadget_BackColor, $FFFFFF)
    ImageGadget(#PB_Any, 25, 25, 20, 20, LoadIcon_(0, icon))
    
    hFont = LoadFont(#PB_Any, "Consolas", 8)
    If hFont
      SetGadgetFont(#PB_Default, FontID(hFont))
      hText = TextGadget(#PB_Any, 70, 25, width - 80, height - 70, message)
      SetGadgetColor(hText, #PB_Gadget_BackColor, $FFFFFF)
    EndIf
    CloseGadgetList()
    SetActiveGadget(hButton)
    
    StickyWindow(hWnd, #True)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Escape, 11)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Return, 12)
    
    BindGadgetEvent(hButton, @MessageBoxHandler())
    BindEvent(#PB_Event_Menu, @MessageBoxHandler(), hWnd)
    BindEvent(#PB_Event_CloseWindow, @MessageBoxHandler(), hWnd)
  EndIf
  
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
mainWindow = OpenWindow(#PB_Any, 0, 0, 300, 200, "Custom Message Box", wFlags)
clockDisplay = TextGadget(#PB_Any, 0, 40, 300, 50, "Clock Display", #PB_Text_Center)
showMessage = ButtonGadget(#PB_Any, 50, 100, 200, 50, "Show Message Box")
AddWindowTimer(mainWindow, clock, 1000)

Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Select EventWindow()
        Case mainWindow
          appQuit = 1          
      EndSelect
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case showMessage          
          message.s = #SQ + "Our app name" + #SQ + #DCRLF + " was started without parameters!" + #DCRLF +
                      "Command line parameters" + #CRLF$ +
                      "=======================" + #CRLF$ +
                      "Required:" + #CRLF$ +
                      "/file=''     | File that contains all items to rename" + #CRLF$ +
                      "/editor=''   | Full path to the editor to start" + #DCRLF +
                      "Optional:" + #CRLF$ +
                      "/timeout=<x> | When to quit automatically [Default: 60]" + #CRLF$ +
                      "/flags=<x>   | Binary combined value      [Default: 0]" + #CRLF$ +
                      "               1 = Use a preview window" + #CRLF$ +
                      "               2 = Allow only one instance" + #DCRLF +
                      "This process will exit now!"          
          MessageBox("App name", message)          
          
      EndSelect
      
    Case #PB_Event_Timer
      Select EventTimer()
        Case clock
          SetGadgetText(clockDisplay, FormatDate("%hh:%ii:%ss", Date()))
      EndSelect  

  EndSelect
  
Until appQuit
Continue clicking on the Show Message Box button in the main window and multiple message boxes will pop up. Notice that they will all be handled by the same event handler independently, without suspending the main event loop.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 7:07 am
by infratec
Sorry, was to late.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 8:07 am
by oO0XX0Oo
That's purely a recommended guideline, because while a separate event loop is running, all other processes from the main event loop would be suspended; for example, a timer. Otherwise, it's perfectly valid and safe.
Ok, I'll use these kind of message boxes as a "hey user, I need your attention now! (and in 99% of the cases the app is going to terminate after showing the notice)" and it's perfectly fine if the app is suspended from doing any other loop while this window is displayed.
Dynamic object assignments via the #PB_Any directive is the safest way as they are assigned by the compiler. However, it doesn't preclude the risk of clashing if manual object assignments deliberately use large numbers; which is highly discouraged, and disallowed by the debugger.
This won't be a problem, the enumerations for windows, gadgets, etc. start with 0 and end all in the low range < 100...

Thanks for the code example!

But doing it that way (of using non-blocking message boxes) couldn't I run into problems when all of them assigning the same event number to the keyboard shortcuts?

Code: Select all

    AddKeyboardShortcut(hWnd, #PB_Shortcut_Escape, 11)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Return, 12)

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 8:21 am
by Josh
@oO0XX0Oo

Could it be that you are the first victim of PedroMartin's pattern codes? :mrgreen:
  • Two event loops
  • StickyWindow()
StickyWindow() is definitely wrong for a messagebox. I think you have to put a ParentWindow on OpenWindow. Sorry, currently no PB available to test it.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 12:59 pm
by TI-994A
oO0XX0Oo wrote:...couldn't I run into problems when all of them assigning the same event number to the keyboard shortcuts?

Code: Select all

    AddKeyboardShortcut(hWnd, #PB_Shortcut_Escape, 11)
    AddKeyboardShortcut(hWnd, #PB_Shortcut_Return, 12)
No, there'll be no problems. The keyboard shortcuts are window-specific. The AddKeyboardShortcut() function indicates the window number in the first parameter, and each time the message box is called, it is assigned a different window number.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 2:30 pm
by IdeasVacuum
StickyWindow() is definitely wrong for a messagebox
It's not a standard MessageBox, it's a custom defined Window and StickyWindow() is intended for exactly this purpose!

Concerning the Message Window having it's own event loop, at the moment in time when it's presented to the User, it has sole focus. In that circumstance it's perfectly fine for the secondary event loop to be the 'King'.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 3:12 pm
by Josh
IdeasVacuum wrote:It's not a standard MessageBox, it's a custom defined Window and StickyWindow() is intended for exactly this purpose!
Definitely NO. oO0XX0Oo was asking for a MessageBox.
  • A MessageBox blocks the application because it expects a user's decision.
  • The window of a MessageBox is displayed above the windows of the own application and not over all applications. Other applications are not interested in this message.
Maybe it will be different in Linux and MacOs (I don't know), but in Windows is a modal window for a MessageBox standard.

Re: MessageBox() - How to make it safe?

Posted: Tue Apr 17, 2018 3:44 pm
by oO0XX0Oo
It isn't a problem, another parameter "ontop.b=#False" is enough to solve that situation...