The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 2,318 other followers

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:

–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.

view raw
ValidatableEdits.pas
hosted with ❤ by GitHub

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

view raw
NumberEdits.pas
hosted with ❤ by GitHub

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
<span>%d</span> bloggers like this: