如何限制ftFloat字段的十进制数字?

时间:2018-06-15 10:17:23

标签: delphi delphi-2007 masking

我需要限制用户可以键入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

根据this answer的建议,我已尝试使用EditMask属性。

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

不幸的是,这种方法引入了几个问题:

  1. 我无法为整数部分设置可变位数(例如12123等值...无法输入)
  2. 我无法设置负值(例如-1-12之类的值无法输入)
  3. 编辑时始终可以看到小数点分隔符。

    enter image description here

  4. 如何避免用户在小数部分输入超过N位的数字(不添加任何其他类型的限制)?

2 个答案:

答案 0 :(得分:1)

您可以在将数字发布到数据集之前删除数字,而不是避免键入字段的额外数字。

剥离"额外" TDataset.OnBeforePost事件中的数字,或者更好地使用TDatasource的OnDataChange事件。 (伪代码,未测试)

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;

答案 1 :(得分:1)

由于我没有在标准VCL控件中发现任何内容,我的方法是拥有一个TDBEdit后代,可以为其分配所需的DecimalPlaces,然后可以禁止用户输入超过配置的内容。

这与基础数据类型无关,但对于ftFloat,它会尝试转换结果值,例如,多次decimalcperator。

这使用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.