My Delphi XE7 FireMonkey project is growing controls and naturally I've moved to using frames. Where I've used frames in the VCL there have been situations where I've simply chosen to host one (complex) VCL form inside another instead, creating and displaying it in the form's OnShow and setting it client-aligned (the benefit of this is that you don't get issues with dangling inherited controls when you edit the frame).
With FireMonkey though, things have changed slightly and my attempt to get a child form client aligned inside another is stumbling. I came across this very useful SO link which shows how to host a FireMonkey form inside a VCL form so I built on this with my code as follows:
procedure THostForm.FormCreate(Sender: TObject);
begin
FForm := TChildForm.Create( Self );
FForm.BorderIcons := [];
FForm.BorderStyle := TFmxFormBorderStyle.None;
FForm.Visible := True;
FForm.Parent := Self;
ResizeForm;
end;
procedure THostForm.FormResize(Sender: TObject);
begin
inherited;
ResizeForm;
end;
procedure THostForm.ResizeForm;
begin
if Assigned(FForm) then
FForm.SetBounds( Round(ClientRect.Left), Round(ClientRect.Top), Round(ClientWidth), Round(ClientHeight));
end;
This produces a child form which changes size with the host form, but remains at the top left of the screen. I've tried various position options in the ResizeForm routine too. It seems to me that a form might not be able to be the parent of another because TForm is not IAligneableControl whereas TFrame is. So, I tried 'docking' my child form to a TRectangle client aligned in the host form and this behaves the same way.
Has anyone examined this?
* SOLUTION DETAIL AS SUGGESTED BY MARCO BELOW *
Marco's solution is very neat and reduces the 'hosting' to just two lines of code. You do need to ensure that your child (hosted) form has everything inside another client aligned control - Marco suggested using a TLayout, but I already had a TPanel that I am using for a background so I had no modifications to the child form at all. So, to host this child form TChildForm inside a THostForm simply do:
procedure THostForm.FormCreate(Sender: TObject);
begin
FForm := TChildForm.Create( Self );
FForm.Panel1.Parent := Self;
end;
Job done. Thanks Marco.
Mixing forms and controls in FireMonkey is not such a good idea as it is the VCL, because in the VCL controls and form are all TWinControl descendant with their own Windows handle, while in FireMoneky the form is associuated with an operating system object while the controls are not.
The address scenario, I've used a different solution. Created a form with a client-aligned, useless TLayout with all of the controls inside it. At runtime, create this form and parent the Layout to the new container (for example a tab in a multi tab control).
I've used this a few times, never found big issues with it, and a nice way to dynamically crate tab pages keeping the visual development model.
Related
I am working with this legacy Delphi app that is a candidate for being made more modern-looking with VCL styles. One form is causing a show-stopping performance issue.
This problem form dynamically creates controls from a specification. Not only that, but it doesn't create all the controls at once. It has a hierarchical organization, with controls drawn on groupboxes, and clicking a checkbox or radio button can cause the creation of a new nested groupbox with new controls in it. At least part of the time, all of the controls on the form get their states saved, get deleted, and then get re-created and their states restored. This works acceptably with old-school (think Windows NT 3.51) controls, but when VCL Styles are added this form can take over a minute to redraw itself.
I think what's happening is that styling is causing windows events that the legacy form-drawing code is responding to, causing it to repeat things it's already done. I'm wondering if there is a way to temporarily turn styling off to allow this form to draw itself completely before applying any changes that would be made by the style.
Am I doomed to disappointment?
Edit: This is with Delphi XE3.
Use Vcl.Themes.TStyleManager to toggle the style to use, either Windows (meaning normal style) or the Vcl style you have chosen as in following testcode:
uses ..., Vcl.Themes;
TForm11 = class(TForm)
// ...
private
StylesDisabled: boolean;
// ...
end;
procedure TForm11.Button1Click(Sender: TObject);
begin
StylesDisabled := not StylesDisabled;
if StylesDisabled then
TStyleManager.SetStyle('Windows')
else
TStyleManager.SetStyle('Amethyst Kamri');
end;
How to make my non-modal forms to always be on top of my main form?
I have tried:
procedure TForm3.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent := Application.MainForm.Handle;
end;
Which seems to work fine. Is that correct way?
This is the Win32 concept of window ownership. An owned window always appears on top of its owner. The owner is specified in the call to CreateWindow and can then not be modified.
In the VCL you specify the owner by setting WndParent in CreateParams, and the framework then passes that on to CreateWindow. The VCL does this for you but in older versions the owner handling is, well, somewhat flaky. Modern versions are better and allow more control through the PopupMode and PopupParent properties.
Your code will therefore have the effect that you desire.
I would like to have a seperate form that shows "along" with my main form, so it does not overlap the main form.
Here's an example:
Notice how the main program, overlaps the log? I can't figure out how to do that in Delphi.
Thanks!
The answers to this question lie in the very useful Window Features MSDN topic.
The pertinent information is:
An overlapped or pop-up window can be
owned by another overlapped or pop-up
window. Being owned places several
constraints on a window.
An owned window is always above its owner in the z-order.
The system automatically destroys an owned window when its owner is
destroyed.
An owned window is hidden when its owner is minimized.
The main form in your app is the owner (in Windows terminology rather than Delphi terminology) of the other popup windows. The first bullet point above implies that the owned windows always appear above the main form (the owner).
Try creating an app with 3 forms and show them all. The .dpr would look like this:
program OwnedWindows;
uses
Forms,
Main in 'Main.pas' {MainForm},
Popup1 in 'Popup1.pas' {PopupForm1},
Popup2 in 'Popup2.pas' {PopupForm2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, Main);
Application.CreateForm(TPopupForm1, PopupForm1);
Application.CreateForm(TPopupForm2, PopupForm2);
PopupForm1.Show;
PopupForm2.Show;
Application.Run;
end.
You will see that the main form is always underneath the other two forms, but these other owned forms can be above or below each other. When you minimize the main form they all disappear.
You could if you want make all of your forms top-level unowned windows:
procedure TPopupForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
And like wise for TPopupForm2 in my example. This would result in all 3 windows having taskbar buttons.
One other approach is to revert to the pre-Vista way of things and make the Application's hidden window be the top-level owner window. You do this by making sure that Application.MainFormOnTaskbar is False. Skip all the CreateParams code and you'll now have a single window on the taskbar and any of your windows can be above any other because the top-level owner window is the hidden window Application.Handle. Of course the downside is that you lose your Aero Peek.
So, I guess what you need to do is to make the main form appear on the taskbar as usual, but ensure that the other forms are not owned (in the Windows sense) by the main form. But they need to be owned to avoid having them in the taskbar. So you can make the hidden application window be the owner using the CreateParams method, like so:
procedure TOverlappedPopupForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := Application.Handle;
end;
Although you state otherwise in the comments, when I do this I find that the popup form is indeed hidden when I minimize the main form. And it is shown again when the main form is restored. Thus I think this does solve your problem completely.
I haven't got Delphi open now, but would setting
mainform.formstyle := fsStayOnTop
and show the child form with
childform.show;
work?
or else try using SetWindowPos() and setting the hWndInsertAfter property to something like HWND_TOPMOST on the main form
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;
Please explain the difference between:
ChildForm := TForm.CreateParented(AOwner)
ChildForm := TForm.CreateParentedControl(AOwner)
ChildForm := TForm.Create(AOwner);
ChildForm.ParentWindow := AOwner.Handle
This example may be complicated and convoluted, I'd really just like an overview of when people use the different kinds of Create methods for forms.
Delphi 7 help tells me that I should use CreateParented(AOwner.Handle) and ParentWindow := AOwner.handle with non-VCL controls or across DLL's. Until yesterday I just set Parent := AOwner, and I have absolutely no idea why this stopped working.
(Maybe I just need to reboot my computer)
We have Components. They are visible or invisible items on a form or a datamodule. Each component can have an owner that is responsible for the eventual destruction. If there is no owner, you must take care of the destruction yourself.
We have Controls, which are components that are visible. They also have a Parent which contains the control. For example a Panel is the parent of a button on that panel.
We also have WinControls which are controls that link to windows objects. They also have a handle of the parent window.
So:
TMyControl.CreateParented
constructor CreateParented(ParentWindow: HWnd);
This is used to create a control from which the parent window is provided by an handle.
It creates the control without owner and sets the parentwindow to ParentWindow.
TMyControl.CreateParentedControl
class function CreateParentedControl(ParentWindow: HWND): TWinControl;
Creates the control, without owner, sets the parentwindow to ParentWindow and returns
it.
TMyControl.Create(AOwner: TComponent)
Creates a control with owner set to AOwner.
TMyControl.ParentWindow := AOwner.Handle;
Sets the parentwindow (handle) to the handle of AOwner.