Jump to content
dan13l

Issue with TDataModule base class without DFM (and Delphi designer opening data modules as forms)

Recommended Posts

I am having a weird issue with Delphi which at first looked like a bug in the IDE, but now I'm thinking it's a combination with me not doing something right.

 

The problem is as follows:

- I have a bunch of TDataModules in the project and I needed to change them to descend from a base class that adds a method and some data the descendants can use.

- The base class won't contain any designable items (no published elements), so I don't necessarily need a base DFM.

- So I went and changed all the places that descend from TDataModule to descend from the new base class; all builds and runs fine.  However, when I open the descendant data module in the designer, it opens it as a form and so adds a bunch of form related properties to the DFM, after which I will get runtime errors such "Error reading MainDataModule.ClientHeight: Property ClientHeight does not exist."

 

For completeness, here is the PAS for the base data module:

unit uBaseDataModule;

interface

uses
  System.Classes;

type
  TBaseDataModule = class(TDataModule)
  private
    FSomeList: TList;
  protected
    procedure SomeMethod;
  end;

implementation

[...]

end.

And here is the PAS for the descendant data module:

unit fMainDataModule;

interface

uses
  System.SysUtils, System.Classes, uBaseDataModule;

type
  TMainDataModule = class(TBaseDataModule)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainDataModule: TMainDataModule;

implementation

{%CLASSGROUP 'Vcl.Controls.TControl'}

{$R *.dfm}

end.

The DFM for the descendant looks like this:

object MainDataModule: TMainDataModule
  Left = 0
  Top = 0
  Height = 480
  Width = 640
end

If I did it "properly", the DFM above would inherit the base DFM, so the keyword object would have to be changed to inherited, as I'm sure you know. So I am aware I'm doing things in a slightly hacky way, I just figured if it works for forms, it should work for data modules, but for some reason Delphi assumes the data module is a form. This is what the descendant's DFM looks like after opening the designer and saving (notice the form related properties having been added):

 

object MainDataModule: TMainDataModule
  Left = 0
  Top = 0
  ClientHeight = 402
  ClientWidth = 608
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = 'Segoe UI'
  Font.Style = []
  TextHeight = 15
end

 

On the project I'm working on, there was already a base form - without a DFM - with many forms descending from it, with the designer not having any issues. I'm really curious to know how Delphi determines what is a form and what is a data module when it opens the DFM in the designer.

 

My short term solution is to create a DFM file and change all descendant data modules to say inherited instead of object, and add the base data module to the DPR with the correct bits going into the bit inside the curly braces like so (I simply re-added the base data module unit after adding the {$R *.dfm} bit):

  uBaseDataModule in 'uBaseDataModule.pas' {BaseDataModule: TDataModule};

As an aside, it's weird that Delphi assumes there is a global variable named BaseDataModule. There isn't one defined. It appears to be using this name ("BaseDataModule") when going to File -> Add -> Other -> Inheritable Items. Otherwise, in there, it's just "TDataModule". Surely it should use "TBaseDataModule", not the name defined in curly braces. Similarly, I've noticed that the DFM's root object also has a name, i.e., "MainDataModule: TMainDataModule". What's the point of that? Surely it should just be the type as there may not be a MainDataModule global variable (removing it doesn't break anything; in fact, putting anything for the name works, but Delphi will correct it after saving).

 

This is probably some really ancient Delphi stuff that isn't going to change any time soon, but I wonder if anyone has a workaround so I can avoid needlessly having a base DFM for all my data modules. All I wanted was to add a method and some data to existing data modules, so having them descend from a base data module looked like the way to go.

Share this post


Link to post

To make the inheritance work for designer classes like a data module your 

TBaseDataModule 

has to have a DFM-file, even if it is practically empty, but it must contain a valid Name property. The simplest way to get one is to create a new data module in the IDE and change its Name property to BaseDataModule, then save the unit under the name you want. You can now change the inheritance of your

TMainDataModule

in the editor, but you also have to switch the designer for that module to the dfm view and there change the first "object" keyword to "inherited". And make sure your TBaseDatamodule is not in the list of autocreated items! You can delete the BaseDataModule variable the IDE created for you, it is not needed.

 

Share this post


Link to post

@PeterBelow, thank you but my question was how to have a base data module class without a DFM. This is possible with forms, but when it comes to doing the same thing with data modules, Delphi's designer treats data module as a form when it clearly shouldn't.

 

EDIT:

 

I may not have been clear, so here's a small example.  You can do this with forms:

 

Say you have a form: TForm1 = class(TForm).  You then want to change it to inherit from a base class, e.g. TBaseForm (say, you want to add something to all descendants, but you don't want the base class to be designable). You can simply declare TBaseForm = class(TForm) - no DFM here, and simply change the code to inherit from TBaseForm instead of TForm. This works fine. If you were to then open the designer for TForm1, it would work as normal.  Doing the same thing with data modules works as in the code would compile and run without problems, but if I open the designer for such a data module (descendant data module), it shows a form and screws up the DFM (after which the code won't run :D)

Edited by dan13l
Clarification with example

Share this post


Link to post

OK, I would not do it this way, but your problem is probably a missing FormType node in the DPROJ file. Look for nodes "DCCReference Include" and see how a "uninherited" datamodule is defined there. Compare with one of your derived datamodules.

Edited by PeterBelow

Share this post


Link to post
9 hours ago, dan13l said:

PeterBelow, thank you but my question was how to have a base data module class without a DFM

I don't think you can. I've been following the same method as PeterBelow describes, if not, I'm having the same problems as you.

Share this post


Link to post

Use an interposer for TDataModule.

 

The datamodule base class (no DFM):

unit Foo.Bar;

interface

uses
  Classes;

type
  TBaseDataModule = class(TDataModule)
  private
    FFoobar: integer;
  public
    property Foobar: integer read FFoobar write FFoobar;
  end;

implementation

end.

 

Your datamodule:

unit Whatever;

uses
  Classes, etc. etc., ...
  Foo.Bar;

type
  // The interposer
  // You could have declared it in the Foo.Bar unit but I prefer to declare it explicitly
  // everywhere it's used to make it clear what is going on.
  TDataModule = TBaseDataModule;

// Your regular TDataModule stuff
type
  TMainDataModule = class(TDataModule)
  ...
  end;

etc.

 

Share this post


Link to post

@Anders Melander Thank you, that's what I was looking for. The designer is happy now 🙂 I used this before, just didn't realise it would work here/my mind didn't connect the dots. Thank you again.

 

@Hans J. Ellingsgaard What's with the negative attitude 🙂 In Delphi, what can't you do 🙂 

 

@PeterBelow I've tried your suggestion looking in the dproj, just in case it would also work but it didn't. Inheritable and non-inheritable forms/data modules look identical there. It was worth a shot. Maybe the following logic is flawed somewhat but I think if dproj is missing and is recreated from dpr, it would be pretty annoying having to manually edit things in there to ensure things work as expected (but I realise maybe I'm doing something a little weird).

 

Thank you all for input.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×