How to inherit controls created at runtime? - delphi

I have two forms,one is main and other is inherited form main.Lets say I have a function on the main form:
procedure FormMain.CreateButton;
begin
with TsButton.Create(Self) do begin
Width := 31;
Height := 31;
Left := 31;
Top := 31;
Visible := true;
Parent := Self;
end;
end;
Usually everything on the main form should be on the inherited form,but this is what I do:
I call CreateButton from mainForm ,but the button is only on the main form.
Is it possible to inherit that button too?

There's a difference between design-time and runtime. The form designer creates a definition for your form, which it instantiates at runtime. If you inherit one form from another, then it takes the base template and adds to it. But form-designer forms are only templates, like class definitions.
Now, at runtime, you instantiate a base form and a derived form, and it creates them from the templates stored in the resource section of your app. If you add something to the instance of the base form, you're modifying an individual instance, not the definition, so of course it's not going to show up on another instance. If you want to add a button dynamically to a form, you have to create it on that instance (in this case, the derived form) individually.

If you mean "inherited" the way it's normally meant, then the answer is no. (By normal, I mean you created your main form in the IDE, and then in the IDE created a descendant of that main form.)
In that case, controls created at runtime are not part of the inheritance tree, and the descendant knows nothing about it. You'll have to add the same code manually to the descendant as well.
What exactly are you trying to accomplish? If you know ahead of time that the button will be needed on both the base and the descendant forms (which you obviously do, since you're writing code to create the button), why not just actually drop the button on the ancestor?

If this were to inherit you would have no way of doing anything different on the two forms. Thus you do not want it to inherit your runtime changes!

Related

How to split the code for a large GUI?

I have a program with a large GUI. The GUI is split in several tabs.
The code for managing the GUI (for example one tab contains a full blown Windows Explorer clone) is pretty large.
Which will be the best approach for splitting such a large GUI in multiple files but without having the GUI split in multiple forms (at run time)?
Use embedded forms. This way you can maintain each one in a separate file, and at run-time they all appear to be part of the same GUI form.
Create a global variable to store the current form:
MyForm: TForm;
You don't want to auto-create the forms, so remove them from the project options -> Forms auto-create list. Instead, create them dynamically this way:
MyForm := TMyForm.Create(Application);
Then set the properties as needed, including the following:
I'm assuming you have a panel named something like EmbeddedMyForm_panel on the tabsheet where you want to embed the form. That's what I do, anyway. You could also probably use the TTabSheet directly.
with MyForm do begin
BorderIcons := [];
BorderStyle := bsNone;
parent := EmbeddedMyForm_panel;
Align := alClient;
Visible := true;
end;
I've worked on numerous projects that used this approach very successfully to embed separate forms on tons of tabs inside of one massive GUI.
ADDED: When I've asked why they didn't use frames instead, I was told that with a dozen or more embedded forms on the main form, loading it up with frames would take forever because the IDE would complain about not being able to find the ancestor form for virtually every frame on the form. You need to open all of the frame forms first in order to open the main form without getting any warnings from the IDE. Which is particularly annoying if you simply want to work on the main form itself (eg. edit the main menus) and don't need to deal with any of the frames at all.

How to verify if application has done createForm statement?

In DPR file:
Application.CreateForm(TMain, Main);
Application.CreateForm(TCommStatus, CommStatus);
But I get an error if I want to use CommStatus in Main, because it was not instanced yet. Then inside TMain I tried:
procedure TMainWindow.FormShow(Sender: TObject);
begin
Application.CreateForm(TCommStatus, CommStatus);
CommStatus.Expand(Self);
end;
I was trying to have my LOG window to be positioned and sized according to my MainWindow position and width. But as my LOG window is created after Main Window, I can't really call it in OnCreate(), even because there is no correct positioning data in OnCreate().
Don't use Application.CreateForm to create the CommStatus form at all. Create it yourself in your MainWindow.OnCreate:
proccedure TMainForm.FormCreate(Sender: TObject);
begin
CommStatus := TCommStatus.Create(Self);
CommStatus.Expand(Self);
end;
Don't forget to remove CommStatus from the auto-create forms list (in Project->Options->Forms).
I need to know if CommStatus is Assigned before execute CreateForm.
You answered your own question - use Assigned(), eg:
uses
..., CommStatusFormUnit;
if not Assigned(CommStatus) then
Application.CreateForm(TCommStatus, CommStatus);
Or:
uses
..., CommStatusFormUnit;
if not Assigned(CommStatus) then
CommStatus := TCommStatus.Create(Application);
Global variables, like the CommStatus variable in the CommStatusFormUnit unit, are zero-initialized at program startup, thus Assinged() will return False until the form is actually created, as long as you assign the new Form instance to the global variable (as the examples above do).
But CommStatus identifier does not exists until it. So I can't use Assigned(CommStatus).
Yes, it does exist, and yes, you can use Assigned(CommStatus). If you are having errors with it, then you are not using it correctly.
Expand(Self) should use Main position information to put CommStatus beside Main in the same left position, but it doesn't.
Then you are not handling the positioning logic correctly.
Another option is to create CommStatus first in your DPR. But without using Application.CreateFom (which would then incorrectly make CommStatus you main form).
This way you'll know it has definitely been created, and not have to worry.
I.e.
CommStatus := TCommStatus.Create(Application);
Application.CreateForm(TMain, Main);
However, that fact that you're trying to Expand CommStatus in the OnShow of your main form seems a little dubious to me. You've created a hidden dependency between the two forms, which is exactly one of the reasons globals are frowned upon.
One option to clean that up would be as follows:
CommStatus := TCommStatus.Create(Application);
Application.CreateForm(TMain, Main);
CommStatus.Expand(Main);
This way the relationship between the two is explicit and less error-prone.

Can a component replace it's owner form's event (OnClose) with it's own handler?

I'm working on a component that is placed on every form of my project. Is it possible, in runtime, to have the component include code into it's owner form's OnClose event handler. In other words, the form will trigger it's own OnClose event handler but the component will also include additional event handler code to run on the owner form's OnClose event. (Is that what is called vector replacement?)
Thank you.
You need to get the component to declare a field to store the form's original OnClose. Then you can do in the component's constructor:
FOriginalFormClose := (Owner as TForm).OnClose;
(Owner as TForm).OnClose := FormClose;
Then the component's FormClose would read:
TMyComponent.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// do stuff for this component
if Assigned(FOriginalFormClose) then
FOriginalFormClose(Sender, Action);
end;
Naturally the as cast ties this component to being owned by forms but if you want more flexibility you could easily cater for that.
This is a direct answer to the question that you asked, but it would be remiss of me not to question your overall design. If you want a component to live on every form in your app then surely you should derive a subclass of TForm that contains your customisations. Then make every form in your app be based on that common base form class.
That approach has many other benefits. For example, #LachlanG adds the following very apt comment with which I wholeheartedly concur:
Having a component meddle with it's owning form is undesirable. The vast majority of components should be self contained entities, altering the component owner breaks the expected contract of a Delphi component.
The common base form approach solves this by placing the code that works with the form inside the form.
If you are do go down the route of having a common base form then you should override DoClose rather than using the OnClose event. Always use the DoXXX event raisers rather than the events themselves when you are creating a common base class or a component.

Creating a compound control (parent of other controls) at runtime

I has a piece of code where I override TStringGrid's inplace editor and the hint window. For this, I created my own string grid based on TStringGrid and use a TEdit for inplace editor and a TPanel for tool tips. In TMyStringGrid.Create constructor I initialize them like this:
Constructor TMyStringGrid.Create();
Begin
inherited Create(AOwner);
MyEditor:= TEdit.Create(Self);
MyEditor.Parent := Self;
End;
In this case the owner (the main form) is freeing the controls. I used this for years and it worked.
The thing is that other people argue that the programmer should use NIL instead the Self when instantiation the child controls and later to manually free them in the Destroy destructor. It seems that the second alternative has gigantic advantage over the first one, especially when you dynamically create lots of child controls (not my case). Other problem with my code, they say, is that the child controls may be freed after an Application.ProcessMessages call while the application may still want to use them.
So, I should let my code unchanged or should I manually create and free the child controls?
There is a any Borland example of compound controls?
Delphi 7, Win XP
Reference: http://delphi.about.com/od/kbcurt/ss/dynamiccreateno.htm
Yes you can use your code without changing it.
There is a any Borland example of compound controls?
Best example is to check the implementation of TLabeledEdit.
It was creating the label in constructor
if Assigned(FEditLabel) then exit;
FEditLabel := TBoundLabel.Create(Self);
FEditLabel.FreeNotification(Self);
FEditLabel.FocusControl := Self;
There's no good reason to pass nil instead of Self in this situation. That AOwner parameter is there specifically for that reason. Take advantage of it.
There's a reason to pass nil when creating a control and manually destroy it, but for a completely different situation: if you're creating a control (typically a form) inside a function. This is a pretty common pattern, for example:
MyDialog := TMyDialog.Create(nil);
try
result := MyDialog.ShowModal;
finally
MyDialog.Free;
end;
There, you want to free it immediately instead of waiting around until the current form gets destroyed, which could be much later. But when it comes to sub-components, you usually want them to be destroyed at the same time as the parent component, not later, so pass Self to AOwner and let the VCL take care of it for you.
Considering that constructor, the grid instance is owned by Aowner (which is tipically a TForm or a TFrame). The inplace editor is owned and parented by the grid instance. I don't see how ProcessMessages would cause destruction of the child objects, since they will be destroyed in the destroying loop of TMyStringGrid. That's nothing I can see of wrong on that implementation - and I use that same design for the components I create. Ownership is there on VCL to easy our lifes when managing objects' lifetime. And is not the case where nil is recomended as owner, which is shown in Mason' answer.
In the pattern shown by Mason , the reason for the NIL is that, without a owner, the object destruction will not enter in a Notification loop. If you create a lot of components which destruction you handle manually, you want to make sure the owner is NIL, otherwise a lot of code get executed (without need) in each component construction/destruction.
Many moons ago, there was an excelent white paper on the web archive of (now defunct) eagle-software.com

How to position a form before it shows?

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;

Resources