How to position a form before it shows? - delphi

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;

Related

how to use a private datamodule vs the global instance

I have a datamodule for my frame, which uses a global instance.(dmData)
The data components are linked to the datasources on the dmData instance
now I want to use a datamodule instance that is private to a frame, because I want to have multiple instances of the form which contains the frame showing at the same time.
I can't figure out how to make that happen, either in code or in designing.
in the frame, I am creating the datamodule as dmLocalData := tdmData.Create(self), but in design I don't have the option to link dmLocalData, only the option to link to dmData(so all my data controls are blank (except for that ONE that has a local datasource that gets set in code)
I mean, in code, I could manually go through each component one by one and change the datasource, but thinking there really has to be a better way, the maintenance on that would be pretty much horrendous.
Any ideas about a better way?
Actually there is a way to avoid hand-wiring the controls for a dynamically created datamodule. In short - override the datamodules CreateNew constructor like this:
constructor TMainDM.CreateNew(AOwner: TComponent; Dummy: Integer);
begin
Dummy := -1;
inherited;
end;
This avoids that multiple instances of the datamodule get different names and thus the references are resolved as expected. As the datamodules are private to the frame anyway, there is no need for them to have globally unique names.
A much longer and more detailed explanation can be found in these two articles, which use a quite similar task as an example:
Tweaking DFM Loading (update)
Tweaking DFM Loading

A better way of creating from a class parameter?

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.

Dynamically create form by name?

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.

Using another forms event procedure

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;

Delphi - Creating controls before form Create is run?

Well, my problem is as follows:
I have a Delphi 5 application that I'm essentially porting to Delphi 2010 (replacing old components with their latest versions, fixing the inevitable Ansi/Unicode string issues, etc.) and I've run into kind of a hitch.
Upon creation of one of our forms, an access violation happens. After looking it over, I've come to the conclusion that the reason for this is because one of the setters called in Create attempts to change a property of an object on the form that hasn't been created yet.
I've trimmed it down a little, but the code basically looks like this:
In form declaration:
property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;
In form's Create:
EnGrpSndOption := false;
In Implementation:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
btGrpSnd.Visible := Value;
end;
By tossing in a ShowMessage(BooltoStr(Assigned(btGrpSend), true)) right before btGrpSnd.Visible := Value, I confirmed that the problem is that btGrpSnd hasn't been created yet.
btGrpSend is an LMDButton, but I'm pretty sure that isn't quite relevant as it hasn't even been created yet.
While I realize I probably should only assign a value after confirming that the control is assigned, this would just result in the value set in create not being set to the actual control.
So what I want to do is find a way to make certain that all the controls on the form are created BEFORE my Create is run.
Any assistance in doing this, or information regarding how Delphi creates forms would be appreciated.
It worked back in Delphi 5, so I imagine the cause of this should be mentioned somewhere among the lists of changes between versions. Delphi 2010 is quite a bit newer than Delphi 5 after all.
Like Tobias mentioned (but advocates against) you can change the creation order (right at the form at change the creation order).
But you can also in the setter method check if the form is creating (csCreating in form.componentstate). And if it is you have to store that property value yourself, and handle it in AfterConstruction.
From your comment that you're getting an AV when placing it at design time, that means there's a problem with the control itself and it hasn't been properly ported forward. To reproduce it at runtime under controlled circumstances, you need to write a little program like this:
Make a new VCL app with a single form. Place a TButton on the form. On the button's OnClick, do something like this:
var
newButton: TLMDButton;
begin
newButton := TLMDButton.Create(self);
newButton.Parent := self;
//assign any other properties you'd like here
end;
Put a breakpoint on the constructor and trace into it until you can find what's causing the access violation.
EDIT: OK, from looking at the comments, I think we found your problem!
A form's subcontrols are initialized by reading the DFM file. When you changed your control to a TCustomForm, did you provide a new DFM to define it? If not, you need to override the form's constructor and create the controls and define their properties manually. There's no "magic" that will initialize it for you.
Your Create is always called first, before the ancestor constructor. That's just how constructors work. You should be able to call the inherited constructor before you do the rest of your initialization:
constructor MyForm.Create(Owner: TComponent);
begin
inherited;
EnGrpSndOption := False;
end;
However, there's a better way of indicating what it is you're trying to make happen. Your class is loading properties from a DFM resource. When it's finished, it will call a virtual method named Loaded. It's usually used to notify all the children that everything is ready, so if any of them hold references to other children on the form, they know it's safe to use those references at that point. You can override it in the form, too.
procedure MyForm.Loaded;
begin
inherited;
EnGrpSndOption := False;
end;
That generally shouldn't make much difference in your case, though. Loaded is called from the constructor right after the form finishes loading itself from the DFM resource. That resource tells the form all the controls it should create for itself. If your button isn't being created, then it's probably not listed correctly in the DFM. It's possible for controls to be listed in the DFM that don't have corresponding fields in the class. On the other hand, if there's a published field that doesn't have a corresponding entry in the DFM, the IDE should warn you about it and offer to remove the declaration each time you bring it up in the Form Designer. View your DFM as text and confirm that there's really an entry for a control named btGrpSnd.
Is this good enough to get you going:
if Assigned(btGrpSnd) and btGrpSnd.HandleAllocated then
btGrpSnd.Visible := ...
I see 2 possibilities: check if btGrpSnd is nil before assigning Value to the Visible property. If it's nil, you could either:
not set the property
create btGrpSnd
I would not mess around with the creation order. It's more complicated and may break with further changes.
from your comment: you can check wether you are in design or in runtime mode. Check wether your are in designtime before setting the visibility.
if not (csDesigning in Componentstate) then
begin
btGrpSnd:=Value;
end;
Answer to your comment:
go for this:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then btGrpSnd.Visible := Value;
end;
and one additional property setting btGrpSnd. If it is set to a value <> nil, set the visibility safed in fEnGrpSndOption as well.
If there's no need to set btGrpSnd outside Myform, create a init-procedure that creates everything. E.g.:
constructor Myform.Create(...)
begin
init;
end;
procedure init
begin
btGrpSnd:=TButton.Create;
...
end;
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then init;
btGrpSnd.Visible := Value;
end;
This is still better then depending on some changed init-code-hack that may break in the future.

Resources