Setting the WindowState is showing the form - delphi

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.

Related

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.

How do I make a TVirtualStringTree process key presses with a higher precedence?

We've got a certain search form at work that was revamped recently. Its functionality is that of a standard search form: enter a few criteria in some boxes at the top, hit the Search button, and display results in a grid down below. But it was ugly and very limited in functionality, so one of my coworkers rebuilt it... right before leaving for a new job. Now I'm trying to complete the last few details.
One of the changes was replacing the old TListBox grid with a much more powerful TVirtualStringTree. But in the process, it appears to have broken something: before, if you clicked on a row in the grid (giving the grid in put focus) and hit Enter, the appropriate event handler would fire and deal with your input, opening the detail view for the selected item. In this grid, however, pressing Enter causes the TButton on the form with the Default = true property to fire its OnClick instead.
How can I make the TVirtualStringTree take precedence when it has input focus, so that it will respond to the user pressing Enter itself before (and preferably instead of) dispatching it to the form?
Handle the WM_GETDLGCODE message and include DLGC_WANTALLKEYS in the returned value. For example:
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
....
procedure TMyControl.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result := DLGC_WANTALLKEYS;
end;
Depending on whether or not your control already handles this message and returns something other than 0 you might need to use:
Message.Result := Message.Result or DLGC_WANTALLKEYS;
If you don't want to modify the code for this class then use an interposer or set the WindowProc property of the control to intercept its window procedure.

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.

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

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.

Delphi Modal Form position

I have a form that is being opened by another form.
I set the Position to be poOwnerFormCenter, so that the new form is opened where the original was
However, when I move this new form and then go back to the original, its shown where it was when I first opened the new form, not where I closed it
How would I fix this?
Thanks!
I'm a bit confused by your question so I'll clarify what I'm try to solve here!...
I think what you're trying to do is
When Form2 Opens, it is positioned centrally to Form1 and Form1 is hidden.
When Form2 Closes, Form1 is shown (exactly where it was hidden).
I think you want to do is have Form1 Show where Form2 was closed.
So I'm guessing that you have some code like...
procedure TForm1.ButtonClick(Sender: TObject);
begin
Form2.ShowModal;
end;
and you were expecting Form2 to update form1's position because you set Form2's position to poOwnerFormCenter
Well if I guessed all that correctly then all you need to do to update Form1's position when Form2 closes is
procedure TForm1.ButtonClick(Sender: TObject);
begin
Form2.ShowModal;
Left := Form2.Left;
Top := Form2.Top;
end;
The problem is that you are reusing the same instance of the modal form. Setting the position only works the first time you show the form. You have to options here:
Option 1
You can destroy the modal form every time it closes. One of the ways of doing that is having this line on the OnClose event of the form:
Action = caFree;
Of course, that means you have to recreate the modal form from the caller every time as well.
Option 2
You have to manually set the modal form's position on the OnShow event.
Use the option which best suits you.
This is (I guess) because you recreate the form every time you display it. That is, you do
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
Because you create a new instance of the TForm2 class every time you show it, and destroy it when the form has closed, the position changes; indeed, the new TForm2 object cannot possibly remember the position of any previous TForm2 object. They are two different objects (yes, same class, but that doesn't matter)!
The simplest solution is to add the TForm2 to the 'auto-create forms' list in the Project Options. It is there by default, but if you create it manually (as I think you do, and as in the code snippet above), you should have removed it from the list of forms that are automatically created...
Then you make sure that Unit1 uses Unit2, so that you can access the global Form2 variable in Unit2 from Form1 that resides in Unit1. While editing Unit1, press Alt+F11 to do this.
Then you can just show Form2 by doing
Form2.ShowModal;
The first time it is shown, it will respect its Position parameter, and position itself above its owner form. But then it will remember its position, so the second time you display it, it will be right where it closed the first time.

Resources