Passing classes to interfaces - delphi

I have tested my own class with the dependency injection and now I have to implement it into production. The following is an excerpt of my class and relevant interface:
ITableDB = interface
['{171DE959-8604-4CD3-ACEA-ACCE15E95621}']
procedure Close;
procedure Open;
...
end;
TNewStrategy=class(TObject)
private
FTableDB: ITableDB
.....
public
constructor Create (ATableDB: ITableDB....)
end;
Instead of mocks and stubs I have to provide the class the real objects now. These are a number of third part components I have placed in a form at design time. Here one example:
type
TForm1 = class(TForm)
ThirdyPartDBTable1: ThirdyPartDBTable;
NewStrategy: TNewStrategy;
private
{ Private declarations }
public
{ Public declarations }
end;
How can I pass ThirdyPartDBTable1 to TNewStrategy.Create ? I tried the following code:
TMyThirdyPartDBTable = class(ThirdyPartDBTable, IITableDB)
public
procedure Close;
procedure Open;
...
end;
But when I try to change ThirdyPartDBTable1: ThirdyPartDBTable into ThirdyPartDBTable1: TMYhirdyPartDBTable; the compiler changes the reference TMYhirdyPartDBTable back to ThirdyPartDBTable.

In the code you showed, TNewStrategy is not derived from TComponent, so it cannot be placed on a TForm at design-time. You would have to create it at run-time, in which case you have access to its constructor and can pass ThirdyPartDBTable1 to it, eg:
procedure TForm1.FormCreate(Sender: TObject);
begin
NewStrategy := TNewStrategy.Create(ThirdyPartDBTable1);
end;
However, if TNewStrategy were a TComponent descendant available at design-time, you could link ThirdyPartDBTable1 to NewStrategy at design-time if you change TNewStrategy to expose an ITableDB property instead of passing it in the constructor, eg:
TNewStrategy = class(TComponent)
private
FTableDB: ITableDB
.....
public
constructor Create(AOwner: TComponent); override;
published
property TableDB: ITableDB read FTableDB write FTableDB;
end;
As long as ThirdyPartDBTable implements ITableDB then the Object Inspector and DFM streaming will allow it.
Update: since ThirdPartyDBTable does not implement ITableDB, you can use an interceptor class to implement it, eg:
interface
uses
..., ThirdPartyUnit;
type
ThirdyPartDBTable = class(ThirdPartyUnit.ThirdyPartDBTable, ITableDB)
public
procedure Close;
procedure Open;
end;
TForm1 = class(TForm)
ThirdyPartDBTable1: ThirdyPartDBTable;
NewStrategy: TNewStrategy;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
procedure ThirdyPartDBTable.Close;
begin
...
end;
procedure ThirdyPartDBTable.Open;
begin
...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
NewStrategy := TNewStrategy.Create(ThirdyPartDBTable1 as ITableDB);
end;
end.

You cannot change the class of a component that you've put at design time by modifying it in the form declaration, the IDE owns the declarations in the upper public part of the form.
You can create your derived component at run time instead, or install it in a run time package and register with the component library. For a single time job, or for testing purposes, you can use an interposer class. In the below example I used a TPanel since I don't have any ThirdyPartDBTable, so be sure to put a panel on the test form. Also omitted the 'Close' method for brevity.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
ITableDB = interface
['{171DE959-8604-4CD3-ACEA-ACCE15E95621}']
procedure Open;
end;
TPanel = class(extctrls.TPanel, ITableDB)
public
procedure Open;
end;
TNewStrategy=class(TObject)
private
FTableDB: ITableDB;
public
constructor Create(ATableDB: ITableDB);
end;
TForm1 = class(TForm)
Button1: TButton;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
private
NewStrategy: TNewStrategy;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TPanel }
procedure TPanel.Open;
begin
ShowMessage('Open what?');
end;
{ TNewStrategy }
constructor TNewStrategy.Create(ATableDB: ITableDB);
begin
FTableDB := ATableDB;
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
NewStrategy := TNewStrategy.Create(Panel1 as ITableDB);
NewStrategy.FTableDB.Open;
end;
end.

Related

Only last added Delphi component does action

I created my own component (it like a button which can move) in Delphi, installed it. Then I create a new project and new from there and added few new my component elements. But only last one added is able to move! Others not. Why does it happen? How could I fix it?
Here's the component code:
unit ModifiedButton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
var Timer: TTimer;
type
TSpeed = (Slow,Normal,Fast);
TModifiedButton = class(TButton)
private
{ Private declarations }
FCount:integer;
Velocity:integer;
FSpeed:TSpeed;
protected
{ Protected declarations }
procedure Click;override;
procedure Move(Vel:Integer);
procedure OnTimer(Sender: TObject);
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;
property Speed: TSpeed read FSpeed write FSpeed;
constructor Create(aowner:Tcomponent); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TModifiedButton]);
end;
{ TModifiedButton }
procedure TModifiedButton.Click;
begin
inherited Click;
FCount:=FCount+1;
if (Speed = Slow) then
Velocity:=2;
if (Speed = Normal) then
Velocity:=10;
if (Speed = Fast) then
Velocity:= 20;
Timer.Enabled:=True;
end;
constructor TModifiedButton.Create(aowner: Tcomponent);
begin
inherited Create(aowner);
Timer:=TTimer.Create(self);
Timer.Enabled:=false;
Timer.OnTimer:=OnTimer;
Timer.Interval:=10;
end;
procedure TModifiedButton.Move(Vel: Integer);
begin
Left:=Left + Vel;
end;
procedure TModifiedButton.OnTimer(Sender: TObject);
begin
Move(Velocity);
end;
procedure TModifiedButton.ShowCount;
begin
ShowMessage('You cliked '+ caption+' for '+inttostr(FCount)+' times');
end;
end
.
Since the Timer is a global variable, each new button you create will overwrite the OnTimer event handler of the previous button. Solution, make the Timer a member of your TModifiedButton class:
unit ModifiedButton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TSpeed = (Slow,Normal,Fast);
TModifiedButton = class(TButton)
private
{ Private declarations }
FCount:integer;
Velocity:integer;
FSpeed:TSpeed;
Timer: TTimer;
protected
{ Protected declarations }
procedure Click;override;
procedure Move(Vel:Integer);
procedure OnTimer(Sender: TObject);
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;
property Speed: TSpeed read FSpeed write FSpeed;
constructor Create(aowner:Tcomponent); override;
destructor Destroy; override;
end;
procedure Register;
implementation
{ TModifiedButton }
procedure TModifiedButton.Click;
begin
inherited Click;
FCount:=FCount+1;
Case Speed of
Slow : Velocity:=2;
Normal : Velocity:=10;
Fast : Velocity:= 20;
end;
Timer.Enabled:=True;
end;
procedure TModifiedButton.Move(Vel: Integer);
begin
Left:=Left + Vel;
end;
procedure TModifiedButton.OnTimer(Sender: TObject);
begin
Move(Velocity);
end;
procedure TModifiedButton.ShowCount;
begin
ShowMessage(Format('You clicked %s for %d times', [Caption, FCount]));
end;
constructor TModifiedButton.Create(AOwner: Tcomponent);
begin
inherited Create(AOwner);
Timer := TTimer.Create(self);
Timer.Enabled:=false;
Timer.OnTimer:=OnTimer;
Timer.Interval:=10;
end;
destructor Destroy;
begin
Timer.Enabled := False;
Timer.Free;
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TModifiedButton]);
end;
end.

Delphi 6 create new form with constructor

I am new to Delphi and have a problem with creating a new form dynamically. I want to create the new form with the elements properties from the gui I made. Here is the form I want to dynamically create :
unit AddEmployeeF;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TAddEmployee = class(TForm)
GroupBox1: TGroupBox;
AddName: TLabel;
AddDept: TLabel;
AddPhone: TLabel;
AddExtension: TLabel;
AddDetails: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
procedure CancelButtonClick(Sender: TObject);
private
{ Private declarations }
public
constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
end;
var
AddEmployee: TAddEmployee;
implementation
{$R *.dfm}
constructor TAddEmployee.CreateNew(AOwner: TComponent; Dummy: Integer = 0; Detail : String);
begin
inherited Create(AOwner);
AddDetails.Caption := Detail;
end;
procedure TAddEmployee.CancelButtonClick(Sender: TObject);
begin
self.Close;
end;
end.
I dont want to create all the gui elements again in the constructor, just to modificate some properties of the elements, like caption but keep the positions and other properties from the gui definition. It's possible? And how to create the form from another form, like this? :
procedure TWelcome.SpeedButton1Click(Sender: TObject);
var
myForm :TAddEmployee;
begin
myForm := TAddEmployee.CreateNew(AOwner, Dummy, Details);
myForm.ShowModal;
end;
You overrode the wrong constructor. The TForm.CreateNew() constructor bypasses DFM streaming, so all of your design-time components will not be created at run-time. Worse, your overridden CreateNew() constructor is calling the inherited TForm.Create() constructor, which calls CreateNew() internally, thus you will get stuck in an endless loop that causes an stack overflow error at runtime.
To do what you are asking for, override the TForm.Create() constructor instead, or define a whole new constructor that calls TForm.Create() internally. Do not involve TForm.CreateNew() at all.
type
TAddEmployee = class(TForm)
...
public
constructor Create(AOwner: TComponent); override; // optional
constructor CreateWithDetail(AOwner: TComponent; Detail : String);
end;
constructor TAddEmployee.Create(AOwner: TComponent);
begin
CreateWithDetail(AOwner, 'Some Default Value Here');
end;
constructor TAddEmployee.CreateWithDetail(AOwner: TComponent; Detail : String);
begin
inherited Create(AOwner);
AddDetails.Caption := Detail;
end;
procedure TWelcome.SpeedButton1Click(Sender: TObject);
var
myForm : TAddEmployee;
begin
myForm := TAddEmployee.CreateWithDetail(AOwner, Details);
myForm.ShowModal;
myForm.Free;
end;
Declare your constructor like this:
constructor Create(AOwner: TComponent; const Detail: string); reintroduce;
Implement it like this:
constructor TAddEmployee.Create(AOwner: TComponent; const Detail: string);
begin
inherited Create(AOwner);
AddDetails.Caption := Detail;
end;
Call it like this:
myForm := TAddEmployee.Create(MainForm, Details);
I'm not sure what you want to pass as the owner. Could be the main form, could be something else.
You should also remove the global variable named AddEmployee and so force yourself to take control over instantiating the form.
I chose to name my constructor Create, and so hide the inherited constructor of that name, to force consumers of the class to supply the Details parameter in order to make an instance of the class.

Releasing interface with inner interfaces issue

I have a code here:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
IInnerTest = interface (IInterface)
procedure DoSth;
end;
TRekScannerData = record
Source: Integer;
Device: IInnerTest;
end;
ITest = interface (IInterface)
procedure DoSth;
end;
ATest = class(TInterfacedObject, ITest)
private
FInner: Array of TRekScannerData;
public
procedure DoSth;
constructor Create();
Destructor Destroy();override;
end;
AInnerTest = class (TInterfacedObject, IInnerTest)
private
FMainInt: ITest;
public
constructor Create(MainInt: ITest);
procedure DoSth;
Destructor Destroy();override;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
test: ITest;
implementation
{$R *.dfm}
{ ATest }
constructor ATest.Create;
begin
SetLength(FInner, 1);
FInner[0].Device := AInnerTest.Create(self);
//<----- Here is the reason. Passing main interface to the inner interface.
end;
destructor ATest.Destroy;
begin
beep;
inherited;
end;
procedure ATest.DoSth;
begin
//
end;
{ AInnerTest }
constructor AInnerTest.Create(MainInt: ITest);
begin
FMainInt := MainInt;
end;
destructor AInnerTest.Destroy;
begin
beep;
inherited;
end;
procedure AInnerTest.DoSth;
begin
//
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
test := ATest.Create;
test.DoSth;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
test := nil;
end;
end.
The problem is that Destroy is not called when test is assigned to nil;
I would like to release all the inner interfaces by one statement ...
Is it possible? or do I need to prior to nil destroy all inner structures by using another method?
EDIT
The class structure is as follows:
Var x = ITest(ATest class) has ->
Inner Interface: IInnerTest(AInnerTest class) which has reference to:
ITest(ATest class)
Nil'ing x doesn't release all structure ...
You have a circular reference. Your implementation of IInnerTest holds a reference to ITest. And your implementation of ITest holds a reference to IInnerTest. And this circular reference means that the interface reference count can never go to zero.
The normal solution to this issue to to use a weak reference. Some useful links:
"Weak reference": down to earth explanation needed
http://www.finalbuilder.com/Resources/Blogs/PostId/410/WeakRefence-in-Delphi-solving-circular-interfac.aspx
http://delphisorcery.blogspot.co.uk/2012/06/weak-interface-references.html

Error passing a generic type from a class to another

I am using Delphi 2010
I get the error: E2506 Method of parameterized type declared in interface section must not use local symbol.
Is there a way to accomplish this task?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Rtti;
type
MyFormType<T: TForm> = class
class procedure SpecialOpen(var FormVar: T; Params: array of TValue);
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure ShowForm<T1: TForm>(var aForm: T1);
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.ShowForm<T1>(var aForm: T1);
begin
if aForm = nil then
MyFormType<T1>.SpecialOpen(aForm, [Self]) // <-- Error
else
aForm.Show;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowForm<TForm2>(Form2)
end;
{ MyFormType<T> }
class procedure MyFormType<T>.SpecialOpen(var FormVar: T; Params: array of TValue);
var lRttiContext: TRttiContext;
begin
FormVar := lRttiContext.GetType(TClass(T)).GetMethod('Create').Invoke(TClass(T), Params).AsType<T>;
FormVar.Show;
end;
end.
Tanks and sorry for my english.
This is one of a great many generics bugs in Delphi 2010. Your code compiles in XE2. Your options are to look for a workaround that works in 2010, or to upgrade. Delphi XE and XE2 do include a great many fixes for generics compiler bugs and so if you are serious about making use of generics, Delphi 2010 is not a great choice.

Delphi throbber

What is the best solution to show that the application is doing something?
I tried showing a progress indicator, but it did not work.
UPDATE: -------------
A progress bar works fine, but isn't what I want.
I want to show a throbber, like what Web browsers use, so as long as something is being updated it keeps turning.
Cursor can also be in crHourGlass mode.
Try this:
AnimateUnit
unit AnimateUnit;
interface
uses
Windows, Classes;
type
TFrameProc = procedure(const theFrame: ShortInt) of object;
TFrameThread = class(TThread)
private
{ Private declarations }
FFrameProc: TFrameProc;
FFrameValue: ShortInt;
procedure SynchedFrame();
protected
{ Protected declarations }
procedure Frame(const theFrame: ShortInt); virtual;
public
{ Public declarations }
constructor Create(theFrameProc: TFrameProc; CreateSuspended: Boolean = False); reintroduce; virtual;
end;
TAnimateThread = class(TFrameThread)
private
{ Private declarations }
protected
{ Protected declarations }
procedure Execute(); override;
public
{ Public declarations }
end;
var
AnimateThread: TAnimateThread;
implementation
{ TFrameThread }
constructor TFrameThread.Create(theFrameProc: TFrameProc; CreateSuspended: Boolean = False);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
FFrameProc := theFrameProc;
end;
procedure TFrameThread.SynchedFrame();
begin
if Assigned(FFrameProc) then FFrameProc(FFrameValue);
end;
procedure TFrameThread.Frame(const theFrame: ShortInt);
begin
FFrameValue := theFrame;
try
Sleep(0);
finally
Synchronize(SynchedFrame);
end;
end;
{ TAnimateThread }
procedure TAnimateThread.Execute();
var
I: ShortInt;
begin
while (not Self.Terminated) do
begin
Frame(0);
for I := 1 to 8 do
begin
if (not Self.Terminated) then
begin
Sleep(120);
Frame(I);
end;
end;
Frame(0);
end;
end;
end.
Unit1
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ImgList;
type
TForm1 = class(TForm)
ImageList1: TImageList;
Image1: TImage;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure UpdateFrame(const theFrame: ShortInt);
end;
var
Form1: TForm1;
implementation
uses
AnimateUnit;
{$R *.DFM}
procedure TForm1.UpdateFrame(const theFrame: ShortInt);
begin
Image1.Picture.Bitmap.Handle := 0;
try
ImageList1.GetBitmap(theFrame, Image1.Picture.Bitmap);
finally
Image1.Update();
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
AnimateThread := TAnimateThread.Create(UpdateFrame);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
AnimateThread.Terminate();
end;
end.
The Images
You are probably running your time consuming task in the main thread.
One option is to move it to a background thread which will allow your message queue to be serviced. You need it to be serviced in order for your progress bar, and indeed any UI, to work.
Answer to the updated question:
generate an animated gif e.g. here
add a GIF library to your environment (JEDI JVCL+JCL)
insert a TImage and load the generated gif
make it visible if you need it
A indicator is OK. You have to call Application.ProcessMessages after changing it.
"What is the best solution to show that that application is doing something?" - set mouse cursor to crHourGlass? or to create another form/frame/etc which attentions the user that the application is 'doing' something, and he needs to wait.
From your lengthy task, you can occasionally update a visual indicator, like a progress bar or anything else. However, you need to redraw the changes immediately by calling Update on the control that provides the feedback.
Don't use Application.ProcessMessages as this will introduce possible reentrancy issues.

Resources