Search code examples
delphidelphi-10.1-berlindelphi-10.2-tokyo

Avoid calling duplication for validation


I am stuck a bit. My aim is to check and validate my currency field always when it's value changed never mind from the inline code or from the user interface. Is there any validation pattern or sample for it? I want to avaiod calling my validation procedure twice.

Any help or advice would be greatly appreciated.

Here is my example:

type
TMyForm = class(TForm)   
  ceMyCurrencyEdit: TcxCurrencyEdit;
  procedure FormShow(Sender: TObject);
private
{ Private declarations }
  procedure SetupMyForm;
public
{ Public declarations }
  procedure ValidateMyCurrencyValue;
  function IsValidCurrency: Boolean;
end; 

procedure TMyForm.ValidateMyCurrencyField;
begin
  if not IsValidCurrency then
  begin
    WarningDlg(
       Format(
           'InValid value in field: [%s..%s]',
           [Formatfloat(ceMyCurrencyEditDisplayFormat, ceMyCurrencyEdit.MinValue),
           Formatfloat(ceMyCurrencyEdit.DisplayFormat, ceMyCurrencyEdit.MaxValue)]
        )
    );
    ceMyCurrencyEdit.Value := ceMyCurrencyEdit.MinValue; 
  end;   
end;

function TMyForm.IsValidCurrency: Boolean;
begin
  Result := (ceMyCurrencyEdit.Value >= ceMyCurrencyEdit.MinValue) and (ceMyCurrencyEdit.Value <= ceMyCurrencyEdit.MaxValue);  
end;

procedure TMyForm.SetupMyForm;
begin
  //MaxValue is 100
  ceMyCurrencyEdit.Value := 102;
  //At this point I need to call ValidateMyCurrencyField to get warning msg and refuse its value
  ValidateMyCurrencyField;
end;

procedure TMyForm.FormShow(Sender: TObject);
begin
  SetupMyForm;
end;

procedure TMyForm.ceMyCurrencyEditPropertiesEditValueChanged(Sender: TObject);
begin
  ValidateMyCurrencyField;
end;

What I want is...

Thanks for the answers!


Solution

  • I'm not sure why you seemed a bit dismissive os @Nil's suggestion, but below is an example project which shows using the cxCurrencyEdit's validation facility and using an interposer TcxCurrencyEdit class to record the result of the validation process in its ValidationState property.

    Obviously you could use the ValidationState property to avoid repeating your validation process or indeed doing it at all if the state says it's valid. However, performance-wise, I very much doubt that you'd be saving yourself anything.

    Obviously, the point of the interposer class is to record the validation state of the control so that you can avoid validating it a second time. Whether you use it in combination with the cxCurrencyEdit1PropertiesValidate event is entirely up to you.

    I leave you to weave this in with your existing code.

    Btw, the TEdit control is just there to allow focus to be shifted from the cxCurrencyEdit to trigger its cxCurrencyEdit1PropertiesValidate event.

    UpdateYou asked in a comment about validating a value which is set in code. With an interposer class, it's easy to add a Getter and Setter for the Value property and do the validation in the Setter - see `SetValue below,

      type
        TCurrencyValidation = (cvNotDone, cvOK, cvInvalid);
    
        TcxCurrencyEdit = class(cxCurrencyEdit.TcxCurrencyEdit)
        private
          FValidationState : TCurrencyValidation;
          function GetValue: Double;
          procedure SetValue(const AValue: Double);
        protected
          property ValidationState : TCurrencyValidation read FValidationState write FValidationState;
        published
          property Value : Double read GetValue write SetValue;
        end;
    
        TForm1 = class(TForm)
          cxCurrencyEdit1: TcxCurrencyEdit;
          Edit1: TEdit;
          procedure FormCreate(Sender: TObject);
          procedure cxCurrencyEdit1PropertiesChange(Sender: TObject);
          procedure cxCurrencyEdit1PropertiesValidate(Sender: TObject;
            var DisplayValue: Variant; var ErrorText: TCaption;
            var Error: Boolean);
        end;
    
      procedure TForm1.FormCreate(Sender: TObject);
      begin
        cxCurrencyEdit1.Properties.MinValue := 5;
        cxCurrencyEdit1.Properties.MaxValue := 10;
        cxCurrencyEdit1.Value := 8;
      end;
    
      procedure TForm1.cxCurrencyEdit1PropertiesChange(Sender: TObject);
      begin
        TcxCurrencyEdit(Sender).ValidationState := cvNotDone;
      end;
    
      procedure TForm1.cxCurrencyEdit1PropertiesValidate(Sender: TObject;
        var DisplayValue: Variant; var ErrorText: TCaption; var Error: Boolean);
      var
        Min,
        Max : Double;
      begin
        Min := TcxCurrencyEdit(Sender).Properties.MinValue;
        Max := TcxCurrencyEdit(Sender).Properties.MaxValue;
        if (DisplayValue >= Min) and (DisplayValue <= Max) then
          TcxCurrencyEdit(Sender).ValidationState := cvOK
        else
          TcxCurrencyEdit(Sender).ValidationState := cvInvalid;
        Error := not (TcxCurrencyEdit(Sender).ValidationState = cvOK);
        if Error then
          ErrorText := 'InvalidValue';
      end;
    
      function TcxCurrencyEdit.GetValue: Double;
      begin
        Result := inherited Value;
      end;
    
      procedure TcxCurrencyEdit.SetValue(const AValue: Double);
      begin
        //  insert code to validate AValue here
        inherited Value := AValue;
      end;