Showing MDI form as modal - delphi

This will sound against the nature of MDI.. I need to show a MDI form (FormStyle=fsMdiChild) as modal sometimes. And I also need to access the part between Application.CreateForm and OnShow event of another MDI form, i.e.
Application.CreateForm(Form2,TForm2); // but don't set form2's visible property true.
Form2.caption:='not working example';
Form2.SomeMagicToSetVisibleTrue;
Any ideas?

For your first problem: Add another constructor, for example CreateAsMDI, like this:
constructor TModalAndMDIForm.CreateAsMDI(AOwner: TComponent);
begin
f_blChild := true;
GlobalNameSpace.BeginWrite;
try
inherited CreateNew(AOwner);
if(not(csDesigning in ComponentState)) then begin
Include(FFormState, fsCreating);
try
FormStyle := fsMDIChild;
if(not(InitInheritedComponent(self, TForm))) then
raise Exception.CreateFmt('Can't create %s as MDI child', [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
end;
finally
GlobalNameSpace.EndWrite;
end;
end;
In the normal constructor just set the variable f_blChild to false and call the inherited create.
You need two more things, rather self explaining:
procedure TModalAndMDIForm.Loaded;
begin
inherited;
if(f_blChild) then
Position := poDefault
else begin
Position := poOwnerFormCenter;
BorderStyle := bsDialog;
end;
end;
//-----------------------------------------------------------------------------
procedure TModalAndMDIForm.DoClose(var Action: TCloseAction);
begin
if(f_blChild) then
Action := caFree;
inherited DoClose(Action);
end;
Now you can call the form modal, if created with the standard constructor, and as MDI child, if created with CreateAsMDI.
If you include this in your form's declaration
property IsChild: boolean read f_blChild;
you can even do things depending on whether the form is an MDI child or not, just interrogating the isChild property.
As for your second problem: do not use Application.CreateForm, but create your form yourself:
Here the two creations for modal and MDI:
//Modal
frmDialog := TMyForm.Create(self);
// Your Code
frmDialog.ShowModal;
frmDialog.Release;
//MDI-Child
frmDialog := TMyForm.CreateChild(self);
// Your code
frmDialog.Show;
I have translated this answer form an article on the site DelphiPraxis.

The simplest method is to create a trivial subclass of the form, and set
FormStyle = fsMDIChild
AND
Form.Visible = False
in the property inspector. This is tried and tested!

At least for Delphi 2007 and 2009 creating the MDI child form invisible is easy. For the early Delphi versions (where it was impossible to set Visible to False in the property inspector) you just have to provide a handler for the OnCreate event and access a protected field of the class:
procedure TMDIChild.FormCreate(Sender: TObject);
begin
FFormState := FFormState - [fsVisible];
end;
This will disable the automatic showing of the MDI child. After you are done with your other initialisations you can simply Show it or set Visible to True.
I will not try to answer your question about modal MDI child forms, as this violates the conventions of the Windows platform.

No answers above actually does the job required. The best answer is wrong as well, because of following:
Opening the first form;
Maximize it;
Now say this form calls another form (mdi);
When it is constructed the same way you will get buggy layout... (it will mdi child, but not maximized, however the first will be still maximized). So a buggy state.
If you really need to decide in run-time whether it's a fsMDIChild or fsNormal you need to apply following approach.
You have form which is stored as fsNormal in design (or vice-versa);
Override the InitializeNewForm method;
Set the value of FFormStyle to fsMDIChild (as shown below).
...
TYourForm = class(TForm)
...
protected
procedure InitializeNewForm; override;
...
procedure TYourForm.InitializeNewForm;
var
FS: ^TFormStyle;
begin
inherited;
if DoYourCheckForMDI then
begin
FS := #FormStyle;
FS^ := fsMDIChild;
end;
end;

Related

Control over Delphi form components destroy order

Is there any way to control the order of which a Delphi form is destroying its components?
I got AVs when the form is destroying because it is destroying a component before the other component which accesses the first component.
Currently, I have no way to avoid AVs except free the first component in finalization section
You will have to call FreeNotification() on the control being referenced and override the protected Notfication() method of your control that is referencing it.
Say you have a property that links to another component (say, a TEdit):
property EditControl: TEdit read FEdit write SetEdit;
Then, if your property is set to link to such a component, you tell it that you want to be notified when it is freed, by calling its FreeNotification() method:
procedure TMyControl.SetEdit(Value: TEdit);
begin
if FEdit <> Value then
begin
if FEdit <> nil then
FEdit.RemoveFreeNotification(Self);
FEdit := Value;
if FEdit <> nil then
FEdit.FreeNotification(Self);
end;
end;
This means that your Notification() procedure will be called when the TEdit is destroyed. You will have to override it:
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
...
procedure TMyControl.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FEdit) then
FEdit := nil;
end;
That way, you will know when you can access the TEdit component, and when not to anymore. If FEdit is nil, you should not access it.
Examples taken from:
http://mc-computing.com/Languages/Delphi/ComponentNotification.html
Documentation:
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TComponent.FreeNotification
I did some search and some tests and found how to control the Delphi form components destroy order.
There are two ways, at design time and at runtime:
At design time, Edit the form's DFM, move the first component's object block as first block before any other object blocks of any other components that must be freed after the first one.
At runtime, in form OnCreate, Change ComponentIndex property of first component to any value lower than the other components which depend on it, Say 0, this will make it last one to be freed.
Example: FirstComp.ComponentIndex := 0;
That's it!
As you can see, Delphi frees components in the opposite order of which they were added to the form (LIFO), But we can alter this order as I explained.
Thanks everyone for your valuable help. Appreciate it.

Why is it that sometimes showing hiding forms misfires?

My form1 is the mainform. I use it for login purposes and after I dont need it
I hide it. If the login is successful, on button click,form3 is called.
procedure TForm1.AdvGlowButton1Click(Sender: TObject);
begin
ABSQuery4.Active:=false;
ABSQuery4.SQL.Clear;
ABSQuery4.SQL.Add('select .....bla,bla,bla....');
ABSQuery4.Open;
if ABSQuery4.FieldByName('passsword').AsString<>''
then begin
Form3.Show;
Form1.Hide;
end else begin
cxTextedit1.Text := '';
showmessage('wrong password');
end;
end;
Now I am noticing that sometimes the event produces strange results.
Form3 is shown but Form1 remains visible also. For showing the main form
from form3 I use :
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Form1.Show;
end
Since I need the application icon for Form3, I have there :
procedure TForm3.CreateParams(var Params: TCreateParams) ;
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
Params.WndParent := 0;
end;
Can you tell me am I messing anything up or is there a better way to
make the forms behave properly?
There is a better way to do this. For a start, you should give your forms meaningful names!
Your real problem is that your main form is being hidden because you are using it as your login form. You make life needlessly difficult and complicated by doing that.
So the main piece of advice is that you should make you real main form be the Delphi main form. The Delphi main form is the first form created using Application.CreateForm. I suggest that you call Application.CreateForm exactly once, to create the main form.
This may leave you wondering how to create other forms. Well, you just create them using the standard constructor, just like any other object.
So your .dpr file code might look like this:
Application.Initialize;
LoginForm := TLoginForm.Create(nil);
try
if LoginForm.ShowModal <> mrOK then
exit;
finally
LoginForm.Free;
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
Once you make this change you will find that you don't need to take steps to force the main form onto the taskbar.

How do I refer to components created at runtime rather than in the form designer?

i have a little problem. I'm trying to create in Delphi7 a list of components at run-time and to resize them with form's .OnResize event but no use... i can't figure out how to do it.
Here's my code:
procedure TForm1.Button1Click(Sender: TObject);
var
//ExtCtrls
panel: TPanel;
memo: TMemo;
splitter: TSplitter;
list: TListBox;
begin
panel := TPanel.Create(Self);
list := TListBox.Create(Self);
splitter := TSplitter.Create(Self);
memo := TMemo.Create(Self);
with panel do
begin
Parent := Form1;
BevelOuter := bvNone;
Top := 12;
Left := 12;
Height := Form1.Clientheight - 24;
Width := Form1.Clientwidth - 24;
end;
with list do
begin
Parent := panel;
Align := alClient;
Top := 0;
Height := panel.Height;
end;
with splitter do
begin
Parent := panel;
Top := 0;
Width := 12;
Align := alLeft;
end;
with memo do
begin
Parent := panel;
Top := 0;
Left := 0;
Width := round(panel.Width / 4) * 3;
Height := panel.Height;
Align := alLeft;
end;
end;
Do i have to somehow register their names in order to use them in form's event? Or maybe, to create a class and include them?
Any kind of help is really appreciated! Thank you in advance.
Your variables are local to the procedure where they are created so you can't refer to them using those variables when outside that procedure. The solution is to make them fields of the form class.
type
TForm1 = class(TForm)
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FPanel: TPanel;
FMemo: TMemo;
FSplitter: TSplitter;
FList: TListBox;
end;
Then your FormResize event handler can refer to them.
procedure TForm1.FormResize(Sender: TObject);
begin
if Assigned(FPanel) then
begin
...
end;
end;
Don't forget to remove the local variables from Button1Click and use the fields instead.
procedure TForm1.Button1Click(Sender: TObject);
begin
FPanel := TPanel.Create(Self);
...
end;
Although David's answer is also very correct, I thought I would take a moment and go into some more detail. By the looks of it, you seem to be very new with Delphi. There is a very common issue with beginners, which David doesn't address in his answer, pertaining to creating and freeing these objects. Any and every time you ever call 'Create' on a class, at some point, when you're done with it, you have to also 'Free' that class. Failure to free anything will result in a memory leak, and no one wants that. Freeing is just as simple as creating - until you get into the subject of keeping a list of objects (which you don't need right now).
Let's say you wanted to create a text box (TEdit) control and place it in the center of your form. Now first of all, the Delphi IDE allows you to simply drop these controls in your form, just making sure you know. You don't necessarily need to create/free them yourself, unless there's some special scenario. But doing this is dangerous. For the sake of this example, we're assuming that this TEdit control will be there for the entire duration of your application.
First, you need to declare a variable somewhere for this control. The most reasonable place for this is inside the class where it will be used (in this case, your form which we'll call Form1). When working with variables (aka Fields) in your form, make sure you do not put anything above the private section. Everything above private is intended for auto-generated code by Delphi for anything which has been dropped (and is visual) in your form. Otherwise, any manually created things must go under either private or under public. The public area would be a good place for your control...
type
TForm1 = class(TForm)
private
public
MyEdit: TEdit;
end;
Now that it's declared, we have to create (and free) it. It's a good practice that any and every time you ever create something, that you immediately put the code to also free it before you continue working. Make an event handler for your form's OnCreate and OnDestroy events...
procedure TForm1.FormCreate(Sender: TObject);
begin
MyEdit:= TMyEdit.Create(nil);
MyEdit.Parent:= Self;
MyEdit.Left:= (ClientWidth div 2) - (Width div 2);
MyEdit.Top:= (ClientHeight div 2) - (Height div 2);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(MyEdit) then MyEdit.Free;
end;
If this object is not created (before creation or after destruction), then you will get an "Access Violation" when trying to use it. This is because your application tries to access an area of the computer's memory which is not allocated or not matching with the type you meant to get.
Well, that's the basics to fix your scenario. However one more thing to show you. Suppose you need to just create an object for a short time, for the duration of a procedure. There's a different approach for this. In your code above, you declared your variable directly within the procedure. This example will show you when it is necessary to do this...
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject: TMyObject;
begin
MyObject:= TMyObject.Create;
try
MyObject.DoSomething;
Caption:= MyObject.GetSomething;
finally
MyObject.Free;
end;
end;
You see, as long as MyObject will only be used in this one call to this procedure, then you can declare it here. But if the object is expected to stay in memory after this procedure is over and done with, then things get more complicated. Again, in your case, stick with putting this in the form's class until you're more familiar with dynamically creating objects.
A final note, as mentioned above, you do have the ability to place the TEdit control directly on your form in design-time without writing your own code. If you do this, you need to remember NOT to try to create or free these ones. This is also the case when Delphi will automatically put the code above the private section - is when there's something which you're not supposed to play with.
I don't think I am eligible to "comment", so I'm phrasing this as an "answer". If you want to resize your runtime components when their parent changes size, take a good look at the Anchors property. It can save you a lot of work.

How to keep Modal Dialog on top of Dynamic Created Form? (CreateParams - overridden)

I am dynamically creating a Form that overrides the CreateParams so that I can have it displayed on the TaskBar. From the dynamically created Form, I am calling a TColorDialog but once it is displayed my Form will go under the MainForm with the ColorDialog on top of that.
After the ColorDialog is closed the dynamic Form will return back over the MainForm.
I see that on the ColorDialog Execute method there is a Handle that can be passed but I am not sure if I am on the right track with that?
If I click under the Dialog on the MainForm it will flash but how can I have the dynamically created Form "own" this Dialog with the MainForm at the back?
I create the Form like this:
procedure TMain.Button1Click(Sender: TObject);
var
SEMArcF: TWriteSEMArcFrm;
begin
SEMArcF := TWRiteSEMArcFrm.Create(nil);
SEMArcF.Show;
end;
and it is freed on the OnClose Event:
procedure TWriteSEMArcFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
I am overriding the CreateParams like this:
procedure TWriteSEMArcFrm.CreateParams(var Params: TCreateParams);
begin
inherited;
if (FormStyle = fsNormal) then begin
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
Params.WndParent := GetDesktopWindow;
end;
end;
and to show the ColorDialog I either create it or just have a TColorDialog Component on the Form, either way will result in the same. I want it to be owned by the dynamic Form.
EDIT
I now add:
Application.ModalPopupMode := pmAuto;
The full code:
procedure TWriteSEMArcFrm.btnBackColourClick(Sender: TObject);
var
ColorDlg: TColorDialog;
begin
Application.ModalPopupMode := pmAuto;
ColorDlg := TColorDialog.Create(nil);
try
if ColorDlg.Execute then
re.Color := ColorDlg.Color;
finally
ColorDlg.Free;
end;
end;
This works fine but could there be any unusual behaviour by setting this?
Thank you
Chris
TColorDialog derives from TCommonDialog, which has two overloaded versions of Execute() available - the legacy parameterless version that has existed for years, and a newer overload that takes a parent HWND as an input parameter. You are likely calling the former. That overload uses the Handle property of the currently active TForm (only if the TApplication.ModalPopupMode property is not set to pmNone), falling back to the Handle of the MainForm if needed. If you want more control, you should call the other overload directly instead, then you can pass the dynamic form's Handle property as the parameter value.

How to set a TCustomControl's Parent In Create

When we create a component as a custom control and the control is dropped on a panel the control always appears on the form rather than the containing control. How do you set the parent of the custom control in Create so that when the button is dropped on panel the buttons parent is the panel?
TGlassButton = class(TCustomControl)
...
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
...
constructor TGlassButton.Create(AOwner: TComponent);
begin
inherited; ???????????
inherited Create(AOwner); ????????????
Parent := TWinControl( AComponent ); ??????????????
...
end;
The problem is designtime creation not runtime. This works perfectly:
procedure TForm10.FormCreate(Sender: TObject);
begin
GlassButton0 := TGlassButton.Create( Panel1 );
GlassButton0.Parent := Panel1;
GlassButton0.Left := 20;
GlassButton0.Top := 6;
GlassButton0.Width := 150;
GlassButton0.Height := 25;
GlassButton0.Caption := 'Created At RunTime';
end;
DO NOT set the Parent property in the constructor! As others have said, the IDE and DFM streaming systems will assign the Parent automatically AFTER the constructor has exited. If you need to perform operations in your constructor that are dependant on a Parent being assigned, then you need to re-design your component. Override the virtual SetParent() and/or Loaded() methods and do your operations from there instead. And make use of if (csDesigning in ComponentState) then ... checks in places where you can avoid operations that are not actually needed at design-time.
Parents should be set by whomever is creating the control. For controls created at design time, this would be done by the streaming system when the form is created. For controls created at run-time, it should be done when the control is created:
var
Control: TWinControl;
begin
Control := TGlassButton.Create(<Form or Application>);
Control.Parent := <Some other control on the form>;
end;
Please note that in general the form is the owner of all controls on it, regardless of parent-ing. The Parent of a control is / should be the control responsible for painting it: in other words the control in which it is visually located. Ie a Panel, TabSheet, GroupBox or some other container.

Resources