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

GUI form validation at the point of string gadget input

Post by PBJim »

Hi,

I'm looking for a solution for validating string gadgets at the point of input, if that's possible.

It is not always appropriate to validate a screenful of user inputs just prior to saving a record or processing data.

In legacy systems, which were usually driven through keyboard input, the user would press Enter after inputting a field value and the software would validate it at that point, disallowing further input progress until corrected. I know that is not possible in the same way with a GUI form, because (a) Enter isn't normally used and (b) there are other forms of 'navigation-away' from the input, such as clicking on another field.

I read an article about C# Windows forms validation, which gives the below synopsis. Is there a mechanism in PureBasic that allows for a similar concept?
Once the user has entered a value to the control and moves the focus from the control, then the Validating event is fired. Here, I evaluate the input value according to a correct value based on the data type selected by the property DataType.
https://www.c-sharpcorner.com/UploadFil ... plication/
This is a generalised question, but to the extent that sample code is helpful to serve as the basis for the requirement, I provide the below. Let's say for example that we need to validate that the group is four characters in length and the cost is between zero and 999.99. How might that be achieved?

I'd like to know how to do this using PureBasic functions — not to have to move to some other product.

Code: Select all

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, 30, 100, 160, 25, "Cost")

pcode.i  = StringGadget(#PB_Any, 200, 25, 140, 25, "")
group.i  = StringGadget(#PB_Any, 200, 60, 100, 25, "")
cost.i   = StringGadget(#PB_Any, 200, 95, 100, 25, "")

SetActiveGadget(pcode.i)

Repeat
  event = WaitWindowEvent()
  ; Debug "Event = " + event

  Select event
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget
      ; Debug "EventGadget() = " + EventGadget()
      
      Select EventGadget()
          
      EndSelect
  EndSelect
  
ForEver

CloseWindow(win.i)

End
User avatar
jacdelad
Addict
Addict
Posts: 2030
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

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

Post by jacdelad »

I am not sure whether I do understand your question or not. To me it seems like you want to react to #PB_EventType_Chabhnge, read the StringGadget, validate it (via regex or not) and react in it (like changing the text colour to red or green)?
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
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

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, 30, 100, 160, 25, "Cost")

pcode.i  = StringGadget(#PB_Any, 200, 25, 140, 25, "")
group.i  = StringGadget(#PB_Any, 200, 60, 100, 25, "")
cost.i   = StringGadget(#PB_Any, 200, 95, 100, 25, "")

SetActiveGadget(pcode.i)

Repeat
  event = WaitWindowEvent()
  ; Debug "Event = " + event
  
  Select event
    Case #PB_Event_CloseWindow
      Break
      
    Case #PB_Event_Gadget
      ; Debug "EventGadget() = " + EventGadget()
      
      Select EventType() 
        Case #PB_EventType_Focus
        Case #PB_EventType_Change
          If GetGadgetText(EventGadget()) <> ""
            Debug "change"
          EndIf
        Case #PB_EventType_LostFocus
          If GetGadgetText(EventGadget()) <> ""
            Debug "save?"
          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: Sat Oct 26, 2024 8:31 pm

Code: Select all

      Select EventType() 
        Case #PB_EventType_Focus
        Case #PB_EventType_Change
  
Thanks, I happened to see #PB_EventType_Focus while searching, after posting the code. I wrote the below attempt to try it.

Validation works, but the problem is that if a user's input fails the validation, he must first correct it, just to be able to exit the application with the exit button. This is because the validation code keeps executing, taking precedence over the button.

Incidentally, I need to validate when the field loses focus, not when the value changes. The purpose here is to validate the user's input before he moves on to another field.

To see the problem, enter 1000 into cost and click Exit button. As you'll see, I have to keep the user's input captive, by calling SetActiveGadget(), since otherwise it renders the validation nothing more than a warning.

Code: Select all

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

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

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

SetActiveGadget(pcode.i)

Repeat
  event = WaitWindowEvent()
  EventType = EventType() 
  
  
  If EventType = #PB_EventType_LostFocus
    
    Select EventGadget()
      Case group.i
        groupval.s = GetGadgetText(group.i)
        If groupval.s <> "" And Len(groupval.s) <> 4
          MessageRequester("Error", "Group must be four digits")
          SetActiveGadget(group.i)
        EndIf
        
      Case cost.i
        value.d = ValD(GetGadgetText(cost.i))
        If value.d < 0 Or value.d > 999.99
          MessageRequester("Error", "Cost must be positive and no more than 999.99")
          SetActiveGadget(cost.i)
        EndIf
        
    EndSelect

  EndIf
  
  Select event
      
    Case #PB_Event_CloseWindow
      Break
      
    Case #PB_Event_Gadget
      
      Select EventGadget()
          
          ; **
          ; **  Exit button
          ; **
        Case exitbutton.i
          Break
          
      EndSelect
  EndSelect
  
ForEver

CloseWindow(win.i)

End
User avatar
TI-994A
Addict
Addict
Posts: 2749
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

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

Post by TI-994A »

PBJim wrote: Sat Oct 26, 2024 9:18 pm...if a user's input fails the validation, he must first correct it, just to be able to exit the application with the exit button.
This appears to occur on Windows and Linux, but not on macOS, though.

However, the validation (on lost focus) works well with the exit button here:

Code: Select all

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

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

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

SetActiveGadget(pcode)

Repeat
  event = WaitWindowEvent()
  
  Select event
      
    Case #PB_Event_CloseWindow
      appQuit = #True
      
    Case #PB_Event_Gadget      
      Select EventGadget()
          
        Case pcode
          ;do something...
          
        Case group
          If EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> exitbutton
            groupval.s = Trim(GetGadgetText(group))
            If groupval = "" Or Len(groupval) <> 4
              MessageRequester("Error", "Group must be four digits")
              SetActiveGadget(group)
            EndIf
          EndIf
          
        Case cost
          If EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> exitbutton
            value.d = ValD(GetGadgetText(cost))
            If value < 0 Or value > 999.99
              MessageRequester("Error", "Cost must be positive and no more than 999.99")
              SetActiveGadget(cost)              
            EndIf            
          EndIf
          
        Case exitbutton
          appQuit = #True                   
          
      EndSelect
  EndSelect
  
Until appQuit
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
mk-soft
Always Here
Always Here
Posts: 6315
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

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

Post by mk-soft »

Check at exit ...

Code: Select all

;-TOP by mk-soft, v1.02.2, 12.11.2022

Enumeration 1
  #IsInteger
  #IsFloat
EndEnumeration

Procedure IsNumeric(Value.s)
  Protected r1, *value.character, signed, num, float, exponent, exponent2
  
  *value = @Value
  If *value
    Repeat
      If *value\c = '+' Or *value\c = '-'
        If signed
          num = #False
          Break
        EndIf
        signed = #True
      ElseIf *value\c >= '0' And *value\c <= '9'
        num = #True
        Break
      ElseIf *value\c <> ' '
        Break
      EndIf
      *value + SizeOf(character)
    ForEver
    If num
      Repeat
        *value + SizeOf(character)
        If *value\c = 0
          Break
        EndIf
        If *value\c = '.'
          float = 1
          Break
        ElseIf *value\c = 'e' Or *value\c = 'E'
          exponent = 1
          Break
        ElseIf *value\c < '0' Or *value\c > '9'
          Break
        EndIf
      ForEver
      
      If float
        Repeat
          *value + SizeOf(character)
          If *value\c >= '0' And *value\c <= '9'
            float + 1
          ElseIf *value\c = 'e' Or *value\c = 'E'
            exponent = 1
            Break
          Else
            Break
          EndIf
        ForEver
      EndIf
      If exponent
        Repeat
          *value + SizeOf(character)
          If *value\c >= '0' And *value\c <= '9'
            exponent + 1
          ElseIf *value\c = '+' Or *value\c = '-'
            If exponent >= 2
              Break
            ElseIf exponent2
              num = #False
              Break
            EndIf
            exponent2 = #True
          Else
            Break
          EndIf
        ForEver
      EndIf
      If num
        If float And float < 2
          r1 = 0
        ElseIf exponent And exponent < 2
          r1 = 0
        Else
          If float
            r1 = #IsFloat
          Else 
            r1 = #IsInteger
          EndIf
        EndIf  
      EndIf
    EndIf
  EndIf
  ProcedureReturn r1
EndProcedure

; ****

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

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

Global pcode.i  = StringGadget(#PB_Any, 140, 25, 140, 25, "")
Global group.i  = StringGadget(#PB_Any, 140, 60, 100, 25, "")
Global cost.i   = StringGadget(#PB_Any, 140, 95, 100, 25, "")
Global buttonOk.i = ButtonGadget(#PB_Any, 30, 160, 100, 25, "Ok")
Global buttonCancel.i = ButtonGadget(#PB_Any, 130, 160, 100, 25, "Cancel")
SetActiveGadget(pcode.i)

Procedure Validation()
   groupval.s = GetGadgetText(group.i)
  If Len(groupval.s) <> 4 Or IsNumeric(GetGadgetText(group)) <> #IsInteger
    MessageRequester("Error", "Group must be four digits")
    SetActiveGadget(group.i)
    ProcedureReturn #False
  EndIf
  
  value.d = ValD(GetGadgetText(cost.i))
  If IsNumeric(GetGadgetText(cost)) = 0 Or value.d < 0 Or value.d > 999.99
    MessageRequester("Error", "Cost must be positive and no more than 999.99")
    SetActiveGadget(cost.i)a
    ProcedureReturn #False
  EndIf
  
  ProcedureReturn #True
  
EndProcedure

Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Break
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case buttonOk
          If Validation()
            Break
          EndIf
          
        Case buttonCancel
          Break
          
      EndSelect
      
  EndSelect
  
ForEver

CloseWindow(win.i)

End
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
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 »

Thanks to both for these examples, they're both very helpful for different reasons.

TI-994A, that's right, you've got the idea behind what's needed. I see that you've simplified my code and eliminated the separate 'lost focus' block of code, which is much better than mine. I actually tried an exit button check similar to yours, i.e. the exit button not having been clicked, but it didn't work in my case.

mk-soft, yes I can already validate at the end of form in that way, but the objective here is to validate at field level, as in the article about C# and windows forms. Nevertheless, interesting how you've implemented it there.

However, despite the working example, there does appear to be a problem when another Windows application gains focus. I had this problem last night with my version, and it's also seen in the TI-994A version, but less easy to reproduce. What happens is that if I open another application, such as Notepad, while the error message is displayed and then go back to the PB application, it enters an infinite loop, presumbly because it's constantly losing focus.

I will try to reliably reproduce this and report back shortly, because at the moment I can't seem to make it happen every time. :? Thanks again.
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 can reproduce the infinite message problem that I saw in my own version and which also occurs in TI-994A's version. I believe it might be happening because opening another application triggers a loss of focus and it probably doesn't need to. There are two ways to reproduce it. This is on Windows, incidentally.

Code: Select all

1. Execute the code with F5
2. Press TAB to move to the group input
3. Enter 12
4. Press TAB
5. Error dialogue box is displayed
6. Click OK
7. Use Windows Run and execute NOTEPAD (note that the error re-appears, as the input field has lost focus)
8. Move the Notepad window aside, away from the PB application
9. Click OK to the error dialogue
10. Dialogue repeatedly appears and it is necessary to kill the application
The other way to reproduce this is more or less the same :

Code: Select all

As per steps 1 to 5 above, i.e. do those steps in the same way.
6. Use Windows Run and execute NOTEPAD
7. Move the Notepad window aside, away from the PB application
8. Click OK to the error dialogue
9. Error dialogue box disappears
10. Click on the Notepad window
11. Error dialogue box is displayed and if you click OK, re-appears indefinitely
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 »

PBJim wrote: Sun Oct 27, 2024 11:35 am I can reproduce the infinite message problem that I saw in my own version and which also occurs in TI-994A's version. I believe it might be happening because opening another application triggers a loss of focus and it probably doesn't need to. There are two ways to reproduce it. This is on Windows, incidentally.

Code: Select all

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

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

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

SetActiveGadget(pcode)

Define change
Repeat
  event = WaitWindowEvent()
  
  Select event
      
    Case #PB_Event_CloseWindow
      appQuit = #True
      
    Case #PB_Event_Gadget      
      If EventType() = #PB_EventType_Change
        change = 1
      EndIf
        Select EventGadget()
          
        Case pcode
          ;do something...
          
        Case group
          If change And EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> exitbutton
            groupval.s = Trim(GetGadgetText(group))
            If groupval = "" Or Len(groupval) <> 4
               change = 0
              MessageRequester("Error", "Group must be four digits")
              SetActiveGadget(group)
            EndIf
          EndIf
          
        Case cost
          If change And EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> exitbutton
            value.d = ValD(GetGadgetText(cost))
            If value < 0 Or value > 999.99
              change = 0
              MessageRequester("Error", "Cost must be positive and no more than 999.99")
              SetActiveGadget(cost)              
            EndIf            
          EndIf
          
        Case exitbutton
          appQuit = #True                   
          
      EndSelect
  EndSelect
  
Until appQuit
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: Mon Oct 28, 2024 5:04 pm

Code: Select all

      If EventType() = #PB_EventType_Change
        change = 1
      EndIf
Yes, that's one way of doing it — don't prevent the user from moving to another input field. It might be a good compromise, so perhaps I'll go with that option.

There is a small bug, but it can be easily resolved by moving the sequence of the Exit button :

If you go to the Cost input and enter 1000, then press Tab, it will not say that the value should be 999.99 or lower, because it has already reached the Exit button gadget.

Hence, this test then fails :

Code: Select all

If change And EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> exitbutton
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

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

Post by breeze4me »

If you want the message box to always appear, you can remove all the parts about the "skip" variable.

Code: Select all

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

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

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

SetActiveGadget(pcode.i)

Repeat
  event = WaitWindowEvent()
  ; Debug "Event = " + event

  Select event
    Case #PB_Event_ActivateWindow
      skip = 0
      
    Case #PB_Event_DeactivateWindow
      skip = 1
      
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget
      ; Debug "EventGadget() = " + EventGadget()
      
      et = EventType()
      eg = EventGadget()
      
      If et = #PB_EventType_LostFocus
        
        Select eg
          Case group
            text.s = Trim(GetGadgetText(eg))
            If Len(text) <> 4
              If skip = 0
                MessageRequester("Error", "Group must be four digits")
                SetActiveGadget(eg)
              EndIf
              
              ; Reset the field to its default value.
              SetGadgetText(eg, "")
              
              ; All events should be removed.
              While WindowEvent() : Wend
            EndIf
            
          Case cost
            text.s = Trim(GetGadgetText(eg))
            fcost.f = ValF(text)
            If text = "" Or fcost < 0 Or fcost > 999.99
              If skip = 0
                MessageRequester("Error", "Cost must be positive and no more than 999.99")
                SetActiveGadget(eg)
              EndIf
              
              ; Reset the field to its default value.
              SetGadgetText(eg, "")
              
              ; All events should be removed.
              While WindowEvent() : Wend
            EndIf
            
        EndSelect
        
      EndIf
      
  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 »

breeze4me wrote: Mon Oct 28, 2024 6:05 pm If you want the message box to always appear, you can remove all the parts about the "skip" variable.
Thanks, there are a lot of ways of doing this and I didn't think for one minute that it was going to be easy to get it just right :D

There is probably no perfect solution with a GUI because it's difficult to gain precise control.

It would be easy to validate on reaching end of screen, but financial entry screens tend to be more tightly validated at each step. Just looking at other systems, such as below, it needed to validate the customer's code before proceeding further and displaying the customer's name. It then validated the purchase order number isn't a duplicate. That's the sort of control we need to provide. I'm open to other ideas though — maybe there are better ways with the PB GUI.

Image
User avatar
ChrisR
Addict
Addict
Posts: 1484
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

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

Post by ChrisR »

A bit similar to breeze4me's code, with flush event (remove event)

Code: Select all

EnableExplicit

Global win, pcode, group, cost, buttonOk, buttonCancel 

EnumerationBinary
  #IsDigit
  #IsSign
  #IsPoint
  #IsExp
EndEnumeration

Macro _ProcedureReturnIf(Cond, ReturnValue = #False)
  If Cond
    ProcedureReturn ReturnValue
  EndIf
EndMacro

Procedure IsNumeric(String.s, AcceptedFlag = #IsDigit | #IsSign | #IsPoint | #IsExp) 
  Structure CharacterArray
    StructureUnion
      c.c[0]
    EndStructureUnion
  EndStructure
  
  Protected *String.CharacterArray=@String
  Protected Count, Length = Len(String.s)
  Protected IsNumFlag = #IsDigit
  
  Select *String\c[0]
    Case '+', '-'
      _ProcedureReturnIf(AcceptedFlag & #IsSign <> #IsSign)
      IsNumFlag | #IsSign
      Count + 1
    Case '.'
      _ProcedureReturnIf(AcceptedFlag & #IsPoint <> #IsPoint)
      IsNumFlag | #IsPoint
      Count + 1
  EndSelect
  _ProcedureReturnIf(Length = Count)
  
  Repeat
    Select *String\c[Count]
      Case '0' To '9'
        Count + 1
        Continue
      Case '.'
        _ProcedureReturnIf(AcceptedFlag & #IsPoint <> #IsPoint)
        _ProcedureReturnIf(IsNumFlag & #IsPoint)
        IsNumFlag | #IsPoint
        Count + 1
        Continue
      Case 'e', 'E'
        _ProcedureReturnIf(AcceptedFlag & #IsExp <> #IsExp)
        _ProcedureReturnIf(IsNumFlag & #IsExp)
        Count + 1
        Select *String\c[Count]
          Case '+', '-'
            Count + 1
            Select *String\c[Count]
              Case '0' To '9'
                IsNumFlag | #IsExp
                Count + 1
              Default
                ProcedureReturn #False
            EndSelect
          Case '0' To '9'
            IsNumFlag | #IsExp
            Count + 1
          Default
            ProcedureReturn #False
        EndSelect
        Continue
      Default
        ProcedureReturn #False
    EndSelect
  Until Length = Count
  
  ProcedureReturn Bool(IsNumFlag)
EndProcedure

Procedure DispatchEvent(EventID)
  Select EventID
    Case 0
      ProcedureReturn #False
      ;Case xxx : ProcessEventxxx
    Default
      ProcedureReturn #True
  EndSelect
EndProcedure

Procedure FlushEvents()
  While DispatchEvent(WindowEvent())
  Wend
EndProcedure

Procedure Checkgroup()
  Protected groupval.s = GetGadgetText(group)
  If Len(groupval.s) <> 4 Or Not IsNumeric(GetGadgetText(group), #IsDigit)
    MessageRequester("Error", "Group must be four digits")
    SetActiveGadget(group)
    FlushEvents()
    ProcedureReturn #False
  EndIf
  ProcedureReturn #True
EndProcedure

Procedure Checkcost()
  Protected value.d = ValD(GetGadgetText(cost))
  If Not IsNumeric(GetGadgetText(cost), #IsDigit | #IsPoint) Or value < 0 Or value > 999.99
    MessageRequester("Error", "Cost must be positive and no more than 999.99")
    SetActiveGadget(cost)
    FlushEvents()
    ProcedureReturn #False
  EndIf
  ProcedureReturn #True
EndProcedure

Define event
win = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 475, 210, "Test", #PB_Window_MinimizeGadget | #PB_Window_SystemMenu)

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

pcode  = StringGadget(#PB_Any, 140, 25, 140, 25, "")
group  = StringGadget(#PB_Any, 140, 60, 100, 25, "")
cost   = StringGadget(#PB_Any, 140, 95, 100, 25, "")
buttonOk = ButtonGadget(#PB_Any, 30, 160, 100, 25, "Ok")
buttonCancel = ButtonGadget(#PB_Any, 140, 160, 100, 25, "Cancel")
SetActiveGadget(pcode)

Repeat
  event = WaitWindowEvent()
  If GetActiveWindow() = win
    Select event
      Case #PB_Event_CloseWindow
        Break
        
      Case #PB_Event_Gadget
        Select EventGadget()
          Case group
            If EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> buttonCancel
              Checkgroup()
            EndIf
            
          Case cost
            If EventType() = #PB_EventType_LostFocus And GetActiveGadget() <> buttonCancel
              Checkcost()            
            EndIf
            
          Case buttonOk
            If Checkgroup() And Checkcost()
              Break
            EndIf
            
          Case buttonCancel
            Break
            
        EndSelect
    EndSelect
  EndIf
ForEver

CloseWindow(win)
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 »

ChrisR wrote: Mon Oct 28, 2024 7:53 pm A bit similar to breeze4me's code, with flush event (remove event)
Thanks ChrisR, that does indeed work well. Despite giving it a real bashing, I was unable to find any bug or weakness at all.

I wondered about the below commented-out line — was it intended to trap certain events?

Code: Select all

;Case xxx : ProcessEventxxx
I'm mindful that I'll need to trap certain things, such as at certain points of input, I'll need to trigger another window, that kind of thing.

Jim
User avatar
ChrisR
Addict
Addict
Posts: 1484
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

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

Post by ChrisR »

PBJim wrote: Mon Oct 28, 2024 10:00 pm I wondered about the below commented-out line — was it intended to trap certain events?
Yes, it's a copy and paste from one of my codes, just if you need to manage a specific event.
For the case here, "While WindowEvent() : Wend" as done by breeze4me is enough.

For Windows only, you can also add before FlushEvents(), to select the string value

Code: Select all

PostMessage_(GadgetID(group), #EM_SETSEL, 0, -1)
Post Reply