Home

Tutorial : Building a numeric edit control

TNumEdit

Download the  source of this component here : DownloadTNumEdit
Or on the components page of this site.

type
  TNumEdit = class(TCustomEdit)
 private
    { Private declarations }
   public
    { Public declarations }
  published
    { Published declarations }
  end;

1. The goals of the component TNumEdit

TNumEdit is an edit control which only can be used for numeric input.
Besides numeric values also a decimalseparator and minus (-) key may be entered. (1x each)
The input format is free, which means that at first no specific format is used like for example the number of decimals.
The component offers the possibility to round the value on a given number of decimals.
The formatting takes place in the method DoExit.
If you do the formatting in the Change method it could cause unwanted behaviour because at every keypress the value is formatted which changes the cursor position.
The Change method is added to format values which are 'stuffed' in the control by code.

2.Used techniques

New published properties

Besides the inherited properties of TCustomEdit there are a few new ones introduced :

All properties can be set in design- and runtime.

Override methods

The following methods are 'override' from TCustomEdit :

KeyPress

The property keypressed skips the onchange event formatting.
With the function : Pos(Key, Text) > 0 is determined if the string, yes it is still a string, has already a decimalseparator or a '-'.
Pos returns the position of the searched character or 0 if it isn't found.
With if (Key = '-') and (Selstart <> 0) then is checks the position at front of the  '-' key. (Must be in front.)
If the decimal separator is entered twice the cursor position is set just behind the separator and the decimal part of the string is selected.

procedure TNumEdit.KeyPress(var Key: Char);
begin
  KeyPressed := true;	   // Skip the Change Method
  Inherited KeyPress(Key); // For the user OnKeyPressevent

  //Check for Numeric and backspaces '-' and decimalseparator ','/'.'
  if not (Key in ['0'..'9', '-', DecimalSeparator,#8]) then
  begin
    KeyPressed := False;
    key:=#0;     // Simulate a none keypress
    beep;
  end else  //Check for existing ofdecimalsepatator and '-'
  if ((Key = DecimalSeparator) or (key = '-')) and (Pos(Key, Text) > 0) then
  begin
    If Key = DecimalSeparator then
    begin
      SelStart := Pos(Key, Text);// Position Cursor behind decimalseparator 
      SelLength := Length(Text); // and select decimal part of string
    end;
    key:=#0;
  end else // If '-' then first check position at front
  if (Key = '-') and (Selstart <> 0) then
  begin
    key:=#0;
    beep;
  end;
end;

DoExit / DoEnter

In this methods the rounding is done.

procedure TNumEdit.DoEnter;  // Perform rounding when entering
begin
  If AutoRounding then // Only when AutoRounding is enabled
    if (Text <> '') and Round then
      Text := RoundedText(Text);
end;

procedure TNumEdit.DoExit;   // Perform rounding when exiting
begin
  if (Text <> '') and Round then
    Text := RoundedText(Text);
end;

Change

The change method takes care of rounding when a value is set in code. (KeyPressed false)

The rounding

Rounding is done with the procedure FmtStr.

Function TNumEdit.RoundedText(aText : String) : String; //Perform rounding the text
begin
  FmtStr(Result,'%0.'+ IntToStr(RoundingDecimals) +'f',[StrToFLoat(Text)]);
  KeyPressed := false;
end;


Alignment

Numeric values are mostly right aligned.
A standard edit box doesn't offer a way to do alignment.
After struggling through the vcl I found out that a TMemo (is in fact a multiline edit) can align the text which it contains.
I build this code in TNumEdit to enable alignment.
You have to publish a property alignment of type TAlignment and  with the override method
Createparams you can set the style you wish.

procedure TNumEdit.CreateParams(var Params: TCreateParams);
const
  Alignments: array[Boolean, TAlignment] of DWORD =
    ((ES_LEFT, ES_RIGHT, ES_CENTER),(ES_RIGHT, ES_LEFT, ES_CENTER));// Windows parameters
begin
  inherited CreateParams(Params);// Don't forget to inherit
  with Params do
  begin
    Style := Style or ES_MULTILINE or
      Alignments[UseRightToLeftAlignment, FAlignment];// Set the style for alignment
  end;
end;

 

The complete source code of TNumEdit :

////////////////////////////////////////////////////////////////////////
// TNumEdit Control version 1.0                              08-01-2000
// Input  : 0..9, -(1x) and the DecimalSeparator (1x)
// This control can also round the value by #decimals
//
// (c) BeenSoft   Http://surf.to/beensoft     Mail : beensoft@yahoo.com
////////////////////////////////////////////////////////////////////////

unit NumEdit;

interface

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

type
  TNumEdit = class(TCustomEdit)
  private
    { Private declarations }
    FAutoRounding  : Boolean;
    FRound         : Boolean;                       // Rounding
    FRoundingDecimals : Integer;                    // Number of decimals for rounding
    FKeyPressed    : Boolean;                       // Keypressed
    FAlignment     : TAlignment;
    Function RoundedText(aText : String) : String;  // Perform the rounding
    procedure KeyPress(var Key: Char); override;
    procedure DoExit; override;
    procedure DoEnter; override;
    procedure Change; override;
  public
    { Public declarations }
    Constructor Create (AOwner : TComponent); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure SetAlignment(Value: TAlignment);
    property KeyPressed : boolean read FKeyPressed write FKeyPressed default False;
    property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify;
   published
    { Published declarations }
    //new properties
    property AutoRounding : boolean read FAutoRounding write FAutoRounding default False;
    property RoundingDecimals : Integer read FRoundingDecimals write FRoundingDecimals;
    property Round : boolean read FRound write FRound default false;
    //publishing properties declared in TCustomEdit 
    property Anchors;
    property AutoSelect;
    property AutoSize;
    property BiDiMode;
    property BorderStyle;
    property CharCase;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property Font;
    property HideSelection;
    property ImeMode;
    property ImeName;
    property MaxLength;
    property OEMConvert;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PasswordChar;
    property PopupMenu;
    property ReadOnly;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Text;
    property Visible;
    property OnChange;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDock;
    property OnStartDrag;
  end;

procedure Register;

implementation

Constructor TNumEdit.Create (AOwner : TComponent);
begin
  Inherited Create(AOwner);
  SetAlignment(taRightJustify);
  Text := '0';
end;

procedure TNumEdit.CreateParams(var Params: TCreateParams);
const
  Alignments: array[Boolean, TAlignment] of DWORD =
    ((ES_LEFT, ES_RIGHT, ES_CENTER),(ES_RIGHT, ES_LEFT, ES_CENTER));
begin
  inherited CreateParams(Params);
  with Params do
  begin
    Style := Style or ES_MULTILINE or
      Alignments[UseRightToLeftAlignment, FAlignment];
  end;
end;

procedure TNumEdit.SetAlignment(Value: TAlignment); //Set the Alignment
begin
  if FAlignment <> Value then
  begin
    FAlignment := Value;
    RecreateWnd;   //Force a redraw of the control
  end;
end;

Function TNumEdit.RoundedText(aText : String) : String; //Perform rounding the text
begin
  FmtStr(Result,'%0.'+ IntToStr(RoundingDecimals) +'f',[StrToFLoat(Text)]);
  KeyPressed := false;
end;

procedure TNumEdit.Change;
begin
  if not KeyPressed then
  begin
    if (Text <> '') and Round then
      Text := RoundedText(Text);
  end
end;

procedure TNumEdit.DoEnter;  // Perform rounding when entering
begin
  If AutoRounding then
    if (Text <> '') and Round then
      Text := RoundedText(Text);
end;

procedure TNumEdit.DoExit;   // Perform rounding when exiting
begin
  if (Text <> '') and Round then
    Text := RoundedText(Text);
end;

procedure TNumEdit.KeyPress(var Key: Char);
begin
  KeyPressed := true;
  Inherited KeyPress(Key); // For user events

  //Check for Numeric and backspaces
  if not (Key in ['0'..'9', '-', DecimalSeparator,#8]) then
  begin
    KeyPressed := False;
    key:=#0;
    beep;
  end else  //Check for existing ofdecimalsepatator and '-'
  if ((Key = DecimalSeparator) or (key = '-')) and (Pos(Key, Text) > 0) then
  begin
    If Key = DecimalSeparator then
    begin
      SelStart := Pos(Key, Text);
      SelLength := Length(Text);
    end;
    key:=#0;
  end else // If '-' then first check position at front
  if (Key = '-') and (Selstart <> 0) then
  begin
    key:=#0;
    beep;
  end;
end;

procedure Register;
begin
  RegisterComponents('Beensoft', [TNumEdit]);
end;


end.

arrows.gif (215 bytes)Top