Creating a form that has not been declared works - I don't know why - delphi

I'm using Delphi 7 (I know it's antique) and am a bit confused by a form I'm creating as needed and destroying when done with it.
From my main form I create another form requesting a Username and Password. The newly created form properties etc are contained in a another unit and is included in the Uses clause.
In my main form I previously "had" the following code;
var
MyOtherForm: TMyotherform;
Begin
MyOtherForm := TMyotherform.create(Nil);
{ Then I do stuff - blah blah }
MyOtherForm.free;
End;
My question is, when I remove the declaration for MyOtherForm in my main unit it still works without error. For example;
{ var // removed
MyOtherForm: TMyotherform; // removed }
Begin
MyOtherForm := TMyotherform.create(Nil);
{ Then I do stuff }
MyOtherForm.free;
End;
The same result, the form is created as usual and destroyed. What I cannot understand is why. Have I been doing it wrong in the past by declaring my form in the main unit, or does having it declared in a separate unit sufficient?

By default, Delphi creates a global variable for the form. It is added just below the class declaration of the form.
The name for that variable is the class name minus the 'T', so it's the same name you used for your local variable, which is why the code still works: you just stored a new reference in that global variable.
If you have an auto-create form, Delphi will create an instance of the form on start-up of the application, and store the reference in that global. You can manage auto-created forms and data modules in the project options, or you can simply edit the dpr file, in which you will find a line like:
Application.CreateForm(TMyotherform, Myotherform);
But even if your form is not auto-created, Delphi still adds that global variable.
Personally, I don't like those global variables at all, and I always remove them manually when I create a form or a data module. Unfortunately, there doesn't seem to be a possibility to configure this.
So: Remove the global and declare the local variable like you did in your original code. That is the right way to do it. The global is there to make it easier for beginners, but it's not helping the maintainability of your application.

Related

Transfer data between forms in borland c++ builder

I designed two forms in c++ builder:
TfrmMain
TfrmChooseName
In TfrmMain class I have button named btnNext. when btnNext is clicked, code below runs and creates new TfrmChooseName.
frmChooseName = new TfrmChooseName(this);
this->Hide();
frmChooseName->ShowModal();
this->Show();
delete frmChooseName;
frmChooseName = NULL;
also in TfrmMain I have TEdit control named txtInput.
In costructor of TfrmChooseName I want to get text of txtInput and set it as a caption of form but access volation error occured!
I also made both classes friend!
The best way to handle this is to pass the desired Caption value to the constructor itself, rather than code it to hunt for the value, eg:
__fastcall TfrmChooseName(TComponent *Owner, const String &ACaption)
: TForm(Owner)
{
Caption = ACaption;
}
.
frmChooseName = new TfrmChooseName(this, txtInput->Text);
Alternatively, you can set the Caption after the constructor exits, eg:
frmChooseName = new TfrmChooseName(this);
frmChooseName->Caption = txtInput->Text;
I think it's not possible to detect the exact problem without seeing more of the code. Making the classes friends shouldn't be necessary, since components added using the form designer have public access anyway.
Have you removed TfrmChooseName from Auto-Create forms? If not, and if frmChooseName is the global variable pointing to the auto-created form, that might cause the Access Violation.
The RADStudio Documentation article Creating Forms Dynamically says:
Note: If you create a form using its constructor, be sure to check that the form is not in the Auto-create forms list on the Project > Options > Forms page.
Specifically, if you create the new form without deleting the form of the same name from the list, Delphi creates the form at startup and this event-handler creates a new instance of the form, overwriting the reference to the auto-created instance. The auto-created instance still exists, but the application can no longer access it. After the event-handler terminates, the global variable no longer points to a valid form. Any attempt to use the global variable will likely crash the application.
You may also want to take a look at Creating a Form Instance Using a Local Variable.

Is it safe to remove the declaration of a form from the unit?

When designing a TForm, a line is added to the form's unit declaring the form object...
var
frmMyForm: TfrmMyForm;
I don't need this form auto-created, and I intend to create multiple instances of it, and to make sure I don't make the mistake of using this declared form, I commented it out...
//var
//frmMyForm: TfrmMyForm;
I was wondering if this is safe to do? I don't see any problems, and the form designer still works fine. But could there be some trouble if I leave this out completely?
This is a very common scenario when using form inheritance. You normally don't want to instantiate derived forms from the middle of the inheritance chain.
The only place where these form variables are used (besides your code perhaps) is the dpr file and that only when the form is autocreated.
So, no problem to remove the declaration.

delphi DWScript - change script variable value of type TObject at scriptruntime

I have a DWScript like this
var Outputter: TOutputter;
procedure OutputterTester;
begin
Outputter.Print;
end;
TOutputter (is only a example for a complex class) is declared and created in delphi code and exposed to the DWScript via Rtti.
TOutputter = class
procedure Print;
end;
I want to use the compiled script for several instances, but change the value Outputter that it link to current instance.
I know i can access a script variable with:
var Exec : IdwsProgramExecution;
...
AVar := Exec.Info.Vars['Outputter'].Value;
But the value is a Variant so i can't assigne a object. How can I change the value? If i first create the class in script like:
procedure Init;
Outputter := TOutputter.Create;
end;
I can assign Exec.Info.Vars['Outputter'].ScriptObj.ExtObject a arbitrary instance of TOutputter (created in delphi code) and access them in scriptcode over Outputter. But i want to assign a delphi code created instance of TOutputter without the init part.
Thank you for help!
If I understood correctly, you want to skip the Init procedure, but if that means you have to make Outputter either an external variable or a magic name or you won't be able to recognize which variable it is.
One approach for the above could be to just prepend your boiler plate code to the user script:
var Outputter := TOutputter.Create;
Another option would be to create an external variable in a TdwsUnit, you'll then be responsible for creating the script-side object from the Delphi side when the user accesses that external variable, and can handle what happens if the user assign something to the variable as well.
But if Outputter is meant to be read-only by the user, you could just declare an Outputter() function in a TdwsUnit (and create and return the script object there).
Last option would be to use an RTTI Environment, depending on what you want to do with it, that could be the simplest option, as you can change the instance in the environment directly (one of the unit tests for RTTI environment does that).

Using classes in dwsunit

1- Why is it necessary to name the methods of a class in a dwsunit this way?
dwsUnitClasses'ClassName''MethodName'Eval(Info: TProgramInfo;
var ExtObject: TObject)
2- Is there a link that must be done between the method defined in a dwsunit and its code?
coz it is not working with me. I took the demo attached with the DWscript (Custom Classes) and made something that looks like it but it is just not functioning.
No it's not necessary, that's just the format the IDE uses for auto-generated event names.
Usually you define script classes, their methods in the structure view, then you double-click their OnEval (or other) event in the properties editor, the IDE will create a method with a name like the above and you can implement there.
If you declare the methods manually, then you also have to attach their events manually, but it's not different than what you end up doing for a button click event (and where you end up with a Button1Click method).
Apart from the implementation code itself, most of the declarations in DWScript can happen at design-time, and are stored in the DFM. The Structure View is convenient for that, but you can also use the property editor only (it was even more convenient in older Delphi versions, when the structure view was sticking to design-time structures, rather than being reused for source structure).

How do I avoid to call Application.CreateForm twice?

I stumbled on this page Why shouldn’t I call Application.CreateForm.
Now I have some code like this:
SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update; // force update
Application.Initialize;
Application.CreateForm(TClientData, ClientData);
SplashForm.Update; // force update
Application.CreateForm(TClientMainForm, ClientMainForm);
Application.ShowHint := True;
Application.Run;
ClientMainForm.ServerConnected := false;
FreeAndNil(ClientMainForm);
FreeAndNil(ClientData);
First a splashform is created, then a datamodule and last the main form. The page says that Application.CreateForm should not be called twice. Should the code above be changed?
There is nothing wrong with using Application.CreateForm multiple times. But this introduces global variables for each form which can be a code smell.
Unfortunately the IDE creates one for each form. Although you can remove them if you like.
A better way is to create a form when you need it and release it when you are ready with it. So you only use Application.CreateForm for the main form.
A main datamodule can be created by the main form. But it can be global too, just a matter of taste.
So to answer the question, you can avoid Application.CreateForm by creating and releasing the forms locally.
The article mentions the side effect of Application.CreateForm (the first completed form is the main form).
So there can be unexpected side effects if the main form creates other forms using Application.CreateForm.
So just to avoid any nastyness, you should limit yoursef to a single call. Which is done using only one global form.
If TClientData is a Data Module and TClientMainForm is a form, then no (except perhaps the two FreeAndNil calls at the end - not really needed). But take care. Because as it says Rob Kennedy in his post, the Application.CreateForm does other things behind (it sets the MainForm variable), so I would advise to set up your project file according to the following rules:
Create all the forms which you want to create at startup using a single call to Application.CreateForm - usually this is done by the IDE.
Remove from the project file the forms which you want to create dynamically (on-demand) in your program. (In Project | Options | Forms...) - move them from 'Auto-Create Forms' to 'Available Forms'
Create your forms in your code using TmyForm.Create(Owner) (etc.) and not with Application.CreateForm(...). As an aside, if you are sure that you will free the form, then it is better (in order to speed the things up) to call TmyForm.Create(nil) - iow without any owner.
If you want to do some kind of initialization at startup you can have a procedure / method in the project file tied to a form / data module already created and run it before application run.
For example:
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TdmoMain, dmoMain); //<--this is a data module
Application.CreateForm(TfrmMain, frmMain); //<--this will became the main form
Application.CreateForm(TfrmAbout, frmAbout);
//... other forms created here...
frmMain.InitEngine; //<--initialization code. You can put somewhere else, according with your app architecture
Application.Run;
end.
In this way you will have the project file clean and you will know exactly which is which.
HTH
When I wrote that article, I was thinking primarily of code outside the DPR file. People see the form-creation code generated by the IDE in the DPR file and think that's the best way to create forms generally, so they use that elsewhere in their programs. They sometimes use it in the main form's OnCreate event handler to create other forms their program needs, and then they hit problems because the program's main form isn't what they think it is.
In the code you provided, it's easy to call CreateForm just once. Use it for the main form, and for nothing else. The data module isn't a main form, so you don't need CreateForm's magic there.
SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update; // force update
Application.Initialize;
// Change to this.
ClientData := TClientData.Create(Application);
SplashForm.Update; // force update
Application.CreateForm(TClientMainForm, ClientMainForm);
Application.ShowHint := True;
Application.Run;
ClientMainForm.ServerConnected := false;
// Remove these.
FreeAndNil(ClientMainForm);
FreeAndNil(ClientData);
You really shouldn't free the objects you've created here because you don't own them. They're owned by the global Application object, so let it take care of freeing them: Remove the two calls to FreeAndNil.
The article you refer to is incorrect. There are a number of valid reasons why you would want multiple calls to Application.CreateForm
1) Datamodules: You probably want these available all of the time. Best way to do this is Application.CreateForm. I know of applications with several themed Datamodules e.g. Customer, Invoice, Address to handle different areas of the database & encapsulate the functionality neatly. All of these are created in the .dpr
2) Big, slow loading stuff (which is a bad idea in & of itself but these things happen and are often inherited by support programmers...). Move the load time into the application startup exactly like your example code along with the splash screen updating. The users expect applications to take a while to get going thanks to the stirling efforts of our collegues working on Microsoft Office to lower the bar of expectations for the rest of us :)
So, in summary, don't worry your code is fine - but you can lose the "FreeAndNil" stuff. However small quick hitting Dialog type stuff is best invoked by:
with TMyform.Create(nil) do
try
//Setup
case ShowModal of
// Whatever return values you care about (if any)
end;
finally
Free;
end;
Short, sweet, to the point & minimises memory usage...

Resources