"Stay on top" main form and modal dialogs in Delphi XE - delphi

In Delphi XE Update 1, I’m getting seemingly random behavior of modal forms if the parent (main) form’s FormStyle is set to fsStayOnTop.
1) With MainFormOnTaskbar := False (the old way), everything “just works”. With the new MainFormOnTaskbar := True, modal forms get hidden behind the main form when the main form is set to “stay on top”. In most cases saying
modalForm.PopupParent := self;
just before the call to modalForm.ShowModal seems to help. But not always.
2) All my modal forms are simple, no frills, positioned at MainFormCenter, not using form inheritance, etc. And yet the PopupParent fix only works for about half of them, while the other half still get hidden behind the main form. Strangest of all, in one case the ordering of unrelated lines of code breaks or makes it. See lines marked (1) and (2) in this code:
procedure TEchoMainForm.DBMaintenancePrompt( actions : TMaintenanceActions );
var
frm : TDBMaintenanceForm;
begin
frm := TDBMaintenanceForm.Create( self );
try
frm.Actions := actions; // (1)
frm.PopupParent := self; // (2)
frm.ShowModal;
finally
frm.Free;
end;
end;
When executed in this order, the modal form shows correctly on top of the main form. But when I reverse the lines, the modal form hides behind main. The line marked (1) sets a property of the modal form, which results in several checkboxes being checked on unchecked in a TRzCheckGroup, sitting on a TRzPageControl (from Raize components). This is the setter method that runs when line (1) above executes:
procedure TDBMaintenanceForm.SetActions(const Value: TMaintenanceActions);
var
ma : TMaintenanceAction;
begin
for ma := low( ma ) to high( ma ) do
cgMaintActions.ItemChecked[ ord( ma )] := ( ma in Value );
end;
end;
This is enough for the modal form to show behind the main form if the order of the lines (1) and (2) is reversed.
This might point to TRzCheckGroup (which gets manipulated when the setter code runs), but I have two other forms that show the same problem and do not use TRzCheckGroup (or TRzPageControl). And I could not reproduce the problem with a separate sample app using Raize components. Disabling the form, the pagecontrol or the TRzCheckGroup for the duration of the setter has no effect.
It does not appear to be a timing issue, because when the modal form shows hidden once, it always does. The change in behavior only comes from rearranging the lines of code.
3) One last observation: my modal forms are fairly simple, so they get displayed pretty much instantly, with no visible delay. But when the main form is fsStayOnTop, then very often I can see the modal form show on top of it, then see it get “pushed” behind. Then, on hitting Esc, the (invisible) modal form shows on top of the main form for a fraction of a second, then gets closed.
Either I‘m missing something that’ll seem obvious in hindsight, or this is a call for psychic debugging, I don’t know. Any ideas, please?
UPDATE. I’ve tried to track down the problem on another form where it occurs. It has a few buttons (Raize) and a TSyntaxMemo (an enhanced memo component from eControl.ru). This form has almost nothing in common with the other forms that experience the problem. After removing parts of the code and testing, I can now reproduce the problem by making a tiny change in a method that assigns a string to the memo component:
This is my original code, which causes the form containing the editor to hide behind the main form:
procedure TEditorForm.SetAsText(const Value: string);
begin
Editor.Text := Value;
end;
When I change the assignment to an empty string, the form displays correctly:
procedure TEditorForm.SetAsText(const Value: string);
begin
Editor.Text := ''; // CRAZY! Problem goes away
end;
When I assign a single character to the editor, the form starts hiding again:
procedure TEditorForm.SetAsText(const Value: string);
begin
Editor.Text := 'a'; // Problem is back
end;
Of course the other two problematic forms do not use this editor component or any of its units.
I've tried deleting the memo control and adding it again (think creation order etc.), but it had no effect. Same if I create the memo in code. The form hides as soon as a non-empty string is assigned to the memo's Text property.

I had the same issue some time ago. My solution was to add a Self.BringToFront; to the OnShow event of the modal form.

Windows does not support many top most forms for app. And modal form is topmost by default. But you have this style for your own form.
One decision in mind: remove top most of your main form (no visible effect), call modal form, set back topmost style when modal finished.

Related

Setting the WindowState is showing the form

Many of my application forms inherit from a base form which loads the saved window size, position and state recorded during FormClose and applies it during FormShow.
The inherited; line in the descendant version of FormShow is in the usual place at the start of the method, and is generally followed by a fair amount of code that requires the visual components on the form to have been created so they can be manipulated and set up.
The problem I am having is that the form is usually hidden until the end of the descendent FormShow event, which is the expected behaviour, unless the WindowState is being set to wsMaximized in the ancestor class FormShow event. In that case, the form becomes visible as soon as the inherited; line is executed, and you get to watch as the remaining visual elements get organised.
When setting the WindowState property of a VCL.Forms.TForm, the following code is executed:
procedure TCustomForm.SetWindowState(Value: TWindowState);
const
ShowCommands: array[TWindowState] of Integer =
(SW_SHOWNORMAL, SW_MINIMIZE, SW_SHOWMAXIMIZED);
begin
if FWindowState <> Value then
begin
FWindowState := Value;
if not (csDesigning in ComponentState) then
begin
if Showing then
ShowWindow(Handle, ShowCommands[Value])
else if HandleAllocated and (FWindowState = wsMaximized) then
RecreateWnd;
end;
end;
end;
The apparent cause of the issue is somewhere in that method; either the ShowWindow or the RecreateWnd, which seems to be triggering the form to be immediately painted.
Apart from moving my LoadFormState(Self); method from TBaseForm.FormShow to TBaseForm.FormActivate, is there any other way to set the form to be maximized without it actually showing the form?
You should not be calling your LoadFromState procedure either from TBaseForm.FormShow or TBaseForm.FormActivate.
TBaseForm.FormShow is called every time visibility of your form changes from False to True.
So for instance if you hide your from when showing another and then show it again after another form is closed TBaseForm.FormShow will fire and thus you will load form's dimensions and position from the saved state which might not be the same state as of when the form was hidden. User might have moved and resized the from since application was started.
This will lead to form moving its position without users wish to do so and thus it will annoy your users as hell.
Also don't use TBaseForm.FormActivate as this fires every time Form receives focus. So changing focus from another back to this one will the TBaseForm.FormActivate and thus change your form dimensions and position to the saved state which might not be teh state when form lost its focus.
The correct place for you to call your LoadFormState procedure with which you load initial properties of your form is within the forms constructor.

Unusual form's appearance after calling TCustomForm.SetFocusedControl function

First of all, my goal isn't that to set the focus on a control but I'm trying to understand why a form has a different appearance after deactivating and reactivating it.
Here is TCustomForm.SetFocusedControl function's description:
Sets focus to a control on the form.
Use SetFocusedControl to give a control on the form input focus. SetFocusedControl returns false if the Control specified by the Control parameter was already in the process of receiving focus, true otherwise.
Note: A return value of true does not indicate the control has successfully received input focus. If the Control can't have focus (for example, if it is not visible), SetFocusedControl will still return true, indicating that an attempt was made.
I've created a simple test application in order to reproduce the observed behavior:
procedure TForm1.Button1Click(Sender: TObject);
var
Res : Boolean;
begin
Res := Self.SetFocusedControl(Edit2);
if(Self.ActiveControl <> nil) then
begin
Memo1.Text :=
'ActiveControl is ' + Self.ActiveControl.Name + sLineBreak +
'SetFocusedControl result is ' + BoolToStr(Res, True);
end;
end;
Here are steps I followed to reproduce that behavior:
1) At start, the form appears as follows:
2) After clicking on "Button1", this is what I can see:
Memo1.Text reports that Edit2 is the active control but it hasn't the tipical focused appearance (selection and cursor).
Form's caption isn't grayed and clicking on it doesn't cause any change.
3) I've clicked outside the form (on the windows taskbar).
Form's caption became grayed.
4) I've reactivated the form by clicking on its caption:
Edit2 now looks like it has focus.
Could someone explain the differences between the form at point 2 and at point 4? In both cases, Edit2 was the active control and I can't understand that appearance difference.
Further informations:
Tested on Delphi 2007, Windows 10 Pro.
Despite what the documentation says, TForm.SetFocusedControl() does not actually set input focus onto Edit2 (the Win32 SetFocus() function is not called). Button1 receives the input focus when clicked on, and remains in focus after SetFocusControl() is called. That is why Edit2 does not render as focused. If you want to move input focus to Edit2, call Edit2.SetFocus() instead of Self.SetFocusedControl(Edit2).
However, calling SetFocusedControl() does change the VCL's internal state. It sets the Form's ActiveControl and FocusedControl properties, and it sets the global Screen object's ActiveControl, ActiveCustomForm, ActiveForm, and FocusedForm propeties and reorders the entries of its CustomForms and Forms properties.
When the Form is deactivated and then reactivated, the VCL sets input focus to the last known "focused" control, which is now Edit2 instead of Button1.

Reassign an OnCellClick event

I have an SQLite database with many Tables and one is named "tblAccounts"
I have a dlgCommon that has a TDBGrid on it with the dbgridAccounts.DataSource:=srcAccounts
I have several other Dialogs all of which at some time need to click a button and show the Accounts Grid to select an Account from. Rather than have many Forms all with their own TDBgrid.DataSource:=srcAccounts I am doing this...
procedure TdlgFolders.btnAcctSelClick(Sender: TObject);
begin
dlgCommon.pnlAccounts.Parent:=Self;
dlgCommon.pnlAccounts.Left:=dbedAccount.Left;
dlgCommon.pnlAccounts.Top:=dbedAccount.Top+dbedAccount.Height+2;
dlgCommon.pnlAccounts.Width:=190;
end;
When the user has the dlgFolders active and clicks "btnAcctSel" it all does as I need and shows the Grid. But, when the user clicks the Grid-Cell I am at a loss where/how to put the dbgridAccountsCellClick(Column: TColumn); Handler.
I tried putting it in the dlgCommon and it compiles, but is not used as that is no longer the Parent when the Grid is visible and Cell-clicked in one of the other Dialogs.
I would prefer to keep using the single-Grid approach as the user gets to set the column widths, Row-colors etc and I'd rather not make them do that in every Form where the Accounts Grid is needed.
How can I reassign the dlgCommon.AccountsCellClick so that the click is trapped and used in dlgFolders and other Dialogs that call it too?
I'm not sure I folllow your structure and design, but I would place the grid that shows the accounts on a TFrame. This TFrame would hold all event handlers you need for the grid, in addition to the grid itself.
Then, whenever you need to show the grid, you instantiate the frame, assign its parent and the grid and event handlers are ready for use.
On a second and third reading, if dlgCommon is a form with a hierarchial structure like
dlgCommon: TdlgCommon
pnlAccounts: TPanel
AccountsGrid: TDBGrid
it appears that you have tried to "rip-out" (by changing the parent) the pnlAccounts from that form and then the event handlers don't work, just as you have noticed.
The idea of changing a components parent like this is a really bad idea, because when you assign a new parent to the grid, it will ofcourse not anymore show up in dlgCommon. It can be visible in only one dialog at a time.
If you want the grid simultaneously visible on various forms for (at least) some period of time, I would still use a TFrame as I already suggested.
In this case you can add an OnCellClick event manually to the forms private section
procedure DBGridCellClick(Column: TColumn);
and implement it in the form
procedure TForm1.DBGridCellClick(Column: TColumn);
begin
// whatever you want to do
end;
And you instantiate the frame as follows:
procedure TForm1.Button2Click(Sender: TObject);
begin
frame:= TFrame3.Create(self);
frame.Parent := self;
frame.Left := 8;
frame.Top := 75;
frame.DBGrid1.OnCellClick := DBGridCellClick;
end;
If, on the other hand, the user needs to see the grid only briefly, to select an account (and be done with it), I would simply show the dlgCommon modally.

Delphi form minimise issue

I currently have a delphi 7 project sitting in front of me and what the original creators of the software have done is used the main form as a launch pad for another form which contains the actual controls and logic behind the entire application. So basically, form1 loads up, is set to invisible and another form (the form with all the UI controls and logic) is created and shown, its a strange way to do things, but its the way they did it.
Now I'm not familiar with the way delphi 7 handles its forms, but this second window, the window with all the controls on, whenever i click the minimise button, the form does not drop down to the taskbar as one would expect, but rather, resizes so that only the minimise, maximise and close buttons are visible and then places itself at the bottom left of the screen, just above the start menu.
the creation of this second window is:
frmPlatform := TfrmPlatform.Create(frmMain);
ModalResult := frmPlatform.ShowModal;
where frmMain is the invisible form.
My question is, why does the second window not minimize as one would expect and drop to the taskbar? and how do i get it to work,
thanks
Standard behaviour of forms I'm afraid as the mainform is by default the only form shown by Delphi on the taskbar. You can however set other forms to appear on the taskbar as well:
procedure TForm1.CreateParams
(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle
or WS_EX_APPWINDOW;
end;
And you can hide your current main form of course as well, see: http://delphi.about.com/od/adptips1999/qt/hidefromtaskbar.htm, and http://delphi.about.com/od/delphitips2008/qt/hide_taskbutton.htm for D2007 and up.
I think you will find that in standard Delphi apps only the main (first) form will be shown in the task bar. Since your main form is invisible that's your issue. The minimize is probably due to the second form being called with show modal rather than simply show.
Can you remove the first form or change which form is the main form? This can be done from the project settings page.

Delphi: Show window without activation

I struggle to show a second form above the main form without losing focus.
I have tried ShowWindow(second.handle, SW_SHOWNOACTIVATE), but the mainform loses focus.
If I set Visible := false on the second window, the call to ShowWindow does not activate the second form, but the windows is empty when shown...
Does anyone have a good recipe for this?
UPDATE: What I'm trying to do, is showing a notify-window at a given event. It's crucial that the main form does not lose focus at any time.
There has to be something wrong with your code.
I tested this code, it works:
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowWindow(Form2.Handle, SW_SHOWNOACTIVATE);
Form2.Visible := True;
end;
Be careful to use Visible, not Show ! Otherwise it'll override the SW_SHOWNOACTIVATE.
You can show the window (non modal) and reset the focus to the mainwindow.
procedure TMainForm.ButtonClick(Sender: TObject);
begin
OtherForm.Show;
SetFocus;
end;
Tested on 2006.
This does not show the other form on top. But it is very counter intuitive to have a window on top that does not have the focus.
I've used this in the past
SetWindowPos(WindowHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOSIZE or SWP_NOMOVE);
I've not tested this with recent versions of Delphi though...
If possible, you should considered using some sort of tool tip window to display the notification information. A tool tip will not steal focus from you main window when it is displayed or when a user clicks on it. A regular form will have a border by default and if the user clicks on that border your main form will loose focus.
Here is some basic code to do this. The tip disappears when free is called; however you would be better off setting a timer than using sleep.
with THintWindow.Create(nil) do
try
ActivateHint(MyRect, 'My Notification');
Sleep(DisplayTime);
finally
Free;
end
Here you are:
// you have set your 2nd form as non resizable, without border nor title etc...
Form2.Enabled := False; // prevent the 2nd form to grab focus even when clicked
SetWindowPos(Form2.Handle, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW or SWP_NOACTIVATE or SWP_NOSIZE or SWP_NOMOVE);
// be sure to hide it automatically when done as it is disabled...
I did this in the past, but I don't have the code because it was propietary in last job (sorry).
If I remember well, what I did was:
From client class A call a procedure (or function) that doesn't belongs to any class (a traditional Pascal method).
From that method, call some method in a class B that doesn't inherit from TForm
From the method in B, create an instance of popup form P, but with no parent or owner; and call a method in the instance.
From the method called in the instance, show itself.
The code (of step 3) could go something like this:
var p: TPopupForm;
begin
p := TPopupForm.Create(nil);
p.ShowWindow;
p.Release;
end;
I'm sorry if this doesn't work, I don't have Delphi too.
Daniels code works until ...
ShowWindow(Form2.Handle, SW_SHOWNOACTIVATE);
Form2.Visible := True;
Until your second form is created dynamically. Then your second form is located at position 0,0 with default width and height.
For a short moment when ShowWindow is executed you will see the second form on screen, disappearing when the next line is executed.
I am using the code for a transparent overlay form which is created dyamically. The following code is a combination of the given answers and places the second form without activation over the parent form.
SetWindowPos(Form2.Handle, HWND_TOP, Left, Top, Width, Height, SWP_NOACTIVATE);
Form2.Visible := True;

Resources