How to configure dependencies in Spring4D in Delphi? - delphi

I'm reading Nick Hodges' book "Dependency Injection in DELPHI". The chapter "Registering Factories" considers this example:
There is an interface:
ICoffeeMaker = interface
['{73436E03-EF65-44F5-9606-F706156CBEB5}']
procedure MakeCoffee;
end;
...and the class that implements it:
type
TCoffeeMaker = class(TInterfacedObject, ICoffeeMaker)
private
FCoffeeBrand: string;
FBrewingMinutes: integer;
public
constructor Create(const aCoffeeBrand: string; const aBrewingMinutes: integer);
procedure MakeCoffee;
end;
constructor TCoffeeMaker.Create(const aCoffeeBrand: string; const aBrewingMinutes: integer);
begin
inherited Create;
FCoffeeBrand := aCoffeeBrand;
FBrewingMinutes := aBrewingMinutes;
end;
procedure TCoffeeMaker.MakeCoffee;
begin
WriteLn('Pour hot water over the ', FCoffeeBrand, ' so that it brews for ', FBrewingMinutes, ' minutes.');
end;
Next, a factory function is declared to create an instance of ICoffeeMaker:
{$M+}
TCoffeeMakerFactory = reference to function(const aCoffeeBrand: string; const aBrewingMinutes: integer): ICoffeeMaker;
{$M-}
...and the whole thing is registered in the container:
procedure RegisterStuff(aContainer: TContainer);
begin
aContainer.RegisterType<ICoffeeMaker, TCoffeeMaker>.AsDefault;
aContainer.RegisterFactory<TCoffeeMakerFactory>;
aContainer.Build;
end;
And then the code of the main program is given:
var
CoffeeName: string;
BrewingMinutes: integer;
CoffeeMakerFactory: TCoffeeMakerFactory;
CoffeeMaker: ICoffeeMaker;
begin
Write('What kind of coffee do you want to make? ');
ReadLn(CoffeeName);
Write('How many minutes? ');
ReadLn(BrewingMinutes);
CoffeeMakerFactory := Container.Resolve<TCoffeeMakerFactory>();
CoffeeMaker := CoffeeMakerFactory(CoffeeName, BrewingMinutes);
CoffeeMaker.MakeCoffee;
end;
The author writes that it is not necessary to register TCoffeeMaker in the container. But suddenly we will have some TKitchen class, which will have ICoffeeMaker as a dependency.
The question is:
What if there really is a TKitchen class that implements the IKitchen interface, which in the constructor takes ICOfeeMaker as a parameter? It turns out I will register it in the container:
begin
//...
aContainer.RegisterType<IKitchen, TKitchen>;
//...
end;
...and then create in the main program:
begin
//...
Kitchen := Container.Resolve<IKitchen>();
//...
end;
How, in this case, to choose the desired implementation and configure the created instance of ICoffeeMaker?

To be honest that code example is a kinda bad one.
You don't create new coffeemakers for different brands or brewing times. These are different things. The brand and brewing time should be parameters on the MakeCoffee method. If a coffeemaker has some internal parts like a grinder for the beans or something like that that could be a ctor parameter.
And if you change the code like this your question disappears because you don't need to create new coffeemakers just to brew different brands of coffee with different brewing times.
Of course you could have a different model where these things are indeed passed into the ctor but then you have a coffeemaker with a fixed brand and brewing time and you simple "press the button" to get a coffee and it does it for you.
Now if you have a kitchen that has a coffeemaker you don't create a new kitchen with a new coffeemaker just to get a different brand or brewing time, do you? Yes, I am comparing the real world with software here but it shows that something is not right with this software model.

Related

How to access a private field from a class helper in Delphi 10.1 Berlin?

I would like to use Gabriel Corneanu's jpegex, a class helper for jpeg.TJPEGImage.
Reading this and this I've learned that beyond Delphi Seattle you cannot access private fields anymore like jpegex does (FData in the example below). Poking around with the VMT like David Heffernan proposed is far beyond me. Is there any easier way to get this done?
type
// helper to access TJPEGData fields
TJPEGDataHelper = class helper for TJPEGData
function Data: TCustomMemoryStream; inline;
procedure SetData(D: TCustomMemoryStream);
procedure SetSize(W,H: integer);
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := self.FData;
end;
Today I found a neat way around this bug using the with statement.
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
Besides that Embarcadero did a nice job building walls to protect the private parts and that's probably why they named it 10.1 Berlin.
Beware! This is a nasty hack and can fail when the internal field structure of the hacked class changes.
type
TJPEGDataHack = class(TSharedImage)
FData: TCustomMemoryStream; // must be at the same relative location as in TJPEGData!
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := TJPEGDataHack(self).FData;
end;
This will only work if the parent class of the "hack" class is the same as the parent class of the original class. So, in this case, TJPEGData inherits from TSharedImage and so does the "hack" class. The positions also need to match up so if there was a field before FData in the list then an equivalent field should sit in the "hack" class, even if it's not used.
A full description of how it works can be found here:
Hack #5: Access to private fields
By using a combination of a class helper and RTTI, it is possible to have the same performance as previous Delphi versions using class helpers.
The trick is to resolve the offset of the private field at startup using RTTI, and store that inside the helper as a class var.
type
TBase = class(TObject)
private // Or strict private
FMemberVar: integer;
end;
type
TBaseHelper = class helper for TBase // Can be declared in a different unit
private
class var MemberVarOffset: Integer;
function GetMemberVar: Integer;
procedure SetMemberVar(value: Integer);
public
class constructor Create; // Executed automatically at program start
property MemberVar : Integer read GetMemberVar write SetMemberVar;
end;
class constructor TBaseHelper.Create;
var
ctx: TRTTIContext;
begin
MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;
function TBaseHelper.GetMemberVar: Integer;
begin
Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;
procedure TBaseHelper.SetMemberVar(value: Integer);
begin
PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;
As you can see it requires a bit of extra typing, but compared to patching a whole unit, it is simple enough.

How to design an 'open' list of classes?

I have a class TTask from which I derive many other tasks:
TTask= class(TObject)
TaskName: string;
TaskDescription: string;
end;
TTask1= class(TTask)
TTask2= class(TTask)
TParser= class(TTask)
etc...
(each class implemented in its own unit)
Each child task has its own name and description:
constructor TParser.Create;
begin
inherited;
TaskName := 'Parser';
TaskDescript:= 'Parses a result file';
end;
The GUI:
At runtime I want to populate a listbox list called 'Available tasks' with the name of TTask1, TTask2, etc. I want to let user drag and drop tasks from 'Available' to 'Current tasks' listbox.
When populating the Available listbox at run time, because TTask1, TTask2 are not instantiated, probably it would be smart to use something like class var to obtain TaskName. Otherwise, if I ever change the name of the task, I will have to change the code in two places.
If possible I should be able to easily add more tasks to the 'Available' list box as I finish the code for them.
What is the neatest way of doing such a dynamic design?
What I don't like mostly is this design:
var Task: TTask;
...
if lstAvailTasks.SelectedItem= 'Parser' then
begin
Task:= TTask1.Create;
Tasks.Add(Task)
end
else
etc
You are looking for a registry of task classes. Something along these lines:
type
ETaskClassNotRecognised = class(Exception);
TTask = class
public
constructor Create; virtual; abstract;
class function TaskTypeName: string; virtual; abstract;
end;
TTaskClass = class of TTask;
TTaskClassRegistry = class
private
FRegistry: TDictionary<string, TTaskClass>;
public
constructor Create;
destructor Destroy; override;
procedure RegisterTaskClass(TaskClass: TTaskClass);
function RegisteredTaskClasses: TArray<TTaskClass>;
function CreateTask(const Name: string): TTask;
end;
constructor TTaskClassRegistry.Create;
begin
inherited;
FRegistry := TDictionary<string, TTaskClass>.Create;
end;
destructor TTaskClassRegistry.Destroy;
begin
FRegistry.Free;
inherited;
end;
procedure TTaskClassRegistry.RegisterTaskClass(TaskClass: TTaskClass);
begin
FRegistry.Add(TaskClass.TaskTypeName, TaskClass);
end;
function TTaskClassRegistry.RegisteredTaskClasses: TArray<TTaskClass>;
begin
Result := FRegistry.Values.ToArray;
end;
function TTaskClassRegistry.CreateTask(const Name: string): TTask;
var
TaskClass: TTaskClass;
begin
if not FRegistry.TryGetValue(Name, TaskClass) then begin
raise ETaskClassNotRecognised.CreateFmt(
'No task class named ''%s'' has been registered.',
[Name]
);
end;
Result := TaskClass.Create;
end;
Notes:
I added a virtual constructor to the task class. That's always needed when you use meta classes to instantiate objects.
I added the task type name as a virtual method of the task type. That allows the type name to be centralised and mentioned only once.
The registry is managed by an encapsulated dictionary.
The RegisteredTaskClasses method allows you to obtain a list of registered classes. This would be useful when attempting to populate your UI with the names of registered classes.
You'll want a single instance of the registry.

How to provide a derived instance with an ancestor field declaration?

I am having some trouble figuring out this case of inheritance.
In my class TBalans, I have a routine Initialiseer that takes a TBalPar object as parameter. TBalPar is the ancestor class of TNewBalPar that has additional fields. Now I would like to reach the additional fields from within my TBalans class. I still can feed a TNewBalPar object to the Initialiseer routine, but how do I get to the data of the descendant class?
What I tried is the following: I derived TBalans too into TNieuweBalans, gave it the new additional fields, and assign them in the routine:
type
TBalPar = class
//some vars
end;
TNewBalPar = class(TBalPar)
ExtraVar: TValue;
end;
TBalans = class
MyBalPar: TBalPar;
function Initialiseer(ABalPar: TBalPar): Boolean; virtual;
end;
TNieuweBalans = class(TBalans)
MyBalPar: TNewBalpar; //declared again so I don't need to cast it when using it
MyExtraVar: TValue;
function Initialiseer(ABalPar: TBalPar): Boolean; override;
end;
function TBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
MyBalPar := ABalPar;
end;
function TNieuweBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
inherited;
MyBalPar := TNewBalPar(ABalPar);
MyExtraVar := MyBalPar.ExtraVar; //instead of casting TNewBalPar(MyBalPar).ExtraVar
end;
This code works, but it feels wrong: I declare the MyBalPar field twice. I would like to improve on it.
Note that I am not looking for a way how to expose ExtraVar to the outside world, but how to use it conveniently within TNieuweBalans.
How can I eliminate the double MyBalPar field but still prevent frequent typecasting?
Current design
The need for a convenient designated derived field type for an ancestral field is not forbidden, nor uncommon for that matter. But your implementation, like you sense already, has some problems:
the doubled fields require unnecessary memory,
you need to synchronize changes to TBalans.MyBalPar and TNieuweBalans.MyBalPar,
you need to synchronize changes to TNieuweBalans.MyBalPar.ExtraVar and TNieuweBalans.MyExtraVar,
you do not enforce the derived class type: feeding a TBalPar object to TNieuweBalans.Initialiseer results in an access violation because MyBalPar.ExtraVar does not exist.
There are multiple ways to overcome each of these problems.
The most elementary solution to prevent extra fields is to provide properties for them with getters that extract the values from the inherited class (I renamed some of your types and variables for comprehensibility):
type
TBalPar = class(TObject)
// some variables
end;
TBalParEx = class(TBalPar)
private
FExtra: TValue;
public
property Extra: TValue read FExtra write FExtra;
end;
TBalance = class(TObject)
private
FBalPar: TBalPar;
public
function Initialize(ABalPar: TBalPar): Boolean; virtual;
property BalPar: TBalPar read FBalPar;
end;
TBalanceEx = class(TBalance)
private
function GetExtra: TValue;
procedure SetExtra(Value: TValue);
public
function BalPar: TBalParEx;
function Initialize(ABalPar: TBalPar): Boolean; override;
property Extra: TValue read GetExtra write SetExtra;
end;
function TBalanceEx.BalPar: TBalParEx;
begin
Result := TBalParEx(inherited BalPar);
end;
function TBalanceEx.GetExtra: TValue;
begin
Result := BalPar.Extra;
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
BalPar.Extra := Value;
end;
With this approach, there is only one typecast needed and it does not require additional storage.
To enforce TBalanceEx.BalPar to be of type TBalParEx, you could raise an exception in the Initialize routine:
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := True;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
if ABalPar is TBalParEx then
Result := inherited Initialize(ABalPar)
else
raise Exception.Create('Wrong BalPar type');
end;
Of course, this places the sole responsibility of a correct class functioning on the requirement to always call the Initialize routine before any other usage of the other class members. Since that is what initialization obviously is intended for, you could ignore that, but protection against misuse could be added like:
TBalance = class(TObject)
protected
function HasBalPar: Boolean; virtual;
...
TBalanceEx = class(TBalance)
protected
function HasBalPar: Boolean; override;
...
function TBalance.HasBalPar: Boolean;
begin
Result := FBalPar is TBalPar;
end;
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := HasPalBar;
end;
function TBalanceEx.GetExtra: TValue;
begin
if HasBalPar then
Result := BalPar.Extra
else
Result := nil;
end;
function TBalanceEx.HasBalPar: Boolean;
begin
Result := BalPar is TBalParEx;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
Result := inherited Initialize(ABalPar);
if Result = False then
raise Exception.Create('Initialization went wrong');
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
if HasBalPar then
BalPar.Extra := Value;
end;
In turn, this requires not to forget to implement HasBalPar for each derived class. You could 'protect' against that with:
TBalance = class(TObject)
strict private
function HasBalPar: Boolean;
private
...
TBalanceEx = class(TBalance)
strict private
function HasBalPar: Boolean;
private
...
Design considerations
All in all, making this a robust design requires some work. And your current approach raises the question why you would want to have the Extra field in the TBalanceEx class too. Even why to have a TBalanceEx class at all.
From the naming of your classes, I assume you have the following equivalent: A structure which has structural parameters like build date, owner, location, and you have a specialized structure, say a castle, with additional parameters like the number of towers and whether it has a moat:
TStructureData: Location, BuildDate
TCastleData: Location, BuildDate, TowerCount, HasMoat
TStructure: StructureData
TCastle: StructureData, CastleData
The question you need to answer is whether a structure needs to know if it is a castle, or a palace, a warehouse, a biological or chemical structure. Assume your program evolves to being able to handle all different kinds of structures, then you are always bound to add two classes to your program, resulting in a more and more complex and improvised design which in the end will get you in trouble, if not already. The challenge is to make this a more generalized and abstract design.
For example:
must TStructureData and TStructure be separate classes?
could calculations, analysations, or presentational requests on the specific data be 'outsourced' to the specific class? E.g.: if you add a GetFeatures routine to the TStructureData class, then the TStructure class can request the features of a TCastle without knowing it being a Castle.
...
Think big.

Delphi - access class string property by its value

I have a class defined which contains only strings as properties, and I need to get the property name based on its value as in the example below. In the example there are only 3 properties, in the real life class there are almost 1000. The problem is that this class is heavily used, and I want to know if I can get the property name by its value in a faster way.
unit Unit5;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,RTTI, StdCtrls, Diagnostics;
type
TConstDBElem = class
public
CCFN_1 : String;
CCFN_2 : String;
CCFN_3 : String;
constructor Create;
end;
TForm5 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form5: TForm5;
Obj: TConstDBElem;
implementation
{$R *.dfm}
procedure TForm5.Button1Click(Sender: TObject);
var iPos:Integer;
timer:TStopwatch;
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
result := vrttiField.Name;
end;
end;
begin
timer := TStopwatch.Create;
timer.Start;
Memo1.Lines.Clear;
for iPos := 0 to 100000 do
GetName(Obj,'TEST3');
timer.Stop;
Memo1.Lines.Add(FloatToStr(timer.Elapsed.TotalSeconds));
end;
constructor TConstDBElem.Create;
begin
CCFN_1 := 'TEST1';
CCFN_2 := 'TEST2';
CCFN_3 := 'TEST3' ;
end;
initialization
Obj := TConstDBElem.Create;
finalization
Obj.Free;
end.
Yes,I know this is a very bad design, and this should not be done like this. Is there any option to make this search faster?
When GetName() finds a match, it is not stopping its loop, so it keeps searching for more matches. Assigning a function's Result does not exit the function, like you clearly think it does. As such, GetName() ends up returning the last match, not the first match. The loop should be calling Exit when it finds the first match:
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
result := vrttiField.Name;
Exit; // <-- add this
end;
end;
Alternatively, use the version of Exit() that takes a parameter:
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
Exit(vrttiField.Name); // <-- assigns Result and exits at the same time
end;
end;
In your simple example, the time wasted to search 3 fields is hardly noticeable, but when searching 1000 fields, it makes a difference.
You state in a comment that the values never change at runtime. In which case you can simply build a single dictionary at startup that has the property values as the dictionary key, and the property name as the dictionary value.
I'm assuming that all instances of the class have the same property values. If that's not the case then you'll need one dictionary per instance.
It's a "bad design" because someone wrote a class that they're treating like a C-style Struct. As has been said already, there are NO PROPERTIES defined in the class, just a bunch of PUBLIC DATA MEMBERS, aka, "fields".
There's no encapsulation, so anything you do to change the structure could have far-reaching implications on any unit that uses this. I agree that replacing the IMPLEMENTATION with a TStringList or a TDictionary would be smart, but ... there are no interfaces to adhere to! You have 1000-odd hard-wired references to public data members.
(The last time I saw something like this was some code written by a bunch of VB programmers who wrote classes as if they were C-style structs containing public data members, and then they wrote external functions to access the data, just as you'd do in C. Only they buried business logic inside of the accessor methods as well, which causes random pieces of the code to make direct references to the data members of the class.)
Off-hand, I'd say you're totally mis-using the RTTI code. Sure, the optimizations above will improve performance, but so what? It's the wrong solution!
If you really want to refactor this (and you certainly should!), first I'd look to see how widespread the use of this poor class is by changing the public to private and see how many errors you get.
Then I'd derive it from TStringList, delete all of the local fields, and move the GetName function inside of the class:
type
TConstDBElem = class( TStringList )
public
constructor Create;
function GetName( aName : string ) : string;
end;
Now, if I'm interpreting your example correctly, you want to do this to initialize the object:
constructor TConstDBElem.Create;
begin
Add( 'TEST1=CCFN_1' );
Add( 'TEST2=CCFN_2' );
Add( 'TEST3=CCFN_3' );
end;
Then replace all of the references in other units with a call to obj.GetName():
function TConstDBElem.GetName( aName : string ) : string;
begin
Result := Values[aName];
end;
You're replacing a reference to obj.CCFN_1 (?) or GetName(obj,'TEST1') with obj.GetName('TEST1').
(Maybe I'm totally off-base at this point. Sorry, but I just don't get how you're using this class from the example, and it doesn't make a whole lot of sense to me anyway. It would make more sense if you said what you're mapping between. I mean ... who needs to look up a local field name from a value associated with it? And what do you do with it once you've found it? Whomever wrote this had to go through some incredible contortions to make the code work because s/he sure didn't understand OOP when this was designed!)
At this point, you will have succeeded in decoupling the clients of this class (other units) from its internal implementation, and replacing those references with calls to an interface (a method) defined in the class instead.
Then you can do some testing to see what happens if you change the implementation, like from a TStringList to a TDictionary. But no matter how you slice it, I cannot imagine that either the TStringList or TDictionary will be slower than how you're abusing the RTTI system in your example. :)

How to pass data between forms in Delphi?

It may seem a little newbie, but I really have got problem with it. I have a form (not the main one)for getting many different data from the user and I want to pass it to a manager class for creating an object with these. The problem is that I can't have this class to use the other unit (getting circle uses) and also it doesn't have access to the manager class instance (which is in main form).
So, what shall I do? I've already considered using public variable but I have a bad feeling about it (regarding OOD patterns).
My suggestion is to decouple data from the GUI because this is causing your problem.
If you have a form which gathers data from the user then you should distinguish the data from the form(TForm).
For example, let's assume that you have some instance of TForm and a form, which is built from three fields: username, age and location. You want the user to enter those three things, but when the user closes the form, you should pass this inserted data onto some object. Form closes, it is freed, but the object persist. Then you pass this object to your manager object.
Simple example:
This is your record which will hold the data
type
TGatheredData = record
Name: String[40];
Age: Byte;
Location: String[40];
end;
Your TForm1 might have an aditional constructor:
constructor TForm1.Create(AOwner: TComponent; var GatheredData: TGatheredData );
begin
inherited Create(AOwner);
FGatheredData := GatheredData;
//you may want to deserialize GatheredData here and show the data in your form controls
end;
You call it, pass GatheredData and then your are showing your form.
Next, when closing form, you pick upd the data from the form controls.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Self.ModalResult = mrOk then
begin
//serialize your object
FGatheredData.Name := '';//name taken from control f.e. TEdit
FGatheredData.Age := '';//name taken from control f.e. TSpinButton
FGatheredData.Location := '';//name taken from control f.e. TEdit
end;
end;
Having this record of data, you may now pass it in the same manner to your Manager object.
You decoupled data from GUI in this way, and you may easly plugin in your record to a number of different forms.
Just remember to declare your record type in external unit and use that unit in your manager unit and forms unit.
Hope this helps a little.
The "manager class" shouldn't be in either form's unit, but in its own. By separating GUI code from bussiness logic you avoid problems such like this.
[Edit: I originally put this answer in a comment, but decided to move it out into full answer. TDatamodules are too important and too common in Delphi not to emphasize them and they provide built-in easy-to-use means of seperating gui from logic and data.]
Other people have given good comments about decoupling gui from the logic and data. Surprisingly, I don't think anybody has mentioned that in Delphi TDatamodules are one main means of doing this. You put your data and logic on the Datamodule, then have both forms "use" the Datamodule to get access to its data and methods. Here is brief intro: http://delphi.about.com/od/database/l/aa101601a.htm
Both of your forms (and other forms) can access datasets or other data/datastructures that are located on/in a Datamodule unit. There should be an easy to find sample project out there illustrating the setup, since it is (or at least was) the standard way to construct Delphi apps.
If I understand your question properly then you want the manager to manage the forms (not the forms to access the manger). Right? You can't close the Main Form as if you do you close the application but you CAN Hide it. (unless you create a console app). But it poses a nice little problem :)
So... Splash form (Main Form) is:
.
.
.
uses
ManagerU;
type
TFormSplash = class(TForm)
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Manager: TManager;
end;
var
FormSplash: TFormSplash;
implementation
{$R *.dfm}
procedure TFormSplash.FormCreate(Sender: TObject);
begin
Manager := TManager.Create;
end;
procedure TFormSplash.FormDestroy(Sender: TObject);
begin
Manager.Free;
end;
procedure TFormSplash.FormPaint(Sender: TObject);
begin
if Visible then
begin
Manager.GetData(Self);
Hide;
Manager.DoDataStuff;
Close;
end;
end;
end.
DaaObject is:
unit DataObjectU;
interface
uses classes;
type TDataObject = class(TObject)
Data: string;
end;
implementation
end.
Manager is:
interface
uses DataObjectU;
type
TManager = Class(Tobject)
MyData: TDataObject;
constructor Create; virtual;
destructor Destroy; override;
procedure GetData(OwnerForm: TForm);
procedure DoDataStuff;
end;
implementation
uses DataFormU;
{ TManager }
constructor TManager.Create;
begin
inherited Create;
MyData := TDataObject.Create;
end;
destructor TManager.Destroy;
begin
MyData.Free;
inherited;
end;
procedure TManager.DoDataStuff;
begin
// do stuff with data here
end;
procedure TManager.GetData(OwnerForm: TForm);
var
MyDataForm: TDataForm;
begin
MyDataForm := TDataForm.Create(OwnerForm);
try
if MyDataForm.ShowModal = mrOK then
begin
MyData.Data := MyDataForm.Data;
end;
finally
MyDataForm.Free;
end;
end;
end.
The Dataform is:
type
TDataForm = class(TForm)
btnOK: TButton;
procedure btnOKClick(Sender: TObject);
private
function GetData: String;
{ Private declarations }
public
{ Public declarations }
MyData: TDataObject;
property Data: String read GetData;
end;
var
DataForm: TDataForm;
implementation
{$R *.dfm}
procedure TDataForm.btnOKClick(Sender: TObject);
begin
MyData := TDataObject.Create;
ModalResult := mrOk;
end;
function TDataForm.GetData: String;
begin
Result := MyData.Data;
end;
You will need to fill in the rest of the unit code and free some stuff but essentially this:
Start Program (Creates Splash)
Splash Creates the manager calls it to get data from the dataform then hides itself
calls manager to manage the data
when manager is done it then closes.
There is no other way to shut it down now except through task manager!)
Tim
To solve circular refrence error, you use that unit in implementation section.
implementation
{$R *.DFM}
Uses <Your Unit>;
end.
Having this 3 units:
FormMain
FormEdit
UnitMyClass
You create your object in FormMain and pass it to the FormEdit in a function like:
class function FormEdit.EditMyObject(AObject: TMyClass): boolean;
this function will showModal the form. The form will do the changes to the object, and finally return True if user pressed OK.
As you can see in UnitMyClass there is no reference to FormMain or FormEdit
FWIW, I did a whole presentation on this topic in a CodeRage 9 video. It can be seen here:
https://youtu.be/qqKx8fQTTfI

Resources