Numeric Entry into a EditBox

Share your advanced PureBasic knowledge/code with the community.
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Numeric Entry into a EditBox

Post by RNBW »

I have been trawling through the forum for examples of numeric input into a textbox. What I wanted was the following:
Check each character as it is entered to ensure that:

• The character was limited to 0 to 9, minus and decimal point (period)
• Minus can only occur as the first character entered
• There can only be one occurrence of minus
• There can only be one occurrence of a decimal point
• If the first character entered is a decimal point (.123) it will be displayed as 0.123
• If the first character is minus and the second a decimal point (-.123) it will be displayed as -0.123
• The display will be right justified
• It must be editable

I couldn’t find code that did all of these, but was able to find two that came close. I have amalgamated the two. Credit is given in the code to the writers of the original code. I am very grateful to them and I hope that the code below will be useful. I shall be grateful if anyone can improve it or put me right if I have got anything wrong.

Code: Select all

;--------------------------------------------------------------------------------------------
;  Numeric input
;---------------------------------------------------------------------------------------------
;  This is an amalgamation of two programs.  One from Danilo
;  included in RSBasic's WinAPI Library (StringGadget section)
;  and BasicallyPure's program in the Purebasic forum 
;  (https://www.purebasic.fr/english/viewtopic.php?f=13&t=50565&hilit=Numeric+entry&start=0)
;  It accepts characters 0 to 9 minus and period.
;  It accepts a minus only as the first character.
;  It only accepts one minus sign and one period in the entry.
;  An entry of .123 displays as 0.123
;  An entry of -.123 is displayed as -0.123
;  The amalgamation of code has been produced by RNBW, who is grateful
;  to all the contributors to the code:
;  Danilo
;  BasicallyPure
;  asked for by 'akee'
;  inspired by 'IdeasVacuum'
;  key assist from 'Shardik'
;---------------------------------------------------------------------------------------------

EnableExplicit

Procedure.s CheckNumeric(iStrGadget.i)
   ;purpose: limit StringGadget to accept only numeric input
   ;with support for floating poing characters: . - + E e
   
   Protected.i iCount, iTextLength, iAccept, iDecimal, iExp, iNeg
   Protected.s sNewText, sChar, sGadgetText
   
   sGadgetText = GetGadgetText(iStrGadget)
   iTextLength = Len(sGadgetText)
   
   For iCount = 1 To iTextLength
     
      sChar = Mid(sGadgetText, iCount, 1)
      iAccept = #False
     
      Select Asc(sChar)
         Case 48 To 57 ; Accepted numbers
            iAccept = #True
            If iExp = 1 : iExp + 1 : EndIf
         Case 43, 45        ; Minus or Plus sign
            If iCount = 1
               iAccept = #True : iNeg = 1
            ElseIf iExp = 1
               iAccept = #True : iExp + 1
            EndIf
         Case 46        ; period
            If Not iDecimal And Not iExp
               iDecimal = #True : iAccept = #True
               If iCount - iNeg = 1 : sChar = "0" + sChar : EndIf
            EndIf
         Case 69, 101 ;    'e' or 'E'
            If Not iExp And iCount > 1 + iNeg
               iExp = 1 : iAccept = #True
            EndIf
      EndSelect
     
      If iAccept : sNewText + sChar : EndIf
     
   Next
   
   SetGadgetText(iStrGadget, sNewText)
   iTextLength = Len(sNewText)
   
   ;windows only
   SendMessage_(GadgetID(iStrGadget),#EM_SETSEL,iTextLength,iTextLength) ;Set cursor to end of string

EndProcedure

If OpenWindow(0,0,0,300,150,"Number of Decimal Places",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  StringGadget(1,10,10,200,20,"",#PB_Text_Right)
  LoadFont (1, "Monaco", 11, #PB_Font_Bold)
  SetGadgetFont(1, FontID(1))
  
  Repeat
    Define EventID=WaitWindowEvent()
    If EventID=#PB_Event_Gadget
      Select EventGadget()
        Case 1
          If EventType() = #PB_EventType_Change
            CheckNumeric(EventGadget())
          EndIf
      EndSelect
    EndIf
    If EventID = #PB_Event_CloseWindow
      End
    EndIf
  ForEver
EndIf
User avatar
idle
Always Here
Always Here
Posts: 5042
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Numeric Entry into a EditBox

Post by idle »

seems to work well, but not "e" exp, needs a bit more thought
as you can type 123.456e1234
the exponent needs to be limited in the range, for float +/- 38 or double +/- 308
so if you have "1." entered you can flag it as a potential of exp and if "e" is entered then range check the numbers after to be
within +/- 38 for float or +/- 308 for double
not sure how to do that right now but will look at it in the morning
Windows 11, Manjaro, Raspberry Pi OS
Image
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

idle wrote:seems to work well, but not "e" exp, needs a bit more thought
as you can type 123.456e1234
the exponent needs to be limited in the range, for float +/- 38 or double +/- 308
so if you have "1." entered you can flag it as a potential of exp and if "e" is entered then range check the numbers after to be
within +/- 38 for float or +/- 308 for double
not sure how to do that right now but will look at it in the morning
You are right that the incorporation of the entry of numbers such as 123.456e1234 (or more importantly 123.456e-1234) needs more thought. With what I had in mind, I wouldn't require such numbers to actually be entered.

One of the problems is that the code as written only allows for a minus sign to be the first character entered and only one minus sign is permitted. The e (or E) shouldn't be too much of a problem to change, but you couldn't enter 123e-12 or -123e-12, the first because of the rule I've just mentioned and the latter with a double no-no because it has a minus in the middle of the entry and also two minus signs.

These problems have to be overcome and it's not easy. I must say, if I was going to need to enter a 123e-12 type number, I would probably use a completely different method, such as entering the whole number and then carrying out a check on its validity, or alternatively enter a number without the e-12 part and identify that with a label after the textbox and deal with the number itself in code outside the entry. Personnally, I would favour the latter method. It's much easier to deal with.

I currently don't have time to look into it, but if anyone could come up with solutions I would like to see them.

Personnally, my next step is to produce a grid of textboxes with numeric entry into them. If anyone has any ideas, it would save my poor old brain a few headaches!
User avatar
captain_skank
Enthusiast
Enthusiast
Posts: 636
Joined: Fri Oct 06, 2006 3:57 pm
Location: England

Re: Numeric Entry into a EditBox

Post by captain_skank »

RNBW wrote:
idle wrote: Personnally, my next step is to produce a grid of textboxes with numeric entry into them. If anyone has any ideas, it would save my poor old brain a few headaches!
I use a scroll areagadget and an list (array) of stringgadgets for my entrirley PB based grid gadget - but it's a bit of a bodge :)
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

I must be going daft (ask my wife she'll probably confirm that - she's being saying it for years). The code DOES allow the input of numbers such as 123e24 and -123e-24. I found I was checking it out using old code, which didn't allow it because I had stripped out the incorporation of 'e'. I decided to include it before I posted the code.
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

captain_skank wrote:
RNBW wrote:
idle wrote: Personnally, my next step is to produce a grid of textboxes with numeric entry into them. If anyone has any ideas, it would save my poor old brain a few headaches!
I use a scroll areagadget and an list (array) of stringgadgets for my entrirley PB based grid gadget - but it's a bit of a bodge :)
Is your grid gadget on the forum?
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

Code: Select all

;windows only
   SendMessage_(GadgetID(iStrGadget),#EM_SETSEL,iTextLength,iTextLength) ;Set cursor to end of string
Is there an equivalent Mac OS equivalent to #EM_SETSEL, or is there another way of achieving the same result?
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

Unfortunately, the code does not work if you try to edit the entered number if a single character precedes the decimal point, such as 1.234 or 0.234

I've tried to resolve this, but unsuccessfully. Can anyone help?
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Numeric Entry into a EditBox

Post by Shardik »

RNBW wrote:

Code: Select all

;windows only
   SendMessage_(GadgetID(iStrGadget),#EM_SETSEL,iTextLength,iTextLength) ;Set cursor to end of string
Is there an equivalent Mac OS equivalent to #EM_SETSEL, or is there another way of achieving the same result?
You may take a look into this cross-platform example which allows to get and set the cursor position in a StringGadget. To position the cursor at the end you should change

Code: Select all

SetCursorPosition(0, 16) ; Set cursor in front of 'fox'
to

Code: Select all

SetCursorPosition(0, Len(GetGadgetText(0)))
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Numeric Entry into a EditBox

Post by Dude »

RNBW wrote:The character was limited to 0 to 9, minus and decimal point (period)
You can't hard-check for a decimal point (period) as different countries use different decimal separators - some use a comma, for example. So you need to get the locale setting for the decimal separator at runtime, and check for that in your text input routine (not for a period).
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

Dude wrote:
RNBW wrote:The character was limited to 0 to 9, minus and decimal point (period)
You can't hard-check for a decimal point (period) as different countries use different decimal separators - some use a comma, for example. So you need to get the locale setting for the decimal separator at runtime, and check for that in your text input routine (not for a period).
What I have in mind to ultimately use the code for will be British only, so a check for a decimal point is OK. However, I do take your point that for a universal solution a check should also be made for a comma. I should say, though, that this can lead to all sorts of formatting arguments and solutions (how thousands are divided and so on), so the code will be quite long and complex and, I would suggest, not for the simple numeric entry into an EditBox that I am looking at.

Some of the separation of thousands 1,234,567 1 234 567 1'234'567 and that's not taking into account how the decimal point is dealt with.

Of course, there are also the arguments about the types of numbers that should be entered, and there are many of these. Maybe I should have headed the thread "Simple Numeric Entry into an EditBox".
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

Shardik wrote: You may take a look into this cross-platform example which allows to get and set the cursor position in a StringGadget. To position the cursor at the end you should change

Code: Select all

SetCursorPosition(0, 16) ; Set cursor in front of 'fox'
to

Code: Select all

SetCursorPosition(0, Len(GetGadgetText(0)))
Thank you. I'll have a look at the code.
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

RNBW wrote:Unfortunately, the code does not work if you try to edit the entered number if a single character precedes the decimal point, such as 1.234 or 0.234

I've tried to resolve this, but unsuccessfully. Can anyone help?
The only way that I can see to get over this is to remove or REM the line

Code: Select all

 If iCount - iNeg = 1 : sChar = "0" + sChar : EndIf
The user can then enter 0.123 -0.123 .123 or -.123 depending on preference. It is then possible to format the number after input. I'll have a look at this.

Shardick
I must say I like the solution you gave for setting the cursor position. Your page
is very useful.
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

Time flies. I meant to look at this straight away after my last post.

At the moment, there are two problems that I have with the code that I originally posted.

1. Whenever you edit the number, the cursor then flies to after the last digit, and
2. If you try to edit a single number before the decimal point, it can only be done by entering a character before the first character and then deleting the character after the inserted character.

I suspect that (2) might be resolved by a solution to (1).

Does anyone know how you can insert a character and the cursor remain at the inserted character, rather than flying to after the last digit?
RNBW
User
User
Posts: 65
Joined: Thu Jan 02, 2014 5:01 pm

Re: Numeric Entry into a EditBox

Post by RNBW »

RNBW wrote:Time flies. I meant to look at this straight away after my last post.

At the moment, there are two problems that I have with the code that I originally posted.

1. Whenever you edit the number, the cursor then flies to after the last digit, and
2. If you try to edit a single number before the decimal point, it can only be done by entering a character before the first character and then deleting the character after the inserted character.

I suspect that (2) might be resolved by a solution to (1).

Does anyone know how you can insert a character and the cursor remain at the inserted character, rather than flying to after the last digit?
Well 75 views at my last post, but no suggestions how to get over the problem. I was hoping that someone would come up with perhaps a sub-classing solution (which has been done on other forums). I have to admit not being able to fully get my head around sub-classing. I have tried but unsuccessfully.

So not to be detered, I looked for a completely different solution. I had previously scoured the Purebasic forums (English and German) and looked back at some of the solutions I found. I came up with the code below, which I modified a bit. It gives me what I want in respect of editing, i.e. leaving the cursor at the point of edit rather than jumping to the end each time. I would be pleased to see any suggestions for improvements.

Code: Select all

;-------------------------------------------------------------------------
;  Filename:  NumericInput_04.pb
;
;  From original code by Danilo and ts-soft
;  http://www.purebasic.fr/german/viewtopic.php?p=299222#p299222
;
;  Modifications by RNBW 22 October 2018
;
;  Input numeric values in the range "-", ".", "0 to 9"
;  into a StringGadget
;  The number is entered into the first box and this is then
;  displayed in the second box as a number formatted to
;  3 decimal places.  The number of decimal places can be
;  changed by changing the value of dec in the program.
;  If you want thousand commas in the displayed number,
;  comment out "formNum = StrD(numb, dec)" and 
;  remove the comment to 
;  "formNum = FormatNumber(numb,dec)"
;-------------------------------------------------------------------------

EnableExplicit

Global formNum.s
Global numb.d
Global dec.i

Procedure checkFloatInput(gadget)
    Protected start.i, count.i, pointcount.i, new.s
    SendMessage_(GadgetID(gadget), #EM_GETSEL, @start, 0)
    Protected txt$ = GetGadgetText(gadget)
    Protected *p.Character = @txt$
   
    While *p\c ; <> 0
        If *p\c = '.'
            pointcount + 1
            If pointcount < 2
                new + Chr(*p\c)
            Else
                If start > count : start - 1 : EndIf
            EndIf
        ElseIf count = 0 And *p\c = '-'
           new + Chr('-')
        ElseIf *p\c >= '0' And *p\c <= '9'
            new + Chr(*p\c)
        Else
            start - 1
        EndIf
        *p + SizeOf(Character)
        count + 1
    Wend
    SetGadgetText(gadget, new)
    SendMessage_(GadgetID(gadget), #EM_SETSEL, start, start)
EndProcedure

Define event
If OpenWindow(0, 0, 0, 300, 150, "Numeric Input Into A StringGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
   
   If LoadFont(1, "Arial", 10)
      SetGadgetFont(#PB_Default, FontID(1))   ; Set the loaded Arial 10 font as new standard
   EndIf
   TextGadget(0, 10, 20, 200, 20, "Enter Number in box below")
   TextGadget(3, 10, 85, 200, 20, "Display formatted number")
   
   If LoadFont(2,"Arial",11, #PB_Font_Bold)
      SetGadgetFont(#PB_Default, FontID(2))   ; Set the loaded Arial 15 font as new standard
   EndIf
   StringGadget(1, 10,  50, 100, 25, "", #ES_RIGHT)
   StringGadget(2, 10, 110, 100, 25, "", #ES_RIGHT | #ES_READONLY)  ; this gadget is read-only
   
    SetActiveGadget(1)      ; set focus in StringGadget(1)

    Repeat
        event = WaitWindowEvent()
        If event = #PB_Event_CloseWindow : End
        ElseIf event = #PB_Event_Gadget
            ;If EventGadget()  = 1 Or EventGadget() = 2  ; this line is not actually required
                If EventType() = #PB_EventType_Change
                   checkFloatInput(EventGadget())
                   numb = ValD(GetGadgetText(1))
                   dec = 3    ; set number of decimal places
                   
                   formNum = StrD(numb, dec)
                   ;formNum = FormatNumber(numb,dec)   ; this would place commas in the displayed number
                   SetGadgetText(2, formNum)
                EndIf
            ;EndIf
        EndIf
    ForEver
EndIf
Post Reply