Delphi - Creating controls before form Create is run? - delphi

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.

Related

Delphi TForm constructor

I have already found something on stackoverflow but it doesn't really solve my doubt. I know that the correct way to create an object is, after the creation, surround the code in a try-finally block. But what about:
procedure TForm3.FormCreate(Sender: TObject);
begin
a := TClassX.Create;
end;
And then call:
procedure TForm3.FormDestroy(Sender: TObject);
begin
a.Free;
end;
Where a: TClassX; is a public declaration inside TForm3 class. Should I create the constructor and the destructor for the form, or I can use the code above? Is it safe?
The try/finally is there, or at least something equivalent. It just exists outside your code, higher up the call stack. Something like:
Form1 := TForm1.Create(nil);
try
// do stuff
finally
Form1.Free;
end;
Your OnCreate and OnDestroy handlers are called from the constructor and destructor, respectively, and so are protected.
So long as everybody plays by the rules nothing leaks. And the rules here are that objects created in the constructor and to be destroyed in the destructor. Whoever actually creates the object is responsible to ensure that it is destroyed no matter what. But that's the task of the consumer of your class rather than you.
I had some issues with this events some time ago so I would not recommend to use OnCreate/OnDestroy events in your application. Here are just some cases I remember:
VCL by default handles exception from OnCreate/OnDestroy events. Actually if a:=TClassX.Create; will generate an exception, it will be shown by application exception handler but the form will be created successfully keeping "a" variable equals to nil. This may lead to Access Violations if you try to access this variable later.
Depending on OldCreateOrder these events may be called either from constructor/destructor or from AfterConstruction/BeforeDestruction methods. It may also lead to Access Violations if you occasionally change OldCreateOrder in form descendants
And even more, it looks strange for me when you are using events instead of virtual functions. In most cases events are used to delegate the functionality to another objects (for example you assign a form's method to a button's OnClick event).

Delphi destroy dynamic control onDestroy of the parent form

How do I make sure a dynamic control is destroyed along with it's parent form?
So, from the main form, I create a button for a secondary form, then I display the secondary form with the button on it.
Now, I want to make sure the created button is destroyed together with the secondary form.
Would it be enough to make the Parent of the Button, the secondary form? Would that do it?
I am using a custom descendant of the TButton Class - TMyButton. So in my constructor I have the next code:
constructor TMyButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.OnClick := Self.MyButtonClick;
Self.Parent:=TWinControl(AOwner);
self.Visible := true;
end;
Is this going to be ok?
It works for me, it throws no errors, but I want to make sure that the said button is destroyed together with the form on which it is placed.
The MyButton will be placed on a secondary form, let's say 'Form2'
So there will be a code like:
var
bt:TMyButton;
begin
bt:=TMyButton.Create(Form2);
bt.Parent:=Form2;
...
form2.Show;
end;
First of all: setting a control's parent in its constructor is wrong! For a control created in design time, the IDE is and will be responsible for setting a parent. For a control created in runtime, the creating code should be responsible for setting a parent.
How do I make sure a dynamic control is destroyed along with it's parent form?
A control which has a Parent will automatically be destroyed when that immediate Parent is destroyed. Furthermore, a control which has an Owner will automatically be destroyed when that Owner is destroyed. It the Owner is in the Parent chain of the control then everything is fine. If the Owner is not in the Parent chain of the control, then you have a design problem which could result in unwanted destruction.
Your current constructor is completely unnecessary. Casting the Owner (which could be nil!) to a TWinControl without checks is wrong. But as said, setting the Parent should not be there. Visible is true by default, and assigning the OnClick event will prevent it for further use. Instead, override Click.
Regarding your comment, the calling code would become:
procedure TForm1.Button1Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(Form2);
Button.OnClick := Form2.ButtonClick;
Button.SetBounds(10, 10, 75, 25);
Button.Parent := Form2;
end;
But this leaves you with no reference to the button. Instead add the variable to a higher scope.
With this code you should not have problems.
You're making both the Owner and the Parent is the child form. So when released form. This frees the objects it contains.
I will explain the difference between Parent and Owner:
When assigning the Parent, you are indicating "visual container". Is the object that show ... a Form a Panel, a Frame.
Moreover, the Owner is the owner. The Object owner is responsibility to release all objects "slaves".
Normally both the Parent and the Owner is the same, because we design interfaces automatically and put the components visually.
When you want to create visual components at runtime must be controlled well who is the Parent, and whom the Owner.
Different is if your in the Owner will set Nil:
Obj := TObject.Create(nil);
There if you have to indicate when you should release it. It's your responsibility.
EDIT:
You can use the TButton. Something like this:
procedure TForm1.CreateButton;
var obj: TButton;
begin
obj := TButton.Create(Form2);
obj.Parent := Form2;
Obj.OnClick := MyEventOnClick;
end;
EDIT 2:
Zarko Gajic, in About.com has an excellent article is worth reading.
This can help supplement what you have offered. You can view the article in http://delphi.about.com/od/objectpascalide/a/owner_parent.htm
The short answer is: Yes, your button will be destroyed along with the form.
But it would be useful if you know how to verify this for yourself.
Write the following code:
(NOTE: Don't forget override where you declare the method.)
destructor TMyButton.Destroy;
begin
inherited Destroy;
end;
Now put a breakpoint on the line inherited Destroy;. Run your application in the debugger, and ensure the code stops at the breakpoint. I.e.
Open your child form.
Destroy your child form. (Emphasis on Destroy, because if CloseAction is caHide, simply closing your form won't be sufficient for the test.)
Verify that you reach the breakpoint.
Press F9 to continue running.
Also verify that you reach the breakpoint only once per button. (Otherwise there would be a double-destroy bug that could lead to access violations and unpredictable behaviour.)
You can also enable Debug DCU's in your compiler options. Then when you hit the breakpoint examine the call stack Ctrl+Alt+S to see where in VCL code your button is being destroyed from.

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.

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;

Cancel / abort creating a new form in Delphi / C++Builder?

Is there any way to cancel or abort form creation from within the form's OnCreate event handler or C++Builder constructor?
Basically, I'd like to be able to call Close() from OnCreate or from the constructor and have it skip showing the form altogether. I have several forms that as part of their initialization may determine that they shouldn't be shown at all. (I realize that I could split apart this portion of the initialization or add extra checks from the calling form or similar, but if there's a way to cleanly do all of this from within OnCreate or the constructor, that seems simplest.)
Edit: In response to a few comments, some of the don't-show-at-all logic is UI logic and not business logic; the form might display a confirmation before showing, or it might use a common dialog box to get input for the form then abort if the user cancels that dialog. (Some of it is business logic and needs to be refactored, but it's often hard to find the time to refactor everything that needs it.)
You can always call Release in the OnCreate handler, but that will lead to the form quickly appearing and then being closed. Not a very professional thing.
So here's another idea. Let the forms have a public function or property to return whether they are in fact to be shown. Then where you would usually have
TheForm := TSomeForm.Create(Self);
TheForm.Show;
you would have
TheForm := TSomeForm.Create(Self);
if TheForm.ShouldAppear then
TheForm.Show
else
TheForm.Release;
Having said that - any other way of coding this (so you don't create a form that will be immediately destroyed) is surely better. Especially if you want to maintain a clear separation between UI and business layer it would be much better to have the code that decides whether the form is to be shown outside of the form. Create the form only after you have made the decision.
I would think it is much better to not even have to create the form in the first place. IF you're performing some logic which determines that the form is not even necessary, and that logic contains state which is important to the form, then re-factor the logic into a separate object (or even a data module) and pass the object to the form as a property. Here is a simple example (using the object approach):
UNIT1
type
TOFormTests = class
fStateData : string;
public
function IsForm1Needed( someparam : string) : boolean;
property StateData : string read fStateData write fStateData;
end;
UNIT2
uses
:
UNIT1;
type
TForm1 = class(tForm)
:
procedure SetFormTests(value : tOFormTests);
property FormTests : TOFormTests read fFormTests write SetFormTests;
end;
procedure SetFormTest(Value:TOFOrmTests);
begin
fFormTests := Value;
// perform gui setup logic here.
end;
then someplace in your code, where you are wanting to determine if you should show your gui or not use something like the following:
var
Tests : TOFormTests;
begin
tests := tOFormTests.create;
try
if Tests.IsForm1Needed('state data goes here') then
begin
Form1 := tForm1.create(nil);
try
Form1.FormTests := Tests;
if Form1.ShowModal = mrOk then
// handle any save state logic here.
;
finally
FreeAndNil(Form1);
end;
end;
finally
freeAndNil(Tests);
end;
end;
This also assumes that the form is NOT in the auto-create list and needs to be shown modal.
Use Abort in the constructor. It raises a silent exception. If an object has an exception in the constructor, then the destructor is called and the memory released. The advantage of Abort is then you don't need to worry about an exception dialog being displayed if you don't add exception handling code.
Add a class function that returns an instance when needed. Then the method that determines if the form should be shown is still in that class, but it can determine if it is necessary before the form is actually constructed. Call it like "CreateIfNeeded" and it will work just like a constructor, but won't actually construct the form if it isn't needed. Minimal code changes, and maximum flexibility.
Just raise an exception in OnCreate.
You'll need also redefine behavior of HandleCreateException method (as default is to display an error message, and not to cancel creation).
I would override ShowModal
function TfHtmlEditor.ShowModal: Integer;
begin
if TabControl1.Tabs.Count=0 then
Result := mrAbort
else
Result := inherited;
end;

Resources