Как ограничить десятичные цифры для поля ftFloat?
Мне нужно ограничить количество десятичных цифр, которые пользователь может ввести в качестве значения для ftFloat
поле.
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;
В примере после ввода 1234,5678
, TDBEdit
контрольные дисплеи 1234,56
но значение поля 1234,5678
,
Как предлагается в этом ответе, я попытался использовать EditMask
имущество.
Fld.EditMask := '9' + DecimalSeparator + '99;1; ';
К сожалению, этот подход создает несколько проблем:
- Я не могу установить переменное количество цифр для целой части (например, такие значения, как
12
,123
... не может быть напечатан) - Я не могу установить отрицательные значения (например, такие значения, как
-1
,-12
не может быть напечатано) Десятичный разделитель всегда виден при редактировании.
Как можно избежать, чтобы пользователь вводил больше десяти цифр в десятичной части (без добавления каких-либо других ограничений)?
2 ответа
Вместо того, чтобы не вводить в поле дополнительные цифры, вы также можете убрать цифры, прежде чем они будут отправлены в набор данных.
Удалите "лишние" цифры в событии TDataset.OnBeforePost или, возможно, лучше, используя событие OnDataChange TDatasource. (Псевдокод, непроверенные)
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;
Поскольку я ничего не нашел в стандартных элементах управления VCL для достижения этой цели, мой подход состоял бы в том, чтобы TDBEdit
потомок, которому можно присвоить желаемое DecimalPlaces
и затем может запретить пользователю вводить больше, чем настроено.
Это не зависит от базового типа данных, но для ftFloat
он попытается преобразовать полученное значение, исключив, например, десятичный десятичный оператор.
Это использует KeyPress
исключить ненужные ключи, которые могли бы сделать недействительным текущее значение, добавив слишком много десятичных знаков или в случае ftFloat
не конвертируемый TryStrToFloat
,
Пример с использованием образца будет:
//control
Edt := TDecimalPlacesDBEdit.Create(Self);
Edt.DataSource := Dsc;
Edt.DataField := Fld.FieldName;
Edt.Top := 5;
Edt.Left := 5;
Edt.Parent := Self;
Edt.DecimalPlaces := 2;
Вот подход к реализации в новом модуле:
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.