Fabrizio
Fabrizio

Reputation: 8043

How to limit decimal digits for a ftFloat field?

I need to limit the number of decimal digits that the user can type as value for a ftFloat field.

var
  Dst : TClientDataSet;
  Dsc : TDataSource;
  Fld : TNumericField;
  Edt : TDBEdit;
begin
  //dataset
  Dst := TClientDataSet.Create(Self);
  Dst.FieldDefs.Add('TEST', ftFloat);
  Dst.CreateDataSet();
  Dst.Active := True;
  Fld := Dst.Fields[0] as TNumericField;
  Dst.Append();
  Fld.AsFloat := 1234.56;
  Dst.Post();

  //field
  Fld.DisplayFormat := '0,.##'; //2 optional decimals, with thousands separator
  Fld.EditFormat := '0.##'; //2 optional decimals, withhout thousands separator

  //datasource
  Dsc := TDataSource.Create(Self);
  Dsc.DataSet := Dst;

  //control
  Edt := TDBEdit.Create(Self);
  Edt.DataSource := Dsc;
  Edt.DataField := Fld.FieldName;
  Edt.Top := 5;
  Edt.Left := 5;
  Edt.Parent := Self;
end;

In the example, after typing 1234,5678, the TDBEdit control displays 1234,56 but the field's value is 1234,5678.

As suggested in this answer, I've tried using the EditMask property.

Fld.EditMask := '9' + DecimalSeparator + '99;1; ';

Unfortunately this approach introduces several problems:

  1. I can't set a variable number of digits for the integer part (e.g. values like 12, 123... can't be typed)
  2. I can't set negative values (e.g. values like -1, -12 can't be typed)
  3. The decimal separator is always visible when editing.

    enter image description here

How can I avoid that the user types more than N digits in the decimal part (Without adding any other kind of limitation)?

Upvotes: 3

Views: 2599

Answers (2)

nil
nil

Reputation: 1328

As I found nothing in standard VCL controls to achieve this, my approach would be to have a TDBEdit descendant that can be assigned desired DecimalPlaces and can then prohibit the user from entering more than configured.

This is independent of the underlying data-type, but for ftFloat it will try to convert the resulting value, eliminating e.g. multiple times decimalseperator.

This uses KeyPress to eliminate unwanted keys that would invalidate the current value, either adding too many decimal places or in case of ftFloat not being convertible by TryStrToFloat.

An example using sample then would be:

  //control
  Edt := TDecimalPlacesDBEdit.Create(Self);
  Edt.DataSource := Dsc;
  Edt.DataField := Fld.FieldName;
  Edt.Top := 5;
  Edt.Left := 5;
  Edt.Parent := Self;
  Edt.DecimalPlaces := 2;

Here is an implementation approach in a new unit:

unit Unit1;

interface

uses
  Vcl.DBCtrls;

type
  TDecimalPlacesDBEdit = class(TDBEdit)
  private
    FDecimalPlaces: Integer;
    function IsValidChar(Key: Char): Boolean;
  protected
    procedure KeyPress(var Key: Char); override;
  public
    property DecimalPlaces: Integer read FDecimalPlaces write FDecimalPlaces;
  end;

implementation

uses
  System.SysUtils,
  Data.DB,
  Winapi.Windows;

{ TDecimalPlacesDBEdit }

function TDecimalPlacesDBEdit.IsValidChar(Key: Char): Boolean;

  function IsValidText(const S: string): Boolean;
  var
    ADecPos, AStartPos: Integer;
    V: Double;
  begin
    Result := False;
    ADecPos := Pos(FormatSettings.DecimalSeparator, S);
    if ADecPos > 0 then
    begin
      AStartPos := Pos('E', UpperCase(S));
      if AStartPos > ADecPos then
        ADecPos := AStartPos - ADecPos - 1
      else
        ADecPos := Length(S) - ADecPos;
      if ADecPos > DecimalPlaces then
        Exit;
    end;
    if Assigned(Field) and (Field.DataType in [ftFloat{, ftSingle, ftExtended}]) then
      Result := TryStrToFloat(S, V)
    else
      Result := True;
  end;

var
  AEndPos, AStartPos: Integer;
  S: string;
begin
  Result := DecimalPlaces = 0;
  if not Result then
  begin
    S := Text;
    AStartPos := SelStart;
    AEndPos := SelStart + SelLength;
    // Prepare current Text as if the user typed his key, then check if still valid.
    Delete(S, SelStart + 1, AEndPos - AStartPos);
    Insert(Key, S, AStartPos + 1);
    Result := IsValidText(S);
  end;
end;

procedure TDecimalPlacesDBEdit.KeyPress(var Key: Char);
begin
  inherited KeyPress(Key);
  if (Key >= #32) and not IsValidChar(Key) then
  begin
    MessageBeep(0);
    Key := #0;
  end;
end;

end.

Upvotes: 1

H.Hasenack
H.Hasenack

Reputation: 1164

Rather than avoiding typing the field extra digits, you can also strip the digits before they are posted to the datasaet.

Strip the "extra" digits on the TDataset.OnBeforePost event, or maybe better using the OnDataChange event of a TDatasource. (Pseudocode,untested)

procedure TSomeClass.OnDataChange(aField:TField)
begin
  if Assigned(aField) and (aField.FieldName='TEST') and not aField.IsNull then
    aField.AsFloat:=round(aField.AsFloat*100)/100.0;    
end;

Upvotes: 2

Related Questions