Reputation: 8043
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:
12
, 123
... can't be typed)-1
, -12
can't be typed)The decimal separator is always visible when editing.
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
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
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