Checking KeyPress is not the place to do your input validation
Posted by jpluimers on 2017/09/19
I have seen too many projects over the years trying to do input validation by checking KeyPress. This is not limited to Delphi projects (C#, VB and other projects suffer from this just as much). Most of these projects suffer from these::
- Much of the KeyPress logic logic in the UI byusing half-baked copy-pasted code fragments.
- They all fail missing important functionality (like paste, clear, Ctrl-key handling and such) either supporting or suppressing that functionality where needed
If doing Delphi, then that code should be rewritten in a generic way based on examples like like these:
- http://www.devsuperpage.com/search/Articles.asp?ArtID=857874 [WayBack]
- https://groups.google.com/forum/#!search/TNumberEdit$20Peter$20Below
–jeroen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit ValidatableEdits; | |
| interface | |
| uses | |
| Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, | |
| StdCtrls; | |
| type | |
| TCharSet = Set of Char; | |
| TBaseValidatableEdit = class(Tedit) | |
| private | |
| { Private declarations } | |
| FLastContent: String; | |
| FLastPosition: Integer; | |
| FAllowedChars: TCharset; | |
| Procedure SetAllowedChars; virtual; abstract; | |
| protected | |
| { Protected declarations } | |
| Procedure SaveState; | |
| Procedure RestoreState; | |
| Function IsValidChar( ch: Char ): Boolean; virtual; | |
| Function Validate: Boolean; virtual; abstract; | |
| Procedure WndProc( Var msg: TMessage ); override; | |
| property AllowedChars: TCharset read FAllowedChars write FAllowedChars; | |
| property LastContent: String read FLastContent; | |
| property LastPosition: Integer read FLastPosition; | |
| public | |
| { Public declarations } | |
| Constructor Create( aOwner: TComponent ); override; | |
| end; | |
| TIntegerEdit = Class( TBaseValidatableEdit ) | |
| private | |
| Procedure SetAllowedChars; override; | |
| function GetAllowNegative: Boolean; | |
| procedure SetAllowNegative(const Value: Boolean); | |
| protected | |
| Function Validate: Boolean; override; | |
| published | |
| { Published declarations } | |
| property AllowNegative: Boolean | |
| read GetAllowNegative write SetAllowNegative default True; | |
| end; | |
| TFloatEdit = Class( TIntegeredit ) | |
| private | |
| Procedure SetAllowedChars; override; | |
| protected | |
| Function Validate: Boolean; override; | |
| end; | |
| procedure Register; | |
| implementation | |
| procedure Register; | |
| begin | |
| RegisterComponents('PBGoodies', [TIntegerEdit, TFloatEdit]); | |
| end; | |
| { TBaseValidatableEdit } | |
| constructor TBaseValidatableEdit.Create(aOwner: TComponent); | |
| begin | |
| inherited; | |
| ControlStyle := Controlstyle – [csSetCaption]; | |
| SetAllowedChars; | |
| end; | |
| function TBaseValidatableEdit.IsValidChar(ch: Char): Boolean; | |
| begin | |
| Result := ch In FAllowedChars; | |
| end; | |
| procedure TBaseValidatableEdit.RestoreState; | |
| begin | |
| Text := FLastContent; | |
| SelStart := FLastPosition; | |
| end; | |
| procedure TBaseValidatableEdit.SaveState; | |
| begin | |
| FLastContent := Text; | |
| FLastPosition := SelStart; | |
| end; | |
| procedure TBaseValidatableEdit.WndProc(var msg: TMessage); | |
| begin | |
| Case msg.msg of | |
| WM_CHAR: Begin | |
| If IsValidChar( Chr( msg.wparam )) Then Begin | |
| SaveState; | |
| inherited; | |
| If not Validate Then | |
| RestoreState; | |
| End { If } | |
| Else | |
| If msg.wparam < 32 Then | |
| { Pass on control characters or Ctrl-C, Ctrl-V, Ctrl-X stop to | |
| work } | |
| inherited; | |
| End; { Case WM_CHAR } | |
| WM_PASTE: Begin | |
| SaveState; | |
| inherited; | |
| If not Validate Then | |
| RestoreState; | |
| End; { WM_PASTE } | |
| Else | |
| inherited; | |
| End; { Case } | |
| end; | |
| { TIntegeredit } | |
| function TIntegerEdit.GetAllowNegative: Boolean; | |
| begin | |
| Result := IsValidChar( '-' ); | |
| end; | |
| procedure TIntegerEdit.SetAllowedChars; | |
| begin | |
| AllowedChars := ['0'..'9','-',#8]; | |
| end; | |
| procedure TIntegerEdit.SetAllowNegative(const Value: Boolean); | |
| begin | |
| If Value Then | |
| Include( FAllowedChars, '-' ) | |
| Else | |
| Exclude( FAllowedChars, '-' ); | |
| end; | |
| {$HINTS OFF}{ Hide hint for "i not used" in method below. } | |
| function TIntegerEdit.Validate: Boolean; | |
| var | |
| err, i: Integer; | |
| begin | |
| Val( Text, i, err ); | |
| Result := (err = 0) or (GetTextLen = 0) or (Text = '-'); | |
| end; | |
| {$HINTS ON} | |
| { TFloatEdit } | |
| procedure TFloatEdit.SetAllowedChars; | |
| begin | |
| inherited; | |
| AllowedChars := AllowedChars + [DecimalSeparator]; | |
| end; | |
| function TFloatEdit.Validate: Boolean; | |
| begin | |
| try | |
| StrToFloat( text ); | |
| Result := true; | |
| except | |
| Result := (GetTextLen = 0) or (Text = '-'); | |
| end; | |
| end; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {== NumberEdits =======================================================} | |
| {: This unit implements an edit control for the input of positive | |
| integer numbers. | |
| @author Dr. Peter Below | |
| @desc Version 1.0 created 15 Oktober 2001<BR> | |
| Current revision 1.0<BR> | |
| Last modified 15 Oktober 2001<P> | |
| This unit started life as an example component for the Borland newsgroups. } | |
| {======================================================================} | |
| {$BOOLEVAL OFF}{Unit depends on shortcut boolean evaluation} | |
| Unit NumberEdits; | |
| Interface | |
| Uses | |
| Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, | |
| Dialogs, StdCtrls; | |
| Type | |
| TEMreplaceSel = TWMSettext; | |
| TFilteredEdit = Class( TEdit ) | |
| Private | |
| Procedure WMPaste( Var msg: TMessage ); message WM_PASTE; | |
| Procedure WMSetText( Var msg: TWMSettext ); message WM_SETTEXT; | |
| Procedure EMReplaceSel( Var msg: TEMReplaceSel ); | |
| message EM_REPLACESEL; | |
| Protected | |
| Procedure KeyPress(Var Key: Char); override; | |
| Function IsCharValid( ch: Char ): Boolean; virtual; abstract; | |
| Function IsStringValid( Const S: String ): Boolean; virtual; | |
| End; | |
| TNumberedit = Class(TFilteredEdit) | |
| Protected | |
| Function IsCharValid( ch: Char ): Boolean; override; | |
| End; | |
| Procedure Register; | |
| Implementation | |
| Uses clipbrd; | |
| Procedure Register; | |
| Begin | |
| RegisterComponents('PBGoodies', [tNumberedit]); | |
| End; | |
| Procedure TFilteredEdit.EMReplaceSel(Var msg: TEMReplaceSel); | |
| Begin | |
| If IsStringValid( msg.Text ) Then | |
| inherited; | |
| End; | |
| Function TFilteredEdit.IsStringValid(Const S: String): Boolean; | |
| Var | |
| i: Integer; | |
| Begin | |
| Result := True; | |
| For i:= 1 To Length(S) Do | |
| If not IsCharValid(S[i]) Then Begin | |
| Result := False; | |
| Break; | |
| End; | |
| End; | |
| Procedure TFilteredEdit.KeyPress(Var Key: Char); | |
| Begin | |
| If IsCharValid(Key) or (Key In [#8, ^V, ^C, ^X]) Then | |
| // #8 = backspace, ^V = Ctrl-V, ^C = Ctrl-C, ^X = Ctrl-X | |
| inherited | |
| Else | |
| Key := #0; | |
| End; | |
| Procedure TFilteredEdit.WMPaste(Var msg: TMessage); | |
| Begin | |
| If IsStringValid( Clipboard.AsText ) Then | |
| inherited; | |
| End; | |
| Procedure TFilteredEdit.WMSetText(Var msg: TWMSettext); | |
| Begin | |
| If IsStringValid( msg.Text ) Then | |
| inherited; | |
| End; | |
| Function tNumberedit.IsCharValid(ch: Char): Boolean; | |
| Begin | |
| result := ch In ['0'..'9']; | |
| End; | |
| End. | |






Leave a comment