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

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;

Related

Delphi DUnitX tests creation of form twice

I created a TestCase with DUnitX which automatically generated some code and I'm confused as to how I should structure the test case.
The (automatically generated) code looks like the following:
procedure TestTBtnMgmtForm.SetUp;
begin
FBtnMgmtForm := TBtnMgmtForm.Create;
end;
procedure TestTBtnMgmtForm.TearDown;
begin
FBtnMgmtForm.Free;
FBtnMgmtForm := nil;
end;
procedure TestTBtnMgmtForm.TestFormCreate;
var
Sender: TObject;
begin
// TODO: Setup method call parameters
FBtnMgmtForm.FormCreate(Sender);
// TODO: Validate method results
end;
However, TBtnMgmtForm.Create will automatically call .FormCreate. Is it good practice to separate these two somehow? What is the difference or should be the difference between .Create and .FormCreate?
The constructor of a form will call the OnCreate event handler, if it is assigned. In your case it is assigned, to the function named FormCreate.
As a general rule, event handlers are invoked by the framework and should not be called directly. I can't see enough of your code to be sure but my instincts tell me that you should not be calling FormCreate at all. It is the job of the framework to do that.
The point of OnCreate is that it allows you to inject code into the constructor of the form class without having to override the constructor. You can use the form designer to add the event handler and fill in the code. Personally I regard OnCreate as somewhat facile. Once you know how to override constructors it seems more explicit to do that.
You however appear to have a form with a parameterless constructor. That is odd. Usually you would override the virtual constructor declared in TComponent. I wonder why you are not doing that.

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).

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;

Where should I put code to execute once after my Delphi app has finished initialising?

I have functions I want to perform after my app has finished initialising and the main form has been created. I did have the code (call it ProcedureX) in the forms OnShow event, but I have just noticed that it is being called twice, because OnShow is firing twice. It fires when the main program DPR calls:
Application.CreateForm(TMainForm, MainForm) ;
as I would expect. But after that, when I read stuff from an INI file that includes the forms on-screen position, I have a call:
MainForm.position := poScreenCenter ;
This, it would appear fires the OnShow event again.
Where can I put my call to ProcedureX, which must only be called once, and which needs the main form to be created before it can execute?
If your code only needs to run once per form creation (or per application and the form is only created once per application run), put the code in the form's OnCreate handler. It is the natural place for it to go.
Nowadays (since D3 I think) the OnCreate fires at the end of the construction process in the AfterConstruction method. Only if you were to set OldCreateOrder to True (and it is False by default), might you get in trouble as that makes the OnCreate fire at the end of the Create constructor.
The normal order of execution for a Form is :
AfterConstruction: when the form and it components are fully created with all their properties.
OnShow: whenever the Form is ready to show (and, yes, any change causing a CM_SHOWINGCHANGED can trigger an OnShow)
Activate: whenever the Form takes the Focus
So, depending on what you need in ProcedureX, AfterConstruction might be enough, and is executed only once; just override it and add ProcedureX after inherited. It'll be after OnCreate.
If it is not the case, you can post a custom message to your Form from AfterConstruction, it will be queued and will reach your custom handler after the other messages have been handled.
In both cases, you would not need a extra boolean Field.
#Sertac,
There's really no need for the FRUNOnce field; simply do OnShow=NIL as the first line of your FormShow method.
FYI, The "run once" idiom -- setting the event handler field to NIL in the first line of the event handler -- is also terribly useful for getting some code up-and-running once a form has been completely initialized. Put your code in a FormActivate method and, as the first line of the method, set OnActivate=NIL.
You can test and set a flag once you call the procedure for the first time. Like so:
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
FRunOnce: Boolean;
public
[...]
[...]
procedure TForm1.FormShow(Sender: TObject);
begin
if not FRunOnce then begin
FRunOnce := True;
ProcedureX;
end;
end;
You can add a procedure in your DPR file, after Application.CreateForm.
Put all code you need to initialize in that procedure.
Works best when you have multiple forms in your app.
Also if the initialization takes a lot, it let's the program to display the forms on the screen so the user will know that the app is loading.
Example:
PROGRAM MyProgram;
begin
Application.Initialize;
Application.CreateForm(TMyForm, MyForm);
MyForm.Show;
LateInitialize; <----------- here
Application.Run;
end.
I'm going to propose a bit different approach to this answer by Server Overflow. We will achieve almost exactly same effect, but without any edit inside the DPR file (main project source file). We will get there by using a class helper in the unit of our main form:
type
{ TAppHelper }
TAppHelper
= Class helper for TApplication
Public Procedure Run;
End;
Procedure TAppHelper.Run;
begin
Unit1.MainForm.PreRun;
inherited Run;
end;
Notice, the Unit1.MainForm.PreRun is some method in your main form, with only one caveat: if your main form is called "MainForm", then you need to prefix it with your unit's name inside the helper's method, because the TApplication class already has a member called MainForm. Incidentally, if you do leave out the prefix, this might still work, given that your Unit1.MainForm is indeed application's main form as well.
The reason why this works, is because the Unit1 is on the uses list of the DPR project, and as long as the TAppHelper is defined in the interface section (not in the implementation section), it will get loaded and by the time the Application.Run method is called in the DPR file, this will already be the helper version of it.
The beauty of this is, that it will run exactly one time, and exactly after all the forms are already created, after all their constructors have already been executed. And the fact that we're effectively customizing the Application.Run call in the DPR file, without editting the DPR file, is kind of ingenious. Again, class helpers in delphi/lazarus !
I will share one more neat trick, first take a look:
Procedure TAppHelper.Run;
begin
TTask.Run(
procedure
begin
sleep(10);
TThread.Synchronize(nil, procedure begin Unit1.MainForm.PreRun; end);
end
);
inherited Run;
end;
This is a trick I use whenever I want the code to execute with a small delay. Why? Because if your code runs before the inherited Run method, it might (depending what happens inside of that code) hang the UI momentarily, but just long enough for the form to flicker and appear not responsive during its startup. Also, we can't simply put the code behind the inherited Run method, because that won't get executed until the application gets terminated. So instead I use TTask from the System.Threading unit. The sleep(10) is probably an overkill, sleep(1) would most likely do the job, possibly even no sleep at all would work, but I do some complex initialization there, so I keep the delay generous. Bonus: if you don't update UI from your PreRun custom method, then you don't even need TThread.Synchronize wrapper, and it becomes even simpler. In case of FPC/Lazarus you can achieve the same by using TApplication.QueueAsyncCall() instead of TTask class.
I really think it's a neat trick, because I can code it entirely outside of the DPR file, in the unit of the form which defines the PreRun method, and it's guaranteed after ALL Forms are already created, not just the one where I implement my PreRun method. Also, if the class helper is in the unit of the form, instead elsewhere, then the PreRun doesn't even need to be public, it will work with protected or even private method as well! This is great for keeping this little logic away from any other part of the code.
#Sertec,
Your code won't work either if you want it to run for every unhide event (you haven't put in any code to reset the frunonce field).
So your method would need to reset frunonce field, and mine would need to set OnShow=FormShow. Same difference, except that you need an additional field.

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