GUI form validation at the point of string gadget input

Just starting out? Need help? Post your questions and find answers here.
PBJim
Enthusiast
Enthusiast
Posts: 296
Joined: Fri Jan 19, 2024 11:56 pm

Re: GUI form validation at the point of string gadget input

Post by PBJim »

I'm experimenting with methods of input validation, continuing from the earlier suggestions in this thread. I'm attempting to keep the solution as simple as possible.

Our screens need to validate at the points of field input, most likely as a warning only.

The below code can cause an infinite loop, due to #PB_EventType_LostFocus repeatedly being triggered.

For example :

1. Type ABC as the group and then click the cost input (or press TAB)
The application shows the group warning
2. Cancel the warning

3. Type 1000 as the cost and then click the group input (or press SHIFT-TAB)
The application shows the cost warning
4. Cancel the warning

Group warnings then continue indefinitely. I found that I can prevent this by discarding the events, as shown in the commented-out lines.

I'm trying to understand why discarding the events should be necessary, given that group has already lost focus, so why does it continuously lose focus? Is there a cleaner method? It doesn't seem like an unusual requirement, so I wonder if there are better ways. Thanks.

Code: Select all

;-**
;-**  ===============================================================================================================
;-**  Field validation warnings, at the point of input, or as hard error on saving record
;-**  ===============================================================================================================
;-**

Procedure.i Validate_Group(validstr.s, title.s)
  
  Define valid.i = #True
  
  If validstr.s <> "" And Len(validstr.s) <> 4
    MessageRequester(title, "Group code must be four characters", #PB_MessageRequester_Error)
    ; While WindowEvent() : Wend
    valid.i = #False
  EndIf
  
  ProcedureReturn valid.i
  
EndProcedure

Procedure.i Validate_Cost(validstr.s, title.s)
  
  Define valid.i = #True
  Define validdbl.d
  
  validdbl.d = ValD(validstr.s)
  If validdbl.d < 0 Or validdbl.d > 999.99
    MessageRequester(title, "Cost must be positive and not more than 999.99", #PB_MessageRequester_Error)
    ; While WindowEvent() : Wend
    valid.i = #False
  EndIf
  
  ProcedureReturn valid.i
  
EndProcedure



win.i = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 475, 210, "Test", #PB_Window_MinimizeGadget | #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

TextGadget(#PB_Any, 30, 30, 160, 25, "Item ref.")
TextGadget(#PB_Any, 30, 65, 160, 25, "Group")
TextGadget(#PB_Any, 340, 65, 160, 25, "(4 characters)")
TextGadget(#PB_Any, 30, 100, 160, 25, "Cost")
TextGadget(#PB_Any, 340, 100, 160, 25, "(0 - 999.99)")

pcode.i  = StringGadget(#PB_Any, 150, 25, 140, 25, "")
group.i  = StringGadget(#PB_Any, 150, 60, 100, 25, "")
cost.i   = StringGadget(#PB_Any, 150, 95, 100, 25, "")
savebutton.i = ButtonGadget(#PB_Any, 30, 160, 100, 25, "Save")
exitbutton.i = ButtonGadget(#PB_Any, 150, 160, 100, 25, "Exit")

SetActiveGadget(pcode.i)

Repeat
  event = WaitWindowEvent()
  eventtype = EventType()
  
  Select event
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget                                               ; Gadget event
      Select EventGadget()
          
        Case savebutton                                                 ; Save
          If Validate_Group(GetGadgetText(group), "ERROR")              ; Validate group before saving record
            If Validate_Cost(GetGadgetText(cost), "ERROR")              ; ... and validate cost
              ; **
              ; ** Save record here
              ; **
              SetGadgetText(pcode, "")
              SetGadgetText(group, "")
              SetGadgetText(cost, "")
            EndIf
          EndIf
          
        Case exitbutton                                                 ; Exit application
          Break
          
        Case group
          If eventtype = #PB_EventType_LostFocus                        ; On lost focus of group input, validate (warning only)
            Validate_Group(GetGadgetText(group), "Warning")
          EndIf
          
        Case cost
          If EventType = #PB_EventType_LostFocus                        ; On lost focus of cost input, validate (warning only)
            Validate_Cost(GetGadgetText(cost), "Warning")
          EndIf
          
      EndSelect
  EndSelect
  
ForEver

CloseWindow(win.i)
End
mestnyi
Addict
Addict
Posts: 1102
Joined: Mon Nov 25, 2013 6:41 am

Re: GUI form validation at the point of string gadget input

Post by mestnyi »

everything is fine on my system. There is no endless loop.
PBJim
Enthusiast
Enthusiast
Posts: 296
Joined: Fri Jan 19, 2024 11:56 pm

Re: GUI form validation at the point of string gadget input

Post by PBJim »

mestnyi wrote: Wed Dec 18, 2024 9:30 am everything is fine on my system. There is no endless loop.
Thanks for trying it, mestnyi. It is necessary to carry out my precise steps 1 - 4 as set out, otherwise it doesn't fail. I've also had a colleague try it on another machine, and another Windows version. All repeat the same problem.

Can anyone else try and confirm this please? Same problem with PB 6.00 Windows 64-bit and PB 6.20 Beta.

You have to enter an invalid group AND invalid cost, then press SHIFT-TAB at the cost, to go back to the group.
mestnyi
Addict
Addict
Posts: 1102
Joined: Mon Nov 25, 2013 6:41 am

Re: GUI form validation at the point of string gadget input

Post by mestnyi »

here is an example showing the problem. :)

Code: Select all

win.i = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 475, 210, "Test", #PB_Window_MinimizeGadget | #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

group.i  = StringGadget(#PB_Any, 150, 60, 100, 25, "ABC")
cost.i   = StringGadget(#PB_Any, 150, 95, 100, 25, "1000")

SetActiveGadget(group.i)
SetActiveGadget(cost.i)

Repeat
  event = WaitWindowEvent()
  eventtype = EventType()
  
  Select event
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget                                               ; Gadget event
      Select EventGadget()
        Case group
          If eventtype = #PB_EventType_LostFocus                        ; On lost focus of group input, validate (warning only)
            MessageRequester("Warning", "Group code must be four characters", #PB_MessageRequester_Error)
          EndIf
          
        Case cost
          If EventType = #PB_EventType_LostFocus                        ; On lost focus of cost input, validate (warning only)
            MessageRequester("Warning", "Cost must be positive And Not more than 999.99", #PB_MessageRequester_Error)
          EndIf
          
      EndSelect
  EndSelect
  
ForEver

CloseWindow(win.i)
End
this is because message takes focus away from the window by itself and from the last active gadget, but does not return it. this is a bug in windows.
mestnyi
Addict
Addict
Posts: 1102
Joined: Mon Nov 25, 2013 6:41 am

Re: GUI form validation at the point of string gadget input

Post by mestnyi »

Code: Select all

Global Active =- 1

Procedure CallbackHandler(hWnd, uMsg, wParam, lParam) 
   Protected gadget = GetProp_( hWnd, "PB_ID" )
   Protected sysProc = GetProp_(hWnd, "sysProc")
   
   Select uMsg
      Case #WM_NCDESTROY 
         SetWindowLongPtr_(hwnd, #GWLP_USERDATA, sysProc)
         RemoveProp_(hwnd, "sysProc")
         
      Case #WM_SETFOCUS
         Debug gadget
         
      Case #WM_KILLFOCUS
         If GetFocus_( )
            Debug "  "+gadget
            SetFocus_(0)
         Else
            Active = gadget
            Debug "    "+gadget
            ProcedureReturn 0
         EndIf
         
   EndSelect
   
   ProcedureReturn CallWindowProc_(sysProc, hWnd, uMsg, wParam, lParam)
EndProcedure 

Procedure BindGadget( gadget )
   Protected hWnd = GadgetID( gadget )
   Protected sysProc = SetWindowLongPtr_(hWnd, #GWL_WNDPROC, @CallbackHandler())
   SetProp_(hWnd, "sysProc", sysProc)
EndProcedure

Global group.i,cost.i


win.i = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 475, 210, "Test", #PB_Window_MinimizeGadget | #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

StringGadget(1, 150, 60, 100, 25, "ABC")
StringGadget(2, 150, 95, 100, 25, "1000")

group.i  = 1
cost.i   = 2

BindGadget( group )
BindGadget( cost )

SetActiveGadget(group.i)
SetActiveGadget(cost.i)

Repeat
   event = WaitWindowEvent()
   eventtype = EventType()
   
   Select event
      Case #PB_Event_CloseWindow
         Break
         
      Case #PB_Event_Gadget                                               ; Gadget event
         Select EventGadget()
            Case group
               If eventtype = #PB_EventType_LostFocus                        ; On lost focus of group input, validate (warning only)
                  MessageRequester("Warning", "Group code must be four characters", #PB_MessageRequester_Error)
                  SetActiveGadget( Active )
               EndIf
               
            Case cost
               If EventType = #PB_EventType_LostFocus                        ; On lost focus of cost input, validate (warning only)
                  MessageRequester("Warning", "Cost must be positive And Not more than 999.99", #PB_MessageRequester_Error)
                  SetActiveGadget( Active )
               EndIf
               
         EndSelect
   EndSelect
   
ForEver

CloseWindow(win.i)
End
PBJim
Enthusiast
Enthusiast
Posts: 296
Joined: Fri Jan 19, 2024 11:56 pm

Re: GUI form validation at the point of string gadget input

Post by PBJim »

mestnyi wrote: Wed Dec 18, 2024 6:22 pm here is an example showing the problem. :)

[...]

this is because message takes focus away from the window by itself and from the last active gadget, but does not return it. this is a bug in windows.
I can't say I fully understand what you say mestnyi, because it's the user who has taken focus away, by moving to another input field. Would it return to the field?

I was able to resolve it with the commented-out code shown in the original example :

Code: Select all

While WindowEvent() : Wend
This must be placed just after the message dialogue box (it does not work if placed before). It would seem therefore, that there are unwanted window events queued as a result of the dialogue box, which we need to clear afterwards. I wish this wasn't necessary.

If this is a Windows bug, as you suggest, shouldn't there be a workaround within PB itself, so we don't need to do this?
mestnyi
Addict
Addict
Posts: 1102
Joined: Mon Nov 25, 2013 6:41 am

Re: GUI form validation at the point of string gadget input

Post by mestnyi »

mestnyi wrote: Wed Dec 18, 2024 6:22 pm here is an example showing the problem. :)
this is because message takes focus away from the window by itself and from the last active gadget, but does not return it. this is a bug in windows.
I found a solution.

Code: Select all

win.i = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 475, 210, "Test", #PB_Window_MinimizeGadget | #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

group.i  = StringGadget(#PB_Any, 150, 60, 100, 25, "ABC")
cost.i   = StringGadget(#PB_Any, 150, 95, 100, 25, "1000")

SetActiveGadget(group.i)
SetActiveGadget(cost.i)

Repeat
  event = WaitWindowEvent()
  eventtype = EventType()
  
  Select event
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget                                               ; Gadget event
      Select EventGadget()
         Case group
            If eventtype = #PB_EventType_LostFocus                        ; On lost focus of group input, validate (warning only)
               ;If GadgetID(EventGadget()) <> GetFocus_( )
               If EventGadget( ) <> GetActiveGadget( )
                  MessageRequester("Warning", "Group code must be four characters", #PB_MessageRequester_Error)
               EndIf
            EndIf
            
         Case cost
            If EventType = #PB_EventType_LostFocus                        ; On lost focus of cost input, validate (warning only)
               ; If GadgetID(EventGadget()) <> GetFocus_( )
               If EventGadget( ) <> GetActiveGadget( )
                  MessageRequester("Warning", "Cost must be positive And Not more than 999.99", #PB_MessageRequester_Error)
               EndIf
            EndIf
          
      EndSelect
  EndSelect
  
ForEver

CloseWindow(win.i)
End
Post Reply