Is there a way to create forms dynamically by only their names;
The concept goes like this. I have a main form, and by some user selection, a number of predefined forms must be created and docked on tabitems on a pagecontols on the main form.
I do know the names of the forms and i do know when to create each one of those, but i would like to know if a better way of creating these forms by a single procedure call, and not having all of these information in my code.
Its Delphi XE3 firemonkey, on win 7.
Thanks in advance for any help
Apparently on Firemonkey Delphi doesn't automatically register form classes to be available by name, so you'll first need to add something like this to the end of the unit that holds your form class:
unit Form10;
[ ... ]
// Right before the final "end."
initialization
RegisterFmxClasses([TForm10]);
end.
This will automatically register TForm10 so it'll be available by name. Next you can use this kind of code to create a form at Runtime by it's class name:
procedure TForm10.Button1Click(Sender: TObject);
var ObjClass: TFmxObjectClass;
NewForm: TCustomForm;
begin
ObjClass := TFmxObjectClass(GetClass(ClassName));
if ObjClass <> nil then
begin
NewForm := ObjClass.Create(Self) as TCustomForm;
if Assigned(NewForm) then
NewForm.Show;
end
end;
You can only create objects when you have a class reference for it. To get a class reference for something given its string name, call FindClass. Call the constructor on the result. You may have to type-cast the result to a different metaclass before the compiler will allow you to access the constructor you want. In the VCL, you might use TFormClass, but plain old TComponentClass will work, too, since all FireMonkey objects are descendants of TComponent; the important part is that you have access to the right constructor, and that's where the one you need is introduced.
It only works for classes that have been registered. Your form classes should be registered by Delphi automatically, but if they're not, you can call RegisterClasses manually, or RegisterFmxClasses if you need to put your classes in groups.
Delphi.About.com has a VCL demonstration.
Related
In TApplication.CreateForm (not sure if it is allowed to paste the code here. I will do so if somemone confirms) it seems a way of creating an instance of a TForm descendant by using the class of the derived form and a variable pointing to the form. Both are parameters of CreateForm;
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
Is there a better or even simpler way (maybe with less code) of doing what is done in CreateForm if I wanted to have a method which creates a derived control with only some parameters as indicators of what class it is and the variable it will be using.
EDIT: I would like to have a method that creates a control which I use in my project. The method will also do some additional code related to the control so that is the reason for the method. I do not want to duplicate that additional work and the method will be called numerous times. I can implement the code in the same way as CreateForm but was wondering if there is a way of doing the same in less or simpler code.
I want to have a method which creates a derived control with only some parameters as indicators of what class it is and the variable it will be using.
You don't need a method for that. You can write it like this:
MyForm := TMyForm.Create(Owner);
Don't be put off by all the code in Application.CreateForm. That code performs many tasks, the principle of which is to assign the Application.MainForm variable. The IDE likes to encourage you to use Application.CreateForm but in reality you only need to call it once, and that is to create the main form.
If you are dead set on making this into a method then it would look like this:
function CreateForm(FormClass: TFormClass; Owner: TComponent): TForm;
begin
Result := FormClass.Create(Owner);
end;
When calling this function you would need to cast the value returned:
MyForm := TMyForm(CreateForm(TMyForm, Owner));
or
MyForm := CreateForm(TMyForm, Owner) as TMyForm;
As an alternative you could use a generic method:
type
TFormCreator = class
public
class function CreateForm<T: TForm>(Owner: TComponent): T; static;
end;
Implement it like this:
class function TFormCreator.CreateForm<T>(Owner: TComponent): T;
begin
Result := T(TFormClass(T).Create(Owner));
end;
Call it like this:
MyForm := TFormCreator.CreateForm<TMyForm>(Owner);
Pretty ridiculous isn't it? All you want to do is instantiate a form! So, I have a strong suspicion that you have been confused by the IDE's use of Application.CreateForm and believe that there is more to instantiating forms than there really is. My instincts tell me that you are actually looking for this:
MyForm := TMyForm.Create(Owner);
AS. You can post YOUR OWN code, but with regards to code which copyrights holds someone else - that gets a bit complicated. I believe it falls under USA "Fair Use" doctrine. For example you can post a snippet of VCL source to criticize or demonstrate something, but not to copy-paste it into another project and only as little of the VCL code - as required for that "fair use" intention.
A VCL form is a component, thus it needs an owner, who would be responsible for memory management. So you can create the form in a typical TComponent creation pattern.
MyFormVariable := TMyFormClass.Create( Application );
This also adds for type safety that untyped var Reference in CreateForm denies.
Whether that way is better or worse than using Application.CreateForm is up to your tastes. Personally I prefer uniform way so when I need to create a form or a datamodule explicitly I usually go the "component" way not the "application" way.
I guess (just guess) that back in old days TApplication.CreateForm added some extra required setup that "component way" of creating forms could not do, at least not before VCL would get started by Application.Run call. Or maybe there were types of TApplication - and AFAIR there are 5 ones for different projects - that were not derived from TComponent? But anyway I think today that limitations - if ever there where any - not apply any more.
I am trying to pass a DataModule to a form in the form's constructor. I also want the form to be the "Owner" of the DataModule so that the form will destroy the DataModule when it is closed. That creates the problem of both objects needing each other in their constructors.
I tried to set the owner of the DataModule after creation but that is a read only property.
My second form looks like this:
type
TSecondPopup = class(TForm)
private
FMyData: TMyData;
public
constructor Create(MyData: TMyData); reintroduce;
end;
var
SecondPopup: TSecondPopup;
implementation
{$R *.dfm}
constructor TSecondPopup.Create(MyData: TMyData);
begin
FMyData := MyData;
inherited Create(nil);
end;
There is no special code in my data module.
In my main form I want to do something like this when showing the second form:
procedure TMainApp.Button1Click(Sender: TObject);
var
MyData: TMyData;
SecondPopup: TSecondPopup;
begin
MyData := TMyData.Create(nil);
SecondPopup := TSecondPopup.Create(MyData);
// Can't change owner now. It is a read only property.
// MyData.Owner := SecondPopup;
SecondPopup.Show;
end;
I know I can change the DataModule to be a property on the form. Then I could create the form first, then create the data module setting the owner, and finally set the property on the form. I am trying to use constructor dependency injection on this project. It had been working great when I had a shared data module that the main form passed to multiple forms. In that case the main form holds on to the data module until it exists. In this case there is only one form that needs this data module so I wanted to force it to manage the life of the data module by setting the owner.
Another option would be to explicitly free the DataModule when I close the second form. However that form has no way of knowing if the caller also passed the datamodule to a different form.
Is there a way to use the constructor to inject my object but still get the form to manage the lifetime?
Currently using Delphi XE3.
Changing ownership of a component is possible with NewOwner.InsertComponent(TheComponent). And since a component can only be owned by one component at a time, the RTL takes care of removing ownership from the previous owner automatically.
But...
Another option would be to explicitly free the DataModule when I close the second form. However that form has no way of knowing if the caller also passed the datamodule to a different form.
If you want the possibility to pass a single DataModule to multiple Forms, then changing ownership of the DataModule is not the solution: the one Form that owns the DataModule will not be able to deside whether it may free the DataModule. Thus the conclusion is that the DataModule cannot be owned by any form, except by the MainForm. (Then I would prefer to let it own by the Application object, but that's a matter of taste.)
Subsequently, you will then need a reference counting mechanism within the DataModule for the Forms which it is attached to.
You don't have to change the DataModule's Owner. You can simply destroy the DataModule whenever you want, even if it has an Owner assigned. When the DataModule is freed, it will simply remove itself from its Owner so it is not freed a second time. If you go this approach, you should also add a call to FreeNotification() so that you are notified if the DataModule gets freed (by its Owner or anyone else) while your form is still referencing it:
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
constructor TSecondPopup.Create(MyData: TMyData);
begin
inherited Create(nil);
FMyData := MyData;
if FMyData <> nil then
FMyData.FreeNotification(Self);
end;
destructor TSecondPopup.Destroy;
begin
if FMyData <> nil then
FMyData.RemoveFreeNotification(Self);
inherited Destroy;
end;
procedure TSecondPopup.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FMyData) then
FMyData := nil;
end;
If you absolutely want to change the DataModule's Owner, that is also doable, via the TComponent.RemoveComponent() and TComponent.InsertComponent() methods:
constructor TSecondPopup.Create(MyData: TMyData);
begin
inherited Create(nil);
FMyData := MyData;
if FMyData <> nil then
begin
// InsertComponent() will call RemoveComponent() internally for you...
Self.InsertComponent(FMyData);
end;
end;
You want to let the form own the data module. Since ownership is most naturally specified at construction time, then the conclusion is that the form should be created first.
So, instead of passing the data module to the form's constructor, pass something that allows the form to invoke the instantiation of the data module. For instance, you could pass a function to the form's constructor that accepts the form as a parameter and returns the newly minted data module. For instance.
type
TCreateDataModule = function(Owner: TMyForm): TMyDataModule of object;
You could perfectly well create the data module outside the form but with no owner. That pass the object to the form constructor. The form can then destroy the data module in its destructor. That sounds the cleanest solution to me. I find it hard to see past this option.
I think you considered this option already but rejected it with this reasoning:
However that form has no way of knowing if the caller also passed the data module to a different form.
If that is so, then nothing can save you. You cannot have two objects both in charge of the lifetime, unless they use reference counting or similar.
Why create a Data Module that is meant to be used by a form, prior to creating the form?
1) Why not just add the Data Module unit to the interface uses list of the form, declare a private variable of the Data Module type in the form, and create the form's Data Module variable in the OnCreate event of the form...and then you can FreeAndNil the Data Module in the OnDestroy event.
Additionally, you can further declare a public property of the form's Data Module variable, with which you can access the Data Module from the calling unit (i.e. TestForm.DataModule)
2) If you are thinking you must create the Data Module outside of the form, perhaps to do a great number of Data Module-involved initializations, processes, etc. first, and then passing the Data Module off to the form and forgetting about it ... and assuming this form you are creating, that will be utilizing the Data Module, will be a NON-modal form (that bit of information would be of great help), you could always do 1) {above} first, then access the 'TestForm.DataModule' to apply all of your initializations, processes, etc. to before calling the form's Show method.
Is there any way to allow one form to use the event procedures from another form?
E.g. I have a form called PongForm and another called ObstPongForm. There is a ticker on PongForm and another one on ObstPongForm. Is it possible to get ObstPongForm to use the code from PongForm's 'tick' event in it's own 'tick' event? Maybe by letting ObstPongForm inherit from PongForm?
You can simply assign it by code (as long as you have access to both instances):
ObstPongForm.Ticker.OnTick := PongForm.TickerTick;
Yes, forms are just classes like any other, and Delphi supports visual inheritance, so you can call inherited methods normally.
If ObstPongForm is a specialized version of PongForm then inheritance makes sense, but be careful as ObstPongForm will inherit all visual controls from the PongForm, including whatever you may add in the future.
Also since I assume you already have both forms, making one inherit from another is doable but requires some manual DFM editing, mainly changing the
Object ObstPongForm: TObstPongForm
to
Inherited ObstPongForm: TObstPongForm
If the code you want to reuse may be needed in several unrelated forms, then moving the code to a common unit used by these forms may be the best solution
It would be better style to have both of the forms call another class that implements the logic used by both. If you're writing all your program logic in your OnTimer event handler, you're heading down a bad road that many delphi programmers take years to realize was a bad idea
So one form needs to call your method, it does it like this:
procedure TForm1.DoSomething;
begin
DataModule1.LogicMethod;
end;
Elsewhere there is a timer...
procedure TForm2.Timer1Timer(Sender:TObject);
begin
DataModule1.LogicMethod;
end;
And then the method itself:
procedure TDataModule1.LogicMethod;
begin
// Everything that you used to have in Timer1Timer goes here, except the setting of
// UI properties in Form1 which is kept in Form1:
Inc(FCounter);// stupid example.
//
if Assigned(FOnResults) then
FOnResults(Self, FCounter, FDataObject1);
// Form2 is connected to FOnResults event, and stores the
// result in the UI somewhere.
end;
Event handlers are just normal procedures. If your ObstPongForm tick handler has additional code that it needs to run in addition to the PongForm's code, then you can call the PongForm's tick handler manually when needed, eg:
uses
..., PongForm;
procedure ObstPongForm.TickHandler(Sender: TObject);
begin
...
PongForm.TickHandler(Self);
...
end;
Is possible create (register) a new class in runtime using delphi.
I have a Class called TMyForm, is possible create a new form derived from TMyForm but with new class type.
i want something like this
var
Myform : TMyForm;
MyFormClassBase : TFormClass;
begin
MyFormClassBase := TFormClass(RegisterMyNewClass('TMyNewClass'));//obviously RegisterMyNewClass does not exist
Myform := MyFormClassBase.Create(Application);
Myform.Show;
end;
i am using delphi 7
UPDATE 1
I dont look create a new instance of the same base class, i need create an new class type in runtime derived from another class.
UPDATE 2
Thank you very much for your interest. but the purpose is a bit complex to explain (because my bad english). I have a form that allows you to edit multiple master data tables, all these tables have the same fields code (an integer primary key) and description (a varchar field), they serve to define currencies, countries, projects, groups, etc.
as logic is the same for all these tables, so only need this form by passing as parameters the title of the table name , to manage these tables. something like this
FormCurrency:= TMyForm.Create( 'Define currencys', 'CURRENCYTABLE')
if ValidateAccess(FormCurrency) then
FormCurrency.Show
else
FormCurrency.Close;
FormGroups:= TMyForm.Create( 'Define Groups', 'GROUPSTABLE')
if ValidateAccess(FormGroups) then
FormGroups.Show
else
FormGroups.Close;
on the other hand I have a validation method (called ValidateAccess) that validates the users access to the forms using the form's class . because of this if you use the same kind of form is restricted access to all the options like "define groups","define currencys", "define countrys" (which I do not want that to happen), because that i need to pass to the ValidateAccess method a diferent class.
I cannot rewrite the ValidateAccess method because exist many diferents forms already registered in the system.
I dont want create a new form type and a new unit over and over just changing the title and the table to use.
Thanks in Advance.
I don't know if I get you right, but what I understand can be achieved in this way:
type
TCurrencyForm = class(TMyForm);
TGroupsForm = class(TMyForm);
FormCurrency:= TCurrencyForm.Create( 'Define currencys', 'CURRENCYTABLE')
if ValidateAccess(FormCurrency) then
FormCurrency.Show
else
FormCurrency.Close;
FormGroups:= TGroupsForm.Create( 'Define Groups', 'GROUPSTABLE')
if ValidateAccess(FormGroups) then
FormGroups.Show
else
FormGroups.Close;
In your ValidateAccess method (assuming the parameter is named Form) you can check something like:
if Form is TCurrencyForm then
else if Form is TGroupsForm then
If you don't have access to the new form class declarations you can use Form.ClassName instead.
It looks like Uwe managed to solve your problem. I should just state for the record that it is possible to add new class types at runtime. Classes are defined by their class reference, which is a pointer to a VMT (Virtual Method Table), and if you know how VMTs are laid out you can create one of your own. I did a session on it at CodeRage last year. Unfortunately, the audio quality sucked. :(
Of course, this isn't much use to you unless you have to create classes whose definition is not available at compile time, for example if you're using a scripting engine. When all the information you need is available at compile time, go with something like what Uwe described.
Why do you need to create a new subclass of the form? You cannot change anything about that new class to make it different from the existing class at runtime. i.e. you cannot add new methods or properties.
I suspect that you have made the mistake of thinking that one form class can have only one instance. But this is not the case. You can create as many instances of a form as you wish:
var
formA : TMyForm;
formB : TMyForm;
begin
formA := TMyForm.Create(Application);
formB := TMyForm.Create(Application);
formA.Show;
formB.Show;
end;
If this isn't what you require, you will need to provide more information about just what exactly it is you are trying to achieve.
IIUC, you can have something like this:
TmyForm = class... //your normal form
...
public
property Title: string read FTitle write SetTitle;
property FormKind: TFormKind read FFormKind write SetFormKind;
function ValidateAccess: boolean;
...
end;
Where TFormKind = (fkCurrency, fkCountry, ...);
And in your SetTitle will also set the form's caption, in your SetFormKind you will do your coressponding initialization(s) if necessary whereas in ValidateAccess you will handle (most probably in a case) the different situations according to the value of FFormKind.
And to use it:
myForm:=TmyForm.Create(Application); //since we'll free it the owner can be also 'nil'
myForm.Title:='Boo!';
myForm.Kind:=fkCurrency;
if myForm.ValidateAccess then
myForm.ShowModal; //btw your 'if' structure is a little bit 'odd' to say at least. You don't need to call Close on a form which isn't showing
myForm.Free; //get rid of it. - of course this applies if we created it. Not applicable if you use 'Show' only, of course.
However perhaps you'll find better to separate the layers and have a different class to handle the validation, according to the form's properties etc.
Delphi is a 'static' language, so you cannot create a new type (or class) at run time. You can do this in some 'dynamic' languages, like Python.
If you are trying to create a new form, populated with different controls, you can do this, but you need to create each individual control, make the form it's parent (and owner) and set it's position and caption etc.
procedure TForm1.Button1Click(ASender: TObject);
var
LForm: TForm;
LLabel: TLabel;
begin
LForm := TForm.Create(nil);
try
LForm.Width := 100;
LForm.Height := 100;
LLabel := TLabel.Create(LForm);
LLabel.Parent := LForm;
LLabel.Caption := 'Hello World!';
LForm.ShowModal;
finally
FreeAndNil(LForm);
end;
end;
I have similar problem, and find some runtime solution.
Only request is that MyForm already created in runtime.
var vOldForm,vNewForm:TObject;
begin
vOldForm:=Application.FindComponent('MyForm');
If vOldForm<>nil then
vNewForm:=TFormClass(vOldForm.ClassType).Create(Application);
If vNewForm is vOldForm.ClassType then (vNewForm as TForm).Show;
end;
Our application used to make use of a common base form that all forms were meant to inherit from. I'd like to get rid of it for a number of reasons, ranging from the need to police that everyone uses it to several annoyances relating to Delphi's VFI implementation. It turns out that the bulk of the features it offered can be done in other, more reliable ways.
The one that I am not so sure about, is automatically positioning all forms in the center of their callers. So if I open Dialog A from my main form, it should be placed over the center of the main form. And if I then open Dialog B from Dialog A, it should be placed over the center of Dialog A and so on.
We used to take care of all this by setting the base form's Position property to poOwnerFormCenter and it worked great. But how do I do this app-wide?
I thought of using Screen.OnActiveFormChange, but I think this happens each time the form receives focus. I also thought of using Application.OnModalBegin but there doesn't seem to be an obvious way to find the form at the point this is called.
Has anyone tried this?
Well, obviously form inheritance is provided to solve exactly the problem you're trying to solve. Any solution is probably going to wind up mimicking form inheritance in some way.
Could you do something as simple as globally searching your code for "= class(TForm)" and replacing the TForm class with either your existing base form or a new, simplified base form class with only the functionality you need?
Failing that, you could try to modify the original TForm class itself to have the positioning behavior you want. Obviously, modifying the supplied classes is a little on the dangerous side.
If you are not going to go with a common base form, then I would suggest placing a non-visual component on each form. That component can inject the behaviors you want into the base form. If you want to have various different behaviors on different forms then give your component a role property that defines what role that form should have, and it can then inject different characteristics based on that role.
BTW, you can also have non-visual form inheritance, which is my preferred method of creating a common base class for all forms. It also has the advantage of adding properties to the form, and then based on those properties you can change the role or behavior of the form.
Without knowing more about your application, my advice would be to add the positioning code to each form individually - the advantages of not having a base class is that it makes it easier to have certain forms that do things slightly differently, and it keeps all the logic of a form together in one place.
I normally use the FormShow event for this, using the SetBounds() procedure.
With other non-form controls you can do the same thing by overriding the CMShowing message.
I took your idea of OnModalBegin and ran with it. The following is a "Hack", but it seems to work. To test simply drag around the form and click the button.
procedure TMainForm.Button1Click(Sender: TObject);
var
mForm: TForm;
begin
mForm := TForm.create(self);
mform.width := 300;
mform.height := 300;
mForm.ShowModal;
mForm.Free;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
application.OnModalBegin := modalbegin;
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
if Screen.FormCount>1 then begin
screen.forms[Screen.FormCount-1].left := round((screen.forms[Screen.FormCount-2].left + screen.forms[Screen.FormCount-2].width/2) - screen.forms[Screen.FormCount-1].width/2);
screen.forms[Screen.FormCount-1].top := round((screen.forms[Screen.FormCount-2].top + screen.forms[Screen.FormCount-2].height/2) - screen.forms[Screen.FormCount-1].height/2);
application.processmessages;
screen.forms[Screen.FormCount-1].Caption := inttostr(screen.forms[Screen.FormCount-1].top)+','+inttostr(screen.forms[Screen.FormCount-1].left);
end;
end;
procedure TMainForm.ModalBegin(Sender: TObject);
begin
if Screen.FormCount>=0 then
screen.forms[Screen.FormCount-1].OnShow := FormShow;
end;