Search code examples
delphivalidationdelphi-7tclientdatasetdbgrid

How to prevent user from leaving a DBGrid's cell depending on it's value


Here's my situation: I have a task in which I need to make some operations using TClientDataSet's (yes I have to use this specific component). The user will be able to edit the data inside the TClientDataSet, so I'm using a TDBGrid connected to a TDataSource, which is connected to my TClientDataSet.

What I need to do is to prevent the user from leaving the cell (moving the focus to somewhere else in the program) based on it's value. A valid value is a time starting from 00:00 to 23:59. It cannot be empty or have blank characters. I'm using the EditMask property of the TClientDataSet's fields so it's very easy to type straight in the right format.

Example: If the user typed 29:00 in the cell, and then tries to move to another cell (or another component), I want to be able to stop that, and make him enter some other valid value in the cell (like 15:00), then he can leave it and do something else (like editing another cell).

What are the options to make this kind of validation? I'm using Borland Delphi 7.

Thanks.


Solution

  • Use the TField.OnValidate event. This is called after the EditMask has been validated, but before the data is actually written to the record buffer. This allows you to do more extensive validations after the basic character validation has been done.

    The event receives the field being validated in the Sender parameter to the TFieldNotifyEvent:

    TFieldNotifyEvent = procedure(Sender: TField) of Object;
    

    You can do what you need to do to validate the data in that event, and raise an Exception if the new value does not meet your time span requirements. For instance, the following will raise an exception if less than 2 characters is entered into the field:

    procedure TForm4.ClientDataSet1ClientDataSet1Field1Validate(Sender: TField);
    begin
      if not (Length(Trim(Sender.AsString)) > 1) then
        raise Exception.Create('Invalid length for field content.');
    end;
    

    An alternative is to use the TDBGrid.OnColExit event instead, but I prefer to keep the data validation code with the database content more directly instead of tying it to a particular part of the user interface. (Keeping it with the database makes it automatically enforced even when you change your mind later and switch from using a grid to using a separate form for record maintenance, or when you set the field value from code instead of the UI.)

    As far as actually validating the data itself, the easiest way is to use TRegEx if it's available in the version of Delphi you're using. Something like this expression should work:

    var
      FoundMatch: Boolean;
      TimeStr: string;
    begin
      TimeStr := Sender.AsString;
      FoundMatch := TRegEx.IsMatch(TimeStr,
            '\A([0-1][0-9]|[2][0-3]):[0-5][0-9]\z', [roMultiLine]);
      if not FoundMatch then
        raise Exception.CreateFmt('Invalid time value %s', [TimeStr]);
    end;
    

    If TRegEx isn't available in your version of Delphi, you can find the free, open source TPerlRegEx at the Regular-Expressions.info website; it's the code that was used in current Delphi versions in the core of its regular expression support. In that case, this code should work as well:

    var
        Regex: TPerlRegEx;
        FoundMatch: Boolean;
        TimeStr: string;
    begin
      TimeStr := Sender.AsString;
      Regex := TPerlRegEx.Create;
      try
        Regex.RegEx := '\A([0-1][0-9]|[2][0-3]):[0-5][0-9]\z';
        Regex.State := [preNotEmpty];
        Regex.Subject := TimeStr;
        FoundMatch := Regex.Match;
        if not FoundMatch then
          raise Exception.CreateFmt('Invalid time value %s', [TimeStr]);
      finally
        Regex.Free;
      end;
    end;