The best way for TEdit Number validation by type number

Published:
Hello everybody

This Article will show you how to validate number with TEdit control,

What's the TEdit control?

TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text.

Usually, programmer use OnKeyPress event to validate number input (Integer for example) like the following example

 
procedure TForm1.Edit12KeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in [#8, '0'..'9']) then Key := #0;
end;

Open in new window


What problems aren't covered by OnKeyPress event?

OnKeyPress event analyzes keyboard inputs, but doesn't work with Copy/Paste or Ctrl + V keyboard shortcut, for that i found that, OnChange event is better to catch any kinds of inputs.

My idea basing on Undo and ClearUndo procedures of TEdit Control.

What's Undo and ClearUndo procedure?

Undo and ClearUndo procedure used by components belong to TCustomEdit class (such TEdit, TMaskedit, TMemo, TRichEdit...), we call Undo procedure to cancel all changes made to the TCustomEdit text since the last call to the ClearUndo procedure, and we call ClearUndo to commit changes to TCustomEdit text, after the excecuting of ClearUndo procedure,  Undo procedure does nothing until the TCustomEdit text has.a new changes.

The method I will use with OnChange event work like the following statement:

if TEdit input change is valid value then ClearUndo else Undo

Validate Number by type

The following example show you how to validate an Integer.
 
procedure TForm1.Edit1Change(Sender: TObject);
var Nbr: Integer;
begin
if TEdit(Sender).Text = '-' then Exit;
if TEdit(Sender).Text = '' then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Integer)) and (Nbr <= High(Integer)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

Open in new window


Explanation

1) To allow input of "-" sign we will add this line (Integer is signed number)
if TEdit(Sender).Text = "-" then Exit;

2) When we clear TEdit we need to put "0"
TEdit(Sender).Text := '0';

3) When value of TEdit is null we need to make the control ready to clear '0' when user type another number
TEdit(Sender).SelectAll;

4) To check if the input is valid integer
if not TryStrToInt(TEdit(Sender).Text, Nbr)

5) To check if the integer value is Int32 (-2147483648..2147483647)
if (Nbr >= Low(Integer)) and (Nbr <= High(Integer)) then

Validate char input

In this part I will use the event to validate input string, the following example will check if input char allowed or not.for example (allowed chars are 'M','A','H','D','I','7','8')
 
procedure TForm1.Edit12Change(Sender: TObject);
Const AllowedStr = 'MAHDI78';
var I: Integer;
begin
for I := 1 to Length(TEdit(Sender).Text) do
  begin
  if (Pos(TEdit(Sender).Text[I], AllowedStr) = 0) then
    begin
    TEdit(Sender).Undo;
    Exit;
    end;
  end;
TEdit(Sender).ClearUndo;
end;

Open in new window

   

Validate format

In this part the event will validate input format, the following example will check format ('ss-nssn-nn') the input value should be for example "AE-5DR8-34".
   
procedure TForm1.Edit13Change(Sender: TObject);
Const AllowedFormat = 'ss-nssn-nn'; // for Example AE-5DR8-34
var I : Integer;
  P: PChar;
begin
if Length(TEdit(Sender).Text) > Length(AllowedFormat) then
  begin
  TEdit(Sender).Undo;
  Exit;
  end;

  P := PChar(TEdit(Sender).Text);
  I := 1;
  while P^ <> #0 do
  begin
  if AllowedFormat[I] = 's' then
     begin
     if not (P^ in ['A'..'Z']) then
       begin
       TEdit(Sender).Undo;
       Beep; Exit;
       end;
     end else
         if AllowedFormat[I] = 'n' then
          begin
          if not (P^ in ['0'..'9']) then
             begin
             TEdit(Sender).Undo;
             Beep; Exit;
             end;
          end else if AllowedFormat[I] <> Char(P^) then
                   begin
                   TEdit(Sender).Undo;
                   Beep; Exit;
                   end;
    Inc(P);
    Inc(I);
  end;
TEdit(Sender).ClearUndo;
end;

Open in new window


For more examples with (Smallint, ShortInt, Int64, Currency...) check the following sample

PAS unit
 
unit Unit1;

/// Posted by Mahdi78 member in Experts-Exchage.com
/// Email mahdi_kamel@yahoo.fr

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Edit5: TEdit;
    Edit6: TEdit;
    Edit7: TEdit;
    Edit8: TEdit;
    Label9: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Edit9: TEdit;
    Label17: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    Label20: TLabel;
    Edit10: TEdit;
    Label21: TLabel;
    Edit11: TEdit;
    Label22: TLabel;
    Edit12: TEdit;
    Label23: TLabel;
    Label24: TLabel;
    Edit13: TEdit;
    Label25: TLabel;
    Label26: TLabel;
    procedure Edit2Change(Sender: TObject);
    procedure Edit3Change(Sender: TObject);
    procedure Edit4Change(Sender: TObject);
    procedure Edit5Change(Sender: TObject);
    procedure Edit6Change(Sender: TObject);
    procedure Edit7Change(Sender: TObject);
    procedure Edit8Change(Sender: TObject);
    procedure Edit9Change(Sender: TObject);
    procedure Edit10Change(Sender: TObject);
    procedure Edit11Change(Sender: TObject);
    procedure Edit1Change(Sender: TObject);
    procedure Edit12Change(Sender: TObject);
    procedure Edit13Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses math;

{$R *.dfm}

procedure TForm1.Edit10Change(Sender: TObject);
var Nbr: Extended;
begin
if TEdit(Sender).Text = '-' then Exit;
if (TEdit(Sender).Text = '') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToFloat(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= -2.9 * Power(10, -39) ) and (Nbr <= 1.7 * Power(10, 38)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit11Change(Sender: TObject);
var Nbr: Currency;
begin
if TEdit(Sender).Text = '-' then Exit;
if (TEdit(Sender).Text = '') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToCurr(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= -922337203685477.5808) and (Nbr <= 922337203685477.5807) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit12Change(Sender: TObject);
Const AllowedStr = 'MAHDI78';
var I: Integer;
begin
for I := 1 to Length(TEdit(Sender).Text) do
  begin
  if (Pos(TEdit(Sender).Text[I], AllowedStr) = 0) then
    begin
    TEdit(Sender).Undo;
    Exit;
    end;
  end;
TEdit(Sender).ClearUndo;
end;

procedure TForm1.Edit13Change(Sender: TObject);
Const AllowedFormat = 'ss-nssn-nn'; // for Example AE-5DR8-34
var I : Integer;
  P: PChar;
begin
if Length(TEdit(Sender).Text) > Length(AllowedFormat) then
  begin
  TEdit(Sender).Undo;
  Exit;
  end;

  P := PChar(TEdit(Sender).Text);
  I := 1;
  while P^ <> #0 do
  begin
  if AllowedFormat[I] = 's' then
     begin
     if not (P^ in ['A'..'Z']) then
       begin
       TEdit(Sender).Undo;
       Beep; Exit;
       end;
     end else
         if AllowedFormat[I] = 'n' then
          begin
          if not (P^ in ['0'..'9']) then
             begin
             TEdit(Sender).Undo;
             Beep; Exit;
             end;
          end else if AllowedFormat[I] <> Char(P^) then
                   begin
                   TEdit(Sender).Undo;
                   Beep; Exit;
                   end;
    Inc(P);
    Inc(I);
  end;
TEdit(Sender).ClearUndo;
end;

procedure TForm1.Edit1Change(Sender: TObject);
var Nbr: Integer;
begin
if TEdit(Sender).Text = '-' then Exit;
if TEdit(Sender).Text = '' then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Integer)) and (Nbr <= High(Integer)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit2Change(Sender: TObject);
var Nbr: Integer;
begin
if (TEdit(Sender).Text = '') or (TEdit(Sender).Text = '-') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Byte)) and (Nbr <= High(Byte)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit3Change(Sender: TObject);
var Nbr: Integer;
begin
if TEdit(Sender).Text = '-' then Exit;
if TEdit(Sender).Text = '' then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Shortint)) and (Nbr <= High(Shortint)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit4Change(Sender: TObject);
var Nbr: Integer;
begin
if TEdit(Sender).Text = '-' then Exit;
if TEdit(Sender).Text = '' then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Smallint)) and (Nbr <= High(Smallint)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit5Change(Sender: TObject);
var Nbr: Integer;
begin
if (TEdit(Sender).Text = '') or (TEdit(Sender).Text = '-') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Word)) and (Nbr <= High(Word)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit6Change(Sender: TObject);
var Nbr: Int64;
begin
if (TEdit(Sender).Text = '') or (TEdit(Sender).Text = '-') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt64(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Cardinal)) and (Nbr <= High(Cardinal)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit7Change(Sender: TObject);
var Nbr: Int64;
begin
if TEdit(Sender).Text = '-' then Exit;
if TEdit(Sender).Text = '' then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt64(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Int64)) and (Nbr <= High(Int64)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit8Change(Sender: TObject);
var Nbr: Int64;
begin
if (TEdit(Sender).Text = '') or (TEdit(Sender).Text = '-') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToInt64(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= Low(Longword)) and (Nbr <= High(Longword)) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

procedure TForm1.Edit9Change(Sender: TObject);
var Nbr: Extended;
begin
if (TEdit(Sender).Text = '') then
  begin
  TEdit(Sender).Text := '0';
  TEdit(Sender).SelectAll;
  end
else if not TryStrToFloat(TEdit(Sender).Text, Nbr) then TEdit(Sender).Undo
  else if (Nbr >= 0 ) and (Nbr <= (1.7 * Power(2, 64)) - 1) then
          TEdit(Sender).ClearUndo else TEdit(Sender).Undo;
end;

end.

Open in new window

 

DFM unit
   
object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 441
  ClientWidth = 804
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 33
    Top = 32
    Width = 36
    Height = 13
    Caption = 'Integer'
  end
  object Label2: TLabel
    Left = 33
    Top = 60
    Width = 22
    Height = 13
    Caption = 'Byte'
  end
  object Label3: TLabel
    Left = 33
    Top = 86
    Width = 38
    Height = 13
    Caption = 'Shortint'
  end
  object Label4: TLabel
    Left = 33
    Top = 114
    Width = 36
    Height = 13
    Caption = 'Smallint'
  end
  object Label5: TLabel
    Left = 33
    Top = 141
    Width = 26
    Height = 13
    Caption = 'Word'
  end
  object Label6: TLabel
    Left = 33
    Top = 168
    Width = 39
    Height = 13
    Caption = 'Cardinal'
  end
  object Label7: TLabel
    Left = 33
    Top = 195
    Width = 26
    Height = 13
    Caption = 'Int64'
  end
  object Label8: TLabel
    Left = 33
    Top = 221
    Width = 47
    Height = 13
    Caption = 'Longword'
  end
  object Label9: TLabel
    Left = 522
    Top = 32
    Width = 132
    Height = 13
    Caption = '-2147483648..2147483647'
  end
  object Label10: TLabel
    Left = 522
    Top = 59
    Width = 32
    Height = 13
    Caption = '0..255'
  end
  object Label11: TLabel
    Left = 522
    Top = 86
    Width = 48
    Height = 13
    Caption = '-128..127'
  end
  object Label12: TLabel
    Left = 522
    Top = 114
    Width = 72
    Height = 13
    Caption = '-32768..32767'
  end
  object Label13: TLabel
    Left = 522
    Top = 140
    Width = 44
    Height = 13
    Caption = '0..65535'
  end
  object Label14: TLabel
    Left = 522
    Top = 169
    Width = 74
    Height = 13
    Caption = '0..4294967295'
  end
  object Label15: TLabel
    Left = 522
    Top = 198
    Width = 74
    Height = 13
    Caption = '-2^63..2^63-1'
  end
  object Label16: TLabel
    Left = 522
    Top = 221
    Width = 74
    Height = 13
    Caption = '0..4294967295'
  end
  object Label17: TLabel
    Left = 33
    Top = 248
    Width = 33
    Height = 13
    Caption = 'UInt64'
  end
  object Label18: TLabel
    Left = 522
    Top = 248
    Width = 52
    Height = 13
    Caption = '0..2^64'#8211'1'
  end
  object Label19: TLabel
    Left = 33
    Top = 280
    Width = 33
    Height = 13
    Caption = 'Real48'
  end
  object Label20: TLabel
    Left = 522
    Top = 280
    Width = 150
    Height = 13
    Caption = '-2.9 x 10^'#8211'39 .. 1.7 x 10^38  '
  end
  object Label21: TLabel
    Left = 32
    Top = 312
    Width = 44
    Height = 13
    Caption = 'Currency'
  end
  object Label22: TLabel
    Left = 522
    Top = 312
    Width = 251
    Height = 13
    Caption = '-922337203685477.5808.. 922337203685477.5807'
  end
  object Label23: TLabel
    Left = 537
    Top = 352
    Width = 162
    Height = 13
    Caption = 'AllowedStr = '#39'M'#39','#39'A'#39','#39'H'#39','#39'D'#39','#39'I'#39','#39'7'#39','#39'8'#39
  end
  object Label24: TLabel
    Left = 32
    Top = 352
    Width = 23
    Height = 13
    Caption = 'Char'
  end
  object Label25: TLabel
    Left = 34
    Top = 398
    Width = 34
    Height = 13
    Caption = 'Format'
  end
  object Label26: TLabel
    Left = 537
    Top = 398
    Width = 177
    Height = 13
    Caption = 'ss-nssn-nn  for example AE-5DR8-34'
  end
  object Edit1: TEdit
    Left = 112
    Top = 29
    Width = 400
    Height = 21
    TabOrder = 0
    Text = '0'
    OnChange = Edit1Change
  end
  object Edit2: TEdit
    Left = 112
    Top = 56
    Width = 400
    Height = 21
    TabOrder = 1
    Text = '0'
    OnChange = Edit2Change
  end
  object Edit3: TEdit
    Left = 112
    Top = 83
    Width = 400
    Height = 21
    TabOrder = 2
    Text = '0'
    OnChange = Edit3Change
  end
  object Edit4: TEdit
    Left = 112
    Top = 110
    Width = 400
    Height = 21
    TabOrder = 3
    Text = '0'
    OnChange = Edit4Change
  end
  object Edit5: TEdit
    Left = 112
    Top = 137
    Width = 400
    Height = 21
    TabOrder = 4
    Text = '0'
    OnChange = Edit5Change
  end
  object Edit6: TEdit
    Left = 112
    Top = 164
    Width = 400
    Height = 21
    TabOrder = 5
    Text = '0'
    OnChange = Edit6Change
  end
  object Edit7: TEdit
    Left = 112
    Top = 191
    Width = 400
    Height = 21
    TabOrder = 6
    Text = '0'
    OnChange = Edit7Change
  end
  object Edit8: TEdit
    Left = 112
    Top = 218
    Width = 400
    Height = 21
    TabOrder = 7
    Text = '0'
    OnChange = Edit8Change
  end
  object Edit9: TEdit
    Left = 112
    Top = 245
    Width = 400
    Height = 21
    TabOrder = 8
    Text = '0'
    OnChange = Edit9Change
  end
  object Edit10: TEdit
    Left = 112
    Top = 277
    Width = 400
    Height = 21
    TabOrder = 9
    Text = '0'
    OnChange = Edit10Change
  end
  object Edit11: TEdit
    Left = 112
    Top = 309
    Width = 400
    Height = 21
    TabOrder = 10
    Text = '0'
    OnChange = Edit11Change
  end
  object Edit12: TEdit
    Left = 112
    Top = 352
    Width = 400
    Height = 21
    TabOrder = 11
    OnChange = Edit12Change
  end
  object Edit13: TEdit
    Left = 112
    Top = 395
    Width = 400
    Height = 21
    TabOrder = 12
    OnChange = Edit13Change
  end
end

Open in new window

2
14,427 Views

Comments (9)

aikimarkSocial distance; Wear a mask; Don't touch your face; Wash your hands for 20 seconds
CERTIFIED EXPERT
Top Expert 2014

Commented:
>>...i think it work like the following example
It doesn't.  I found this Trying article very helpful to explain what is going on
http://www.prototypical.co.uk/pdf/trying.pdf

They showed the two Sysutils functions.
function StrToInt(const S: string): Integer;
var
  E: Integer;
begin
  Val(S, Result, E);
  if E <> 0 then
    ConvertErrorFmt(@SInvalidInteger, [S]);
end;

function TryStrToInt(const S: string; out Value: Integer): Boolean;
var
  E: Integer;
begin
  Val(S, Value, E);
  Result := E = 0;
end

Open in new window


When you use any of the Try conversion functions, you do not have to include a range check, since it is implicit by the successful conversion.
Mark WillsTopic Advisor, Page Editor
CERTIFIED EXPERT
Distinguished Expert 2018

Commented:
I think the "Try" prefix on those functions reflects the boolean nature of those functions and not how they work internally. So, you can use in a similar way as the try/except block by encapsulating within an If/then/else block.

So, instead of (plagiarising from the article link above) :
try
            ResultLabel.Caption := IntToStr(StrToInt(StrEdit.Text));
except on E : EConvertError do
            ResultLabel.Caption := StrEdit.Text + ‘ is not a valid integer’;
end;

Open in new window

you can now do with the "Try..." functions :
if TryStrToInt(StrEdit.Text,TempInt) then begin
            ResultLabel.Caption := IntToStr(TempInt);
end else begin
            ResultLabel.Caption := StrEdit.Text + ‘ is not a valid integer’;
end;

Open in new window


But that is a bit of a moot point in some regards, and I think the Article is a good reminder of where we should actually check content changes, and that would be on the OnChange event so it does capture every user change instance...

The only "gotcha" with that event is when you want to force a value programmatically that might contradict what a user can do (regardless of how they do it). On those rare occassions I have sometimes had to introduce a "ForceChange" type flag to override the normal OnChange processing (bit yucky, but effective all the same).

All in all a good article and thanks for sharing. Voted Yes.

Cheers,
Mark Wills

Author

Commented:
@mark_wills
You're welcome and thank you for participation,

>> The only "gotcha" with that event is when you want to force a value programmatically ...

I agree with you, but in my code force change couldn't work, and couldn't pass through TryStrToInt function and Undo procedure,

Look this scenario:

1- User made changes, if the changes is true TEdit will keep it by ClearUndo procedure by clearing previous undo
2- TEdit will never accept invalid force changes and it will clear them by Undo procedure and replace them with last true change which kept by ClearUndo procedure, else if some of force changes is true, will keep it by ClearUndo
aikimarkSocial distance; Wear a mask; Don't touch your face; Wash your hands for 20 seconds
CERTIFIED EXPERT
Top Expert 2014

Commented:
Where is the cursor after the undo?

Author

Commented:
And even after the ClearUndo, the cursor tun into ghost :D
It's enough to add the following code in the end of event

SetCursorPos(Mouse.CursorPos.X, Mouse.CursorPos.Y);

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.

Get access with a 7-day free trial.
You Belong in the World's Smartest IT Community