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
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. |
{== 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 Reply