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.
Related
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.
I create my own class and I want to use it in my new component but I am getting an error...
The code is the following:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(aName: string; aNumber: double);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure SetMyClass(aName: string; aNumber: double);
begin
FMyClass.Name:= aName;
FMyClass.Number:= aNumber;
end;
it appears that the property has incompatible types, I don't know why.
Does anybody has a clue about that and how can I solve this problem.
Having a FName and FNumber as fields in TMyComponent is not an option, my code is more complex and this is a simple example to explain my goal.
thanks
The things that I can see wrong with your code at present are:
The property setter must receive a single parameter of the same type as the property, namely TMyClass.
The property setter must be a member of the class, but you've implemented it as a standalone procedure.
A published property needs to have a getter.
So the code would become:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
This code does not instantiate FMyClass. I'm guessing that the code that does instantiate FMyClass is part of the larger component code that has been excised for the sake of this question. But obviously you do need to instantiate FMyClass.
An alternative to instantiating FMyClass is to turn TMyClass into a record. Whether or not that would suit your needs I cannot tell.
It looks like you are having some problems instantiating this object. Do it like this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FMyClass:= TMyClass.Create;
end;
destructor TMyComponent.Destroy;
begin
FMyClass.Free;
inherited;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
One final comment. Using MyClass for an object is a bad name. Use class for the type, and object for the instance. So, your property should be MyObject and the member field should be FMyObject etc.
Try this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value);
begin
FMyClass := Value;
end;
unit MyComponentTest2;
interface
uses SysUtils, Classes, Controls, Forms, ExtCtrls, Messages, Dialogs;
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponentTest2 = class(TCustomPanel)
private
FMyClass: TMyClass;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure Register;
implementation
constructor TMyComponentTest2.Create(AOwner: TComponent);
begin
Inherited Create(AOwner);
FMyClass:= TMyClass.Create;
end;
destructor TMyComponentTest2.Destroy;
begin
Inherited;
FMyClass.Free;
end;
procedure TMyComponentTest2.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TMyComponentTest2]);
end;
end.
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
I have a TForm as this:
TMyForm = class (TForm)
[MyAttr('Data')]
edit1: TEdit;
private
FData: String;
end
When I try to get the fields of this form via RTTI, I only get the edit1 field, not FDATA, and when I query for edit1 field attributes a get a empty array.
For anoteher class that not inherit TForm, all work ok. ¿why?
edit for sample
type
{$RTTI EXPLICIT FIELDS([vcPrivate,vcProtected, vcPublic])}
TForm3 = class(TForm)
[TCustomAttribute]
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
[TCustomAttribute]
FData: String;
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
var
LCtx: TRttiContext;
LField: TRttiField;
LAttr: TCustomAttribute;
begin
for LField in LCtx.GetType(Self.ClassInfo).GetDeclaredFields do
begin
Memo1.Lines.Add(lField.Name);
for LAttr in LField.GetAttributes do
Memo1.Lines.Add(#9+LAttr.ClassName);
end;
end;
end.
result where button1 is clicked:
FData
TCustomAttribute
Button1
Memo1
There could be a $RTTI directive somewhere in your project that's causing extended RTTI to not work.
Try adding the following just before the declaration of TMyForm:
{$RTTI EXPLICIT
METHODS(DefaultMethodRttiVisibility)
FIELDS(DefaultFieldRttiVisibility)
PROPERTIES(DefaultPropertyRttiVisibility)}
This will reset RTTI generation for everything declared after it, and you should get proper RTTI after that point.
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.