I have 2 forms. Form1 with one panel and 2 buttons (pnl1, btnShowTree and btnAddItems). There is also Form2 which contains Treeview (tv1).
Please see short code below to understand this little demonstration:
procedure TForm1.btnShowTreeClick(Sender: TObject);
begin
with Form2 do
begin
tv1.Items.clear;
Tv1.Items.AddChild(nil, '1.' );
Tv1.Items.AddChild(nil, '2.' );
Tv1.Items.AddChild(nil, '3.' );
Form2.Parent:=pnl1;
Form2.BorderStyle:=bsNone;
Form2.show;
end;
end;
procedure TForm1.btnAddItemsClick(Sender: TObject);
begin
with Form2 do
begin
BorderStyle:=bsSizeable; // here it works wrong
tv1.Items.clear;
Tv1.Items.AddChild(nil, 'A.' );
Tv1.Items.AddChild(nil, 'B.' );
Tv1.Items.AddChild(nil, 'C.' );
// BorderStyle:=bsSizeable; here it works fine. WHY ?????
Form2.Show;
end;
end;
procedure TForm2.btnCloseForm2Click(Sender: TObject);
begin
Parent:=nil;
Hide;
// when I exchange instructions order like:
// Hide;
// Parent:=nil;
// I get the same problem with improperly nested BorderStyle:=bsSizeable; I have
// only blur idea why it is so...
end;
I expected, that when I click on btnAddItems I will see 3 items (A. B. C.). But it will show 6 items, because the previous ones are not deleted !!! can anybody thow a light on it, 'cause I stucked here for hours to make program work well, but I still have not the thiniest idea what I do wrong...
Changing the BorderStyle at runtime means that the window has to be destroyed and recreated. This means that the VCL has to store the content of whatever controls are on the form (like your TTreeView), destroy the form, create the form with the new BorderStyle, recreate all the controls on the form, and then restore all the content.
You're probably using an older version of Delphi (see note below) that doesn't properly remove the stored content from memory. #M Schenkel is using a later version that does.
The solution, of course, is to stop changing the BorderStyle at runtime, which will stop causing the form to be destroyed and recreated. :-) I've been programming with Delphi starting with version 1 and continuing through the current Delphi 2010, and in all that time I have never once had a need to change BorderStyle at runtime.
NOTE: When posting a Delphi question, you should always indicate what version of Delphi you're using. Differences in Delphi version means differences in the VCL, and a problem can be caused by different things in those different versions. Knowing what version of Delphi you're using makes solving your problem or answering your question easier.
Related
Setting a form to WindowState = wsMaximized will sometimes cause the form to be maximized but not:
Long-time bug: this is a question I first asked in the Borland newsgroups in 2003:
Accepted fix for WindowState = wsMaximized?
and then again in 2006:
wsMaximized breaks it, NOT caused by Position=poScreenCenter, reproducible dfm
and then again in 2008:
Forms not starting maximized
Someone asked it on the Embarcadero forums in 2012:
Thread: Application not starting with maximized window
Now it's time to port the 18 year old bug to Stackoverflow. Maybe someone's finally figured out a workaround.
Steps to reproduce:
My posts contained half a dozen failure modes, but the easiest is:
Drop a Label and an Edit on a form:
Add an OnEnter event for the TEdit:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
Label1.Font.Style := Label1.Font.Style + [fsBold];
end;
and set the form:
WindowState to wsMaximized
AutoScroll to False
And bazinga, fails.
One of the other set of steps from the 2008 post:
Create a new app and a form.
Set the form to maximized (WindowState = wsMaximized) at design time.
Drop a ListView control on the form
During OnShow, add 20 empty items to the list view:
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 20 do
ListView1.Items.Add;
end;
Set the form's AutoScroll property to false (AutoScroll = False) at design time
Of course what I'm not after is "fixed in version n of RadStudio. Just use that". I'm looking for an actual fix (if there is one); which could include quoting relevant changes to the VCL source when CodeGear finally did fix it. (If it is even fixed).
Note: Changing Position from poDesigned to anything else doesn't fix it.
Workaround
A horrible, ugly, awful, disgusting, workaround I had been using was to start a timer during OnShow, and then when the timer fires, maximize the form:
procedure TForm1.tmrVclMaximizeHackTimer(Sender: TObject);
begin
Self.WindowState := wsMaximized;
end;
I later improved this hack to post a message during OnShow; which is essentially the same as a timer message, without having to use a timer:
const
WM_MaximizeWindow = WM_APP + $03;
procedure TForm1.FormShow(Sender: TObject);
begin
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
PostMessage(Self.Handle, WM_MaximizeWindow , 0, 0);
end;
end;
private
procedure WMMaximizeWindow(var Message: TMessage); message WM_MaximizeWindow;
procedure TForm1.WMMaximizeWindow(var Message: TMessage);
begin
Self.WindowState := wsMaximized;
end;
Sometimes I invent the OnAfterShow event that Delphi never did:
const
WM_AfterShow = WM_APP + $02;
procedure TForm1.FormShow(Sender: TObject);
begin
PostMessage(Self.Handle, WM_AfterShow, 0, 0);
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
FMaximizeNeeded := True;
end;
end;
private
procedure WMAfterShow(var Message: TMessage); message WM_AfterShow;
procedure TForm1.WMAfterShow(var Message: TMessage);
begin
if FMaximizeNeeded then
begin
FMaximizeNeeded := False;
Self.WindowState := wsMaximized;
end;
end;
But no hacks are better than hacks.
I Can reproduce with D7/Win7.
I don't use wsMaximized at all (similar random problems as you describe).
Workaround: use OnActivate -> ShowWindow(Handle, SW_MAXIMIZE) e.g.:
procedure TForm1.FormActivate(Sender: TObject);
begin
// Maximize only once when the Form is first activated
if not FMaxsimized then
begin
FMaxsimized := True;
ShowWindow(Handle, SW_MAXIMIZE);
end;
end;
This method will not work during OnShow.
Better Workaround: use ShowWindowAsync during OnShow or OnCreate e.g:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowWindowAsync(Handle, SW_MAXIMIZE);
end;
This sets the show state of a window without waiting for the operation to complete.
I only tested the first reproduction case (with D7, D2007, XE2), and am able to duplicate the problem with D7 and D2007 but not with XE2.
The problem, as I see it, is that the label, having its font changed, requests its parent to re-align itself. This eventually leads to a SetWindowPos call on the form (in TWinControl.AdjustSize) with restored width/height even though the form is already maximized - which leads to the strange, behaviorally maximized but not visually maximized, form sitting on the screen.
I traced the code in D2007 and XE2 to be able to come up with what is different. The code in TWinControl.AlignControls is different between the two versions. What specifically matters is the last statement.
D2007:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
{ Apply any constraints }
if Showing then AdjustSize;
end;
XE2:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
// Apply any constraints
if FAutoSize and Showing then
DoAdjustSize;
end;
I hope this, somehow, helps you devising/deciding what workaround to use.
The workaround I could suggest (although I haven't tested it throughly) is to force show the form maximized early:
procedure TForm1.FormCreate(Sender: TObject);
var
wplc: TWindowPlacement;
begin
if not AutoScroll and (WindowState = wsMaximized) then begin
wplc.length := SizeOf(wplc);
GetWindowPlacement(Handle, #wplc);
wplc.rcNormalPosition.Right := wplc.rcNormalPosition.Left + Width;
wplc.rcNormalPosition.Bottom := wplc.rcNormalPosition.Top + Height;
wplc.showCmd := SW_MAXIMIZE;
SetWindowPlacement(Handle, #wplc);
end;
end;
The above works because it forces to set the focus to the edit control (OnEnter event) before the VCL sets the visible flag for the form. In turn, the label's alignment request does not result with form size adjustment. Also, since, by the time VCL calls ShowWindow the form's window is already visible, it doesn't cause the form to be shown in a restored state at any stage.
However, I don't know if it would help with different reproduction scenarios.
Finally, although I can see that the behavior is corrected in newer Delphi versions, I wouldn't consider this to be a bug in the VCL. In my opinion, user code should be responsible not to cause window adjustment while window showing state is changing. The course of action I'd take for the specific scenario would be to defer to modify label's font until the VCL is done displaying the form.
I don't think this is a bug in Delphi but rather a bug (or just odd behavior) in the Windows CreateWindow function. If you search for CreateWindow and WS_MAXIMIZE not working you'll find similarly very old threads and discussions from people calling CreateWindow or CreateWindowEx passing WS_MAXIMIZE in the style parameter and not seeing a maximized window when they run the application.
Excerpt from an old gamedev.net thread
the problem is that WS_MAXIMIZE apparently does not apply when using WS_OVERLAPPEDWINDOW. if you replace WS_OVERLAPPEDWINDOW with WS_POPUP you will get a maximized window. of course this may not apply to all versions of Windows, or even all versions of the Windows shell UI for that matter.
WS_OVERLAPPEDWINDOW is MS's old default window "type" and they apparently coded CreateWindow/Ex to ignore certain styles, thinking that ShowWindow would be called with SW_SHOWDEFAULT, which causes the window to be displayed according to the CreateProcess startup info parms. this ultimately gives the user the control of how an app's main window would be displayed by using the shell's shortcut settings.
The workaround is just to call ShowWindow. It should work in Delphi, too:
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Handle, SW_MAXIMIZE);
end;
Hope the solution i use helps others (i known window is first shown with design time size):
Add a timer with interval as less as just 1 (do not put 0).
Code for it: theTimer.Enabled:=False;WindowState:=wsMaximized;
It never fail to me.
As soon as form is shown and all pending tasks for such show are finished, the timer triggers and window is maximized.
Some time ago, i was using the Trick of sending a mouse click where maximize button was, but i discovered checking Windows OS version, plugins (on Linux), etc makes the thing so hard. That one worked exactly as if the user asks for maximize the window.
The Timer i now use does exactly the same, but avoid OS checking, etc.
Not to mention: Put WindowState to wsNormal on DesignTime (do not set it to wsMinimized, neither to wsMaximized).
Wow! i did not see on the post:
ShowWindowAsync(Handle,SW_MAXIMIZE);
Thanks for that, with it my problem is solved much better than with a Timer, but not perfect, it still causes a little flicker (window is shown on incomplete render, then it goes to maxized state), it is much better than the timer, less time shown in non maximized state.
And it is compatible with a prior SetBounds() on the OnShow method.
I wish: Set initial size (Width, Height) and maybe also initial position (Left, Top) of a form prior to show it, but that form must be shown maximized; such position and sizes are for when the user un-maximize the window.
That ShowWindowAsync works perfect for such objective, and no need to add an ugly timer (with interval=1 and .Enabled=False on its code as first sentence).
How could i miss it when i read the post!
So, now on i will use (just as example os initial size relative to monitor):
procedure TtheForm.FormShow(Sender: TObject);
var
theInitialDefaultWidth,theInitialDefaultHeight:Integer;
begin
theInitialDefaultWidth:=Round(Screen.Width*3/5);
theInitialDefaultHeight:=Round(Screen.Height*3/5);
WindowState:=wsNormal; // So it can still have at design time wsMaximized, this is for the SetBounds to work on a non maximized state
SetBounds((Screen.Width-theInitialDefaultWidth)div 2,(Screen.Height-theInitialDefaultHeight)div 2,theInitialDefaultWidth,theInitialDefaultHeight); // Set default position and default size as i wish
ShowWindowAsync(Handle,SW_MAXIMIZE); // Make the window to be shown maximized when it will be visible
// ... // Rest of actions for the FormShow method
end;
Works perfect! I do not need to touch design time properties, i can let them as they are (WindowState=wsMaximized, Position=poScreenCenter, etc).. 100% code solution for the problem.
Thanks a lot!
P.D.: Will it work on Linux? I mean when code is compiled for Linux (in Lazarus), i must test it and see, if it does work it will be a great immprove on what i was using till now.
I'm working on getting the helpfile setup with our software. I have added HelpContext numbers for lots of specific forms/frame/controls and they all work fine. The problem is that the main form is not bringing up any help at all. For all of this I'm only using F1 to try to trigger the help.
I'm not at all an expert on Delphi or helpfiles, but I'll post what I've done and where I've looked.
Edit: Thanks to some help I now see the issue is due to the main form being a MDI parent. This still doesn't solve the problem.. it almost seems like a bug to me but I suppose it could be intentional for some reason. EndEdit
I'm including this unit: HtmlHelpViewer for the viewer. In the main forms Create constructor I've added the Application.Helpfile := 'asdf.chm'. For all the other forms I have just added context numbers and it's worked right away. I tried that on the main form and nothing happens. So I tried adding an Application.OnHelp event but this doesn't get called on the main form (and it does for all the other forms where help is working).
Last resort that I could think of was to trace deep down into the code and see what was happening. I got to TCustomForm.WMHelp in Vcl.Forms as the place where the split was happening. Said function has this loop:
if iContextType = HELPINFO_WINDOW then
begin
Control := FindControl(hItemHandle);
while (Control <> nil) and ( not ControlHasHelp(Control)) do
Control := Control.Parent;
if Control = nil then Exit;
GetHelpInfo(Control, HType, ContextID, Keyword);
Pt := Control.ClientToScreen(Point(0, 0));
end
When the main form was calling the Help Control would be nil and then it would exit. Anything else would go on fine.
I obviously don't know why this is happening. The answer could be something very basic. Any ideas would be appreciated!
According to your comments, the WM_HELP message is being targetted at your MDI client window. And since that is not a VCL control it does not respond to the WM_HELP message. You can deal with the problem by intercepting the message and asking the main form to handle it:
type
TMainForm = class(TForm)
protected
procedure WMHelp(var Message: TWMHelp); message WM_HELP;
end;
....
procedure TMainForm.WMHelp(var Message: TWMHelp);
begin
if (Message.HelpInfo.iContextType=HELPINFO_WINDOW)
and (Message.HelpInfo.hItemHandle=ClientHandle) then
Message.HelpInfo.hItemHandle := Handle;
inherited;
end;
If you want to be even more defensive you could write it like this:
if (Message.HelpInfo.iContextType=HELPINFO_WINDOW)
and (FindControl(Message.HelpInfo.hItemHandle)=nil) then
Message.HelpInfo.hItemHandle := Handle;
I've just had a look at my own MDI application and I can see that I have similar code to deal with this exact issue. If it hadn't been written over 10 years ago I might have remembered sooner!
I use the standard Cut, Copy, Paste actions on my Main Menu. They have the shortcuts Ctrl-X, Ctrl-C and Ctrl-V.
When I open a modal form, e.g. FindFilesForm.ShowModal, then all the shortcuts work from the form.
But when I open a non-modal form, e.g. FindFilesForm.Show, then the shortcuts do not work.
I would think that those actions should work if the FindFilesForm is the active form. It's modality should have nothing to do with it, or am I wrong in my thinking?
Never-the-less, how can I get the shortcuts to work on a non-modal form?
After Cary's response, I researched it further. It is not a problem with certain controls, e.g. TMemo or TEdit.
But it is for some others. Specifically, the ones where it happens include:
the text in a TComboBox
the text in a TFindDialog
a TElTreeInplaceEdit control, part of LMD's ElPack
I'll see if there are others and add them to the list.
These are all on important Non-Modal forms in my program.
So I still need a solution.
Okay. I really need help with this. So this becomes the first question I am putting a bounty on.
My discussion with Cary that takes place through his answer and the comments there describe my problem in more detail.
And as I mentioned in one of those comments, a related problem seems to be discussed here.
What I need is a solution or a workaround, that will allow the Ctrl-X, Ctrl-C and Ctrl-V to always work in a TComboBox and TFindDialog in a Non-Modal window. If those two get solved, I'm sure my TElTreeInplaceEdit will work as well.
It takes only a couple of minutes to set up an simple test program as Cary describes. Hopefully someone will be able to solve this.
Just be wary that there seems to be something that allows it to work sometimes but not work other times. If I can isolate that in more detail, I'll report it here.
Thanks for any help you can offer me.
Mghie worked very hard to find a solution, and his OnExecute handler combined with his ActionListUpdate handler do the trick. So for his effort, I'm giving him the accepted solution and the bounty points.
But his actionlist update handler is not simple and you need to specify in it all the cases you want to handle. Let's say there's also Ctrl+A for select all or Ctrl-Y for undo you might want. A general procedure would be better.
So if you do come across this question in your search for the answer, try first the answer I supplied that adds an IsShortcut handler. It worked for me and should handle every case and does not need the OnExecute handlers, so is much simpler. Peter Below wrote that code and Uwe Molzhan gets finders fee.
Thanks Cary, mghie, Uwe and Peter for helping me solve this. Couldn't have done it without you. (Maybe I could have, but it might have taken me 6 months.)
OK, first thing first: This has nothing to do with modal or non-modal forms, it is a limitation of the way the Delphi action components work (if you want to call it that).
Let me prove this by a simple example: Create a new application with a new form, drop a TMemo and a TComboBox onto it, and run the application. Both controls will have the system-provided context menu with the edit commands, and will correctly react on them. They will do the same for the menu shortcuts, with the exception of Ctrl + A which isn't supported for the combo box.
Now add a TActionList component with the three standard actions for Cut, Copy and Paste. Things will still work, no changes in behaviour.
Now add a main menu, and add the Edit Menu from the template. Delete all commands but those for Cut, Copy and Paste. Set the corresponding action components for the menu items, and run the application. Observe how the combo box still has the context menu and the commands there still work, but that the shortcuts do no longer work.
The problem is that the standard edit actions have been designed to work with TCustomEdit controls only. Have a look at the TEditAction.HandlesTarget() method in StdActns.pas. Since edit controls in combo boxes, inplace editors in tree controls or edit controls in native dialogs are not caught by this they will not be handled. The menu commands will always be disabled when one of those controls has the focus. As for the shortcuts working only some of the time - this depends on whether the VCL does at some point map the shortcuts to action commands or not. If it doesn't, then they will finally reach the native window procedure and initiate the edit command. In this case the shortcuts will still work. I assume that for modal dialogs the action handling is suspended, so the behaviour is different between modal and non-modal dialogs.
To work around this you can provide handlers for OnExecute of these standard actions. For example for the Paste command:
procedure TMainForm.EditPaste1Execute(Sender: TObject);
var
FocusWnd: HWND;
begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then
SendMessage(FocusWnd, WM_PASTE, 0, 0);
end;
and similar handlers for the Cut command (WM_CUT) and the Copy command (WM_COPY). Doing this in the little demo app makes things work again for the combo box. You should try in your application, but I assume this will help. It's a harder task to correctly enable and disable the main menu commands for all native edit controls. Maybe you could send the EM_GETSEL message to check whether the focused edit control has a selection.
Edit:
More info why the behaviour is different between combo boxes on modal vs. non-modal dialogs (analysis done on Delphi 2009): The interesting code is in TWinControl.IsMenuKey() - it tries to find an action component in one of the action lists of the parent form of the focused control which handles the shortcut. If that fails it sends a CM_APPKEYDOWN message, which ultimately leads to the same check being performed with the action lists of the application's main form. But here's the thing: This will be done only if the window handle of the application's main form is enabled (see TApplication.IsShortCut() code). Now calling ShowModal() on a form will disable all other forms, so unless the modal dialog contains itself an action with the same shortcut the native shortcut handling will work.
Edit:
I could reproduce the problem - the key is to somehow get the edit actions become disabled. In retrospect this is obvious, the Enabled property of the actions needs of course to be updated too.
Please try with this additional event handler:
procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
var
IsEditCtrl, HasSelection, IsReadOnly: boolean;
FocusCtrl: TWinControl;
FocusWnd: HWND;
WndClassName: string;
SelStart, SelEnd: integer;
MsgRes: LRESULT;
begin
if (Action = EditCut1) or (Action = EditCopy1) or (Action = EditPaste1) then
begin
IsEditCtrl := False;
HasSelection := False;
IsReadOnly := False;
FocusCtrl := Screen.ActiveControl;
if (FocusCtrl <> nil) and (FocusCtrl is TCustomEdit) then begin
IsEditCtrl := True;
HasSelection := TCustomEdit(FocusCtrl).SelLength > 0;
IsReadOnly := TCustomEdit(FocusCtrl).ReadOnly;
end else begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then begin
SetLength(WndClassName, 64);
GetClassName(FocusWnd, PChar(WndClassName), 64);
WndClassName := PChar(WndClassName);
if AnsiCompareText(WndClassName, 'EDIT') = 0 then begin
IsEditCtrl := True;
SelStart := 0;
SelEnd := 0;
MsgRes := SendMessage(FocusWnd, EM_GETSEL, WPARAM(#SelStart),
LPARAM(#SelEnd));
HasSelection := (MsgRes <> 0) and (SelEnd > SelStart);
end;
end;
end;
EditCut1.Enabled := IsEditCtrl and HasSelection and not IsReadOnly;
EditCopy1.Enabled := IsEditCtrl and HasSelection;
// don't hit the clipboard three times
if Action = EditPaste1 then begin
EditPaste1.Enabled := IsEditCtrl and not IsReadOnly
and Clipboard.HasFormat(CF_TEXT);
end;
Handled := TRUE;
end;
end;
I didn't check for the native edit control being read-only, this could probably be done by adding this:
IsReadOnly := GetWindowLong(FocusWnd, GWL_STYLE) and ES_READONLY <> 0;
Note: I've given mghie the answer as he did a lot of work and his answer is correct, but I have implemented a simpler solution that I added as an answer myself
I posted a link to this question on my blog, and got a suggestion from Uwe Molzhan who is not on StackOverflow. Uwe used to run DelphiPool. He pointed me to this thread at borland.public.delphi.objectpascal:
Action List (mis)behavior.
Tom Alexander who asked the original question in this thread even said:
This behavior occurs usually, but not
all the time. Sometimes after a series
of the above errors, the behavior
starts acting as I would expect.
which is exactly the strange behaviour I've been having that has made this problem near to impossible to track down.
Peter Below responded in that thread that if there are colliding shortcuts, you have to take steps to make sure the active control gets first crack at the shortcut.
Taking his code (which was written for a frames problem) and I just had to modify “ctrl is TCustomFrame” to “ctrl is TControl” and it works perfect. So here is what was needed:
public
Function IsShortcut( var Message: TWMKey): Boolean; override;
Function TMyform.IsShortcut( var Message: TWMKey): Boolean;
Var
ctrl: TWinControl;
comp: TComponent;
i: Integer;
Begin
ctrl := ActiveControl;
If ctrl <> Nil Then Begin
Repeat
ctrl := ctrl.Parent
Until (ctrl = nil) or (ctrl Is TControl);
If ctrl <> nil Then Begin
For i:= 0 To ctrl.componentcount-1 Do Begin
comp:= ctrl.Components[i];
If comp Is TCustomActionList Then Begin
result := TCustomActionList(comp).IsShortcut( message );
If result Then
Exit;
End;
End;
End;
End;
// inherited; { Originally I had this, but it caused multiple executions }
End;
So far this seems to work in all cases for me.
The ironic thing is that it didn't work for Tom Alexander, the original question asker. What he did instead was add a procedure to the FrameEnter event that set the focus to the appropriate grid for the frame. That might imply yet another alternative solution to my question, but I have no need to explore that since Peter's solution works for me.
Also note that Peter includes in his answer an excellent summary of the complex steps of key handling that is worth knowing.
But I do want to now check mghie's edit on his answer and see if that is also a solution.
I created a very simple example with two forms in Delphi 2009 (Update 3 and Update 4 installed) running on Vista 64-bit. The second form, Form2 is displayed non-modally (Form2.Show;). I have a TMemo on Form2. Ctrl-X, Ctrl-V, and Ctrl-C work just fine.
This was before I placed a TMainMenu on Form2.
So, I placed a TMainMenu on the form, and added a TActionList. I create an Edit menu items, and added Copy, Cut, Paste submenu items. I hooked these up to the standard actions EditCopy, EditCut, and EditPaste. Still, everything works fine as before. I can either use the menu items, or the Ctrl-C, Ctrl-X, and Ctrl-V key combinations.
There must be something else going on here.
My application is based on modal forms. Main form opens one form with ShowModal, this form opens another with ShowModal, so we have stacked modal forms. There is sometimes a problem that when we call ShowModal in new form, it hides behind previous forms, instead of showing on top. After pressing alt+tab, form comes back to the top, but this is not good solution. Did You meet this problem and how did you handle it?
EDIT:
I use Delphi 7.
You didn't mention which version of Delphi...
Newer Delphi versions have added two new properties to TCustomForm: PopupMode and PopupParent. Setting PopupParent of your modal dialog to the form that's creating that dialog makes sure that the child form stays on top of it's parent. It usually fixes the problem you're describing.
I think this pair of properties were added in Delphi 2006, but it may have been 2005. They're definitely there in Delphi 2007 and up.
EDIT: After seeing you're using Delphi 7, the only suggestion I have is that, in the code that displays your modal form, you disable the form creating it, and re-enable on return. That should prevent the creating window from receiving input, which may help keep the Z-order correct.
Something like this may work (untested, as I'm no longer using D7):
procedure TForm1.ShowForm2;
begin
Self.Enabled := False;
try
with TForm2.Create(nil) do
begin
try
if ShowModal = mrOk then
// Returned OK. Do something;
finally
Free;
end;
end;
finally
Self.Enabled := True;
end;
end;
If Form2 creates a modal window (as you've mentioned), just repeat the process - disable Form2, create Form3 and show it modally, and re-enable Form2 when it returns. Make sure to use try..finally as I've shown, so that if something goes wrong in the modal form the creating form is always re-enabled.
Sorry for adding a separate answer, but I have done a bit more research, and some of it indicates that my previous answer (DisableProcessWindowsGhosting) doesn't help. Since I can't always reproduce this issue, I cannot say for sure.
I found a solution that appears to appropriate. I referenced the code in Delphi 2007 for the CreateParams method and it matches pretty close (without having all of the other code that handles PopupMode).
I created the unit below which subclasses TForm.
unit uModalForms;
interface
uses Forms, Controls, Windows;
type
TModalForm = class(TForm)
protected
procedure CreateParams(var params: TCreateParams); override;
end;
implementation
procedure TModalForm.CreateParams(var params: TCreateParams);
begin
inherited;
params.WndParent := Screen.ActiveForm.Handle;
if (params.WndParent <> 0) and (IsIconic(params.WndParent)
or not IsWindowVisible(params.WndParent)
or not IsWindowEnabled(params.WndParent)) then
params.WndParent := 0;
if params.WndParent = 0 then
params.WndParent := Application.Handle;
end;
What I do then is include this unit in with a form unit, and then change the form's class (in the .pas code file) from class(TForm) to class(TModalForm)
It works for me, appears to be close to CodeGear's solution.
From this link it appears that the problem is with the "Ghosting window" that was introduced in 2000/XP. You can disable the ghosting feature by calling the following code at startup.
procedure DisableProcessWindowsGhosting;
var
DisableProcessWindowsGhostingProc: procedure;
begin
DisableProcessWindowsGhostingProc := GetProcAddress(
GetModuleHandle('user32.dll'),
'DisableProcessWindowsGhosting');
if Assigned(DisableProcessWindowsGhostingProc) then
DisableProcessWindowsGhostingProc;
end;
The only issue that I can see is that it will cause problems with the feature that allows for the user to minimize, move, or close the main window of an application that is not responding. But in this way you do not have to cover each call with the Self.Enabled := False code.
Just set the Visible property of the form, that you want to open modal, to False. Then you can open it with .ShowModal(); and it will work.
I have found that using the "Always On Top" flag on more than one form causes problems with the Z order. And you may also find the need for the BringWindowToTop function.
When launching a message box using the built-in WinAPI (MessageBox), I have found that passing the calling window's handle is necessary in order to make sure that the the prompt appears on top all the time.
try it
OnShowForm:
PostMessage(Self.Handle, WM_USER_SET_FOCUS_AT_START, 0, 0);
As an extension of this question:
TForm.OnResize is sometimes fired before a form is first shown, but not always. For example, if BorderStyle is either bsDialog or bsNone, then OnResize will not fire. For all other BorderStyle values (and with all other properties at their defaults), OnResize does fire.
Are there other things that affect whether OnResize will fire before the form is shown? For example, other properties, or combinations of properties, that can affect this?
The OnResize event is a result of the ShowWindow API function sending a WM_SIZE message to the window. That bears repeating: the message is coming from Windows, not from Delphi. It's a Windows function (ShowWindow) that's (sometimes) sending the message that triggers the event -- so the VCL source code is not really helpful in this case.
Bonus points for definitive answers based on documented ShowWindow / WM_SIZE behavior, e.g. references to MSDN documentation or Petzold books.
Maybe it even depend on user's display settings or desktop theme or Windows version. If OnResize were giving me problems like this, I would build my program to always expect it and handle it in any situation, no matter what I think to be the cause.
I believe that OnResize will fire when an event dispatch a message
saying that form size (left, bottom, width, height) will be modified.
Since you already discovered which message fires that event, you need
now trace where the message is sent in the vcl.
Look at the vcl source code to see if you can spot those operations.
Edit: let's go low level. Forms in windows (grossly talking) have what
is called "window class" (it's not a class like we know it oop). All times the window class of the form is resized (and form is visible), the WM_SIZE is sent.
So it will not happen all the times the form is shown, but only the it's dimensions are changed compared with underlying window class.
As you have observed, many properties valuez change the dimensions of the form (even a few pixels).
This is a very superficial explanation, that's a ton of other details - but it's my understanding how things works "under the hood".
There's no substitute for testing. How about creating a form in code, setting the properties you're interested in and recording when the resize event is called.
If you'll excuse the ugliness of the code, here's a rough proof of concept that tests all combinations of BorderStyle and Position without explicitly coding for each one. You can add more properties and take it as far as you like. A tool like CodeSite would make the logging cleaner and easier, too.
Create an application with 2 forms. Make sure the second one isn't auto-created.
In the second form, add a property and add a little logging code to the form's Resize event:
private
FOnResizeFired: TNotifyEvent;
public
property OnResizeFired: TNotifyEvent read FOnResizeFired write FOnResizeFired;
end;
...
procedure TForm2.FormResize(Sender: TObject);
begin
if Assigned(FOnResizeFired) then
FOnResizeFired(self);
end;
In the main form, add TypInfo to the uses clause and drop a button and a memo on the form.
Add a simple procedure:
procedure TForm1.ResizeDetected(Sender: TObject);
begin
Memo1.Lines.Add(' *** Resize detected');
end;
Now add the following to the ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
lBorderStyle: TFormBorderStyle;
lBorderStyleName: string;
lPosition: TPosition;
lPositionName: string;
lForm: TForm2;
begin
Memo1.Clear;
for lBorderStyle in [low(TFormBorderStyle) .. high(TFormBorderStyle)] do
begin
for lPosition in [low(TPosition) .. high(TPosition)] do
begin
lBorderStyleName := GetEnumName(TypeInfo(TFormBorderStyle), Integer(lBorderStyle));
lPositionName := GetEnumName(TypeInfo(TPosition), Integer(lPosition));
Memo1.Lines.Add(Format('Border: %s Position: %s', [lBorderStyleName, lPositionName]));
Memo1.Lines.Add(' Creating form');
lForm := TForm2.Create(self);
try
Memo1.Lines.Add(' Form Created');
lForm.OnResizeFired := ResizeDetected;
Memo1.Lines.Add(' Setting border style');
lForm.BorderStyle := lBorderStyle;
Memo1.Lines.Add(' Setting Position');
lForm.Position := lPosition;
Memo1.Lines.Add(' Showing form');
lForm.Show;
Memo1.Lines.Add(' Form Shown');
lForm.Close;
Memo1.Lines.Add(' Form Closed');
finally
FreeAndNil(lForm);
Memo1.Lines.Add(' Form Freed');
end;
end;
end;
end;
You'll notice that resize fires when some properties are set before the form is shown, and I see that in some combinations, resize seems to fire twice when the form is shown. Interesting.