Custom DBEdit raises exception at design time

Viewed 107

Hi I am trying to use a database (AbsoluteDB) to fill an HTML div. The table fields include names like "Title" and "Description". My DBEdit component has 2 extra properties "Pre" and "Suff" together with a var "Full". At design time I would fill the extra properties e.g.

"Pre" value would be <title> and the "Suff" value would be </div>.

At runtime the onchange event would fill "Full" with "Pre"+Field.Value+"Suff".

The component (code below) compiles but when, at design time I add the component to my form I get this error message. I am using Delphi 7:

Access violation at address 40341575 in module 'dbrtl70.bpl'. Read of address 000000D2.

 unit PrefEdDb;

interface

uses WinTypes, WinProcs, Messages, SysUtils, Classes, Controls, 
     Forms, Graphics, Dbctrls, Windows, ExtCtrls, StdCtrls, Variants;

type
  TPrefEdDb = class(TDBEdit)
    private
        FPre : String;
        FSuff : String;
        procedure AutoInitialize;
        procedure AutoDestroy;
        function GetPre : String;
        procedure SetPre(Value : String);
        function GetSuff : String;
        procedure SetSuff(Value : String);
        function DoFull:string;

    protected
        procedure Change; override;
        procedure Click; override;
        procedure DoExit; override;
        procedure KeyPress(var Key : Char); override;
        procedure Loaded; override;

    public
        Full : String;
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;

    published
        property OnChange;
        property OnClick;
        property OnDblClick;
        property OnDragDrop;
        property OnEnter;
        property OnExit;
        property OnKeyDown;
        property OnKeyPress;
        property OnKeyUp;
        property OnMouseDown;
        property OnMouseMove;
        property OnMouseUp;
        property Pre : String read GetPre write SetPre;
        property Suff : String read GetSuff write SetSuff;

  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TPrefEdDb]);
end;

function TPrefEdDb.DoFull:string;
var
  s:string;
begin
  result:='';
  if ((Field.Text<>'') or (Field.Value<>null)) then
    begin
      s:='';
      if FPre<>'' then s:=FPre;
      s:=s+field.Text;
      if FSuff<>'' then s:=s+FSuff;
      Result:=s;
    end;
end;

procedure TPrefEdDb.AutoInitialize;
begin
  Full := '';
  FPre := '';
  FSuff := '';
end;

procedure TPrefEdDb.AutoDestroy;
begin
     { No objects from AutoInitialize to free }
end;

function TPrefEdDb.GetPre : String;
begin
  Result := FPre;
end;

procedure TPrefEdDb.SetPre(Value : String);
begin
  FPre := Value;
end;

function TPrefEdDb.GetSuff : String;
begin
  Result := FSuff;
end;

procedure TPrefEdDb.SetSuff(Value : String);
begin
   FSuff := Value;
end;

procedure TPrefEdDb.Change;
begin
  inherited Change;
  if Field.Text<>'' then Full:=DoFull;
end;

procedure TPrefEdDb.Click;
begin
   inherited Click;
end;


procedure TPrefEdDb.DoExit;
begin
   inherited DoExit;
 end;

procedure TPrefEdDb.KeyPress(var Key : Char);
const
  TabKey = Char(VK_TAB);
  EnterKey = Char(VK_RETURN);
begin
     inherited KeyPress(Key);
end;

constructor TPrefEdDb.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  AutoInitialize;
end;

destructor TPrefEdDb.Destroy;
begin
  AutoDestroy;
  inherited Destroy;
end;

procedure TPrefEdDb.Loaded;
begin
  inherited Loaded;
end;


end.
2 Answers

The problem is that you are assuming that when you reference Field, it is not Nil. If the dataset connected to your component uses non-persistent TFields, their values will be Nil until the dataset is open.

Make the changes shown below

function TPrefEdDb.DoFull:string;
var
  s:string;
begin
  result:='';
  Assert(Field <> Nil);      //  MA
  if Field = Nil then exit;  //  MA
  if ((Field.Text<>'') or (Field.Value<>null)) then
    begin
      s:='';
      if FPre<>'' then s:=FPre;
      s:=s+field.Text;
      if FSuff<>'' then s:=s+FSuff;
      Result:=s;
    end;
end;

procedure TPrefEdDb.Change;
begin
  inherited Change;

  Assert(Field <> Nil);      //  MA
  if Field = Nil then exit;  //  MA
  if Field.Text<>'' then Full:=DoFull;
end;

The recompile & reinstall your package, then attempt to drop the component onto the form of a new project. Instead of the AV, you will immediately get a pop-up message that the Assertion in the Change method has failed, and it has failed for exactly the reason @RemyLebeau predicted.

The take-home message is that when coding a db-aware object, any of the following of its properties may be Nil at design-time:

  • Field
  • DataSource
  • DataSet
  • Any of the above's properties

and you need to take account of this in your code.

If you get into the habit of checking for Nil references yourself, you don't really need the Asserts but it does not harm to leave them in because their impact on performance is minimal.

I think that the problem trying to access the field.text

procedure TPrefEdDb.Change;
begin
  inherited Change;
  if Field.Text<>'' then Full:=DoFull;
end;

So I modified that to this

procedure TPrefEdDb.Change;
begin
  inherited Change;
  if ((DataSource<>nil) and (DataSource.DataSet.Active=True) and (Field.AsString<>'')) 
  then Full:=DoFull;
end;

and it works fine. The only problem I have to solve now is that onchange the result is from the previous record not the current. Thank you for your help.

Related