Как ограничить десятичные цифры для поля 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; ';

К сожалению, этот подход создает несколько проблем:

  1. Я не могу установить переменное количество цифр для целой части (например, такие значения, как 12, 123... не может быть напечатан)
  2. Я не могу установить отрицательные значения (например, такие значения, как -1, -12 не может быть напечатано)
  3. Десятичный разделитель всегда виден при редактировании.

    введите описание изображения здесь

Как можно избежать, чтобы пользователь вводил больше десяти цифр в десятичной части (без добавления каких-либо других ограничений)?

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.
Другие вопросы по тегам