Open and close vcl form - delphi

Right now i have 2 forms. On Form1 i open up Form2 like this:
procedure TForm1.Action1Execute(Sender: TObject);
var
Form2: TForm2;
begin
Form2 := TForm2.Create(Form2);
Form2.ShowModal;
Form2.Free;
end;
Now i want to close the Form2 with a button on it. So i tried
procedure TForm2.cancelBtnClick(Sender: TObject);
begin
Form2.Close;
end;`
But that only gives me Access violation error when i click that button. What i am doing wrong?

The normal way to do this is to do
procedure TForm1.Action1Execute(Sender: TObject);
begin
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
end;
and, if TForm2 contains a OK button, this should have the ModalResult property set to mrOK at design-time. Use the object inspector to set this. You probably also want to set Default to True. Now you can 'click' the OK button by pressing the Enter key on your keyboard!
In addition, if there is a Cancel button in the dialog, this should have ModalResult set to mrCancel and Cancel set to True. Now you can 'click' the Cancel button by pressing the Escape key on your keyboard!
A button with a ModalResult value will automatically close a modal dialog box.

Since the form is showing modally the correct solution is to set ModalResult := mrCancel in your button click handler. The shortcut is to set the ModalResult property of the button to mrCancel and then you don't even need the event handler.
Note that your form is being created incorrectly. You are passing the unassigned variable Form2 as the Owner parameter to the constructor. I expect this is the cause of the access violation.
You should pass another form, Application or nil, for example. In fact in this case you may as well pass nil so that the code should read:
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
Form2.Free;
end;
If you pass an owner then the form will be destroyed when the owner is destroyed. Since you are destroying it yourself, you don't need to pass an owner.
That said, it is sometimes useful to set the owner, for example if you are using one of the Position property values that sets the form's position based on the owner's position. If so then I recommend passing Self in this instance which is a TForm1 object reference.

The other two answers discuss other ways to solve your problem.
I am going to point out the cause of the problem.
You have two variables named Form2. One contains the form and the other is uninitialized most likely Nil. The cause of the A/V is because your accessing the Nil variable.
When working with in a class you should avoid using the variable name of the class instead you can reference it's members directly such as calling Close; without referencing the variable. To keep things clear you can also prefix it with self. so replace form2.close; with self.close;

In your second code, you're already in form 2, so you would only need to do
procedure TForm2.cancelBtnClick(Sender: TObject);
begin
Close;
end;
If in your first code you created a variable you didnt use the generic Form2 variable made as a public variable in the form, its not then allocated to the form you called. So it will give errors. If at the top of the unit, you removed the "var Form2: TForm2;" line, you'd see it complain about the lack of variable for that line, change it to close, it will go away and so will your error.

I don't know if you already resolved that question or created a new code to avoid the problem, but what I think is happening (and you didn't showed all your code to confirm) is that you created bad code on the OnActivate main form event.
When you exit the second form, you return to your mainform and it "actvates" again.
Probably you manipulated an object that do not exists anymore.

Related

Webrowser1 in a Procedure Delphi6

When I call WebBrowser1.Navigate('www.google.com'); from a Button OnClick event, eg:
procedure TForm4.Button1Click(Sender:TObject);
begin
WebBrowser1.Navigate('www.google.com');
end;
The web page shows up in WebBrowswer1.
But, if I make my own procedure, eg:
procedure MyProcedure;
var
WebBrowser1:TWebBrowser;
begin
WebBrowser1.Navigate('www.google.com');
end;
And then try to call this procedure from a Button OnClick event, I get an Access Violation error.
Just wondering, why does it work when Delphi makes the procedure for me, but it doesn't work when I write the procedure myself? How can I correct this, or what code do I have to write in the procedure to make it work?
In the first excerpt you have added a TWebBrowser control to the form in the IDE designer. As such the VCL framework instantiates the control for you. Make it be a child control of the form, and applies all the steps needed to get the control up and running correctly.
In the second excerpt, there is no form, no control added in the designer. You declare a local variable WebBrowser1, which you do not initialise. No browser control is created, and any attempt to use the uninitialized variable WebBrowser1 leads to undefined behaviour. A runtime error is pretty much inevitable.
If you want to correct this you would need to instantiate an instance of the TWebBrowser control, set its parent appropriately, and take all the other steps that the VCL does for you.

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.

Delphi form Owner Skips ShowMessage/Dialog call in component's FormClose

My Delphi 2010 application has a number of non-modal forms that are created and owned by the mainform. One of these forms has a formclose procedure that pops up a dialog asking the user if they want to save changes. If the user closes the mainform, the "owned" form's FormClose procedure is called, however the dialog is not shown, and the user has no chance to save.
Any suggestions? I can see the procedure is being called in the debugger, but it seems to just skip the dialog. Same thing happens with a showmessage. Does the owner form somehow override the actual showing of these dialogs?
Thanks
Rusty
That kind of thing should go in the OnCloseQuery event. Set CanClose to false in the handler to abort the closing (which is more or less standard: in these situations, Yes, No and Cancel are the usual answers, with Cancel aborting the closing process).
When the main form is closed then the application terminates which frees the main form which in turn frees the forms owned by it. The owned forms are not closed, just freed, therefore their OnClose event is normally not triggered at all.
If you see ShowMessage being called from the owned form's OnClose event but the dialog doesn't show up it's probably because the application is already terminated and no longer processing messages. This means that the owned form's OnClose event is triggered by somewhere in your own code but too late.
One way to reproduce this behaviour is to post WM_CLOSE message to the owned form from the main form's OnClose event. The message is then processed by the owned form at a later moment when the application is already terminated any attempt to call ShowMessage or any modal form has no effect anymore.
I agree with Michael that OnCloseQuery is better suited for the purpose of displaying a prompt to the user. Unfortunately this alone doesn't help since the owned forms are being freed not closed. You have to call their OnCloseQuery event manually, for example:
procedure TFormMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
I: Integer;
begin
CanClose := False;
for I := 0 to ComponentCount - 1 do
if Components[I] is TCustomForm then
if not TCustomForm(Components[I]).CloseQuery then
Exit;
CanClose := True; // or another check if the main form can be closed, too
end;
I recently ran into something along these lines. I found that simply adding the code:
if not Visible then
Show;
BringToFront;
right before the save changes dialog is displayed ends all confusion. The parent form is displayed if its not visible, and brought upward in zorder to the front of the pile, then on top of that is displayed the dialog.

How to access the checked property of a Delphi 2009 ribbon button on runtime?

I want to reset the "checked" property of all TAction objects of a ribbon to false when clicking on any ribbon button and then only set it true on the pressed button.
But I did not yet find a way to access all the "checked" properties of the ActionManager's Actions.
I think I need to loop through the actionmanager's actionlist... however, but I did not yet find the right way to do.
I'd be very glad if someone could give me some hint on this.
Thanks!
TActionManager descends from TCustomActionList, so whatever you can do with the latter, you can do with the former. It has two properties you'll need to use, Actions, which is the array property that gives you access to all the list's actions, and ActionCount, which tells you how many there are. Use them to write an ordinary loop, like this:
var
i: Integer;
Contained: TContainedAction;
Action: TCustomAction;
begin
for i := 0 to Pred(ActionList.ActionCount) do begin
Contained := ActionList[i]; // shorthand for ActionList.Actions[i]
if not (Contained is TCustomAction) then
continue; // Doesn't have Checked property
Action := TCustomAction(Contained);
Action.Checked := False;
end;
end;
Action lists can hold lots of kinds of actions, and they don't all have Checked properties. That property is introduced in TCustomAction, so the code above also filters out the things that don't descend from that class.

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