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;
Related
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.
I created a TTabControl with two TTabItems. On each TTabItem there is one (or more) TImageViewers and several TEdits. When I click on the TImageViewer, a modal screen pops up, I set some values, and I want to report those values to the user through the TEdits. So on returning from the Modal screen,
I execute
editn.text := whateveritis;
I then say
editn.repaint;
Nothing happens. I say TTabItem.repaint. Nothing happens. I click the other TTabIem and then come back to the first TabItem and, voila, the Edit control contains the right information. So my editn.text := whateveritis must be working (that's the only write to the TEdit), but I can't get the blinkin' control to show the result without going off-tab. How do I get it to redisplay as soon as I change the content? Do I need to write an OnChange routine that is one line, self.repaint? Seems ugly, and I'd hope there's a more global approach. Suggestions?
In light of initial comments, let me give more details. Setup: In main screen, drop in tabcontrol, and in tabcontrol drop in 2 tabitems. In tabitem1, drop in a timageviewer and 4 tedits (plus other stuff, probably irrelevant). Image gets dropped into the imageviewer (and displays correctly). The onclick event activates the following (ellipsis cuts out irrelevant code):
procedure TSCKMain.ImageViewer1Click(Sender: TObject);
var
lochold, scrollhold : tpoint;
backfromwavform : tmodalresult;
begin
lochold.X := mouseloccallback.x;
lochold.Y := mouseloccallback.y;
scrollhold.X := round(imageviewer1.ViewportPosition.X);
scrollhold.Y := round(imageviewer1.ViewportPosition.Y);
…
repeat backfromwavform := Wavform.Showmodal until backfromwavform<>mrnone;
case backfromwavform of
mrOK : begin {blue end}
Specsingle.BlueEnd.X := lochold.X;
Specsingle.BlueEnd.y := lochold.y;
edit13.Text := inttostr(Specsingle.BlueEnd.X);
Edit14.Text := inttostr(Specsingle.BlueEnd.y);
PublicWindowFlag := 'RePlot';
end;
mrContinue : begin {red end}
Specsingle.RedEnd.X := lochold.X;
Specsingle.RedEnd.y := lochold.y;
edit15.Text := inttostr(Specsingle.RedEnd.X);
Edit16.Text := inttostr(Specsingle.RedEnd.y);
PublicWindowFlag := 'RePlot';
end;
…
end;
if PublicWindowFlag<>'Cancel' then
if PublicWindowFlag='RePlot' then
begin
specsingle.RegenImage;
end
else
showmessage('Single image semaphore error. Debug.');
Imageviewer1.scrollto(scrollhold.X-Imageviewer1.viewportposition.X, scrollhold.y-Imageviewer1.ViewportPosition.Y);
end;
The modal screen sends back either mrContinue or mrOK correctly, and the appropriate case executes. However, edit13, edit14, edit15, and edit16 do not change their content. However, if I click over to Tabitem2 and back to Tabitem1, they DO repaint and DO contain the correct characters, which they could only have gotten from the above code. Conclusion: somehow, the edits aren’t repainting independently, but it’s not clear why.
Got it. The canvas for the imageviewer, the canvas for the bitmap in the imageviewer, and the canvas for the parent form are all in play. One must be sure that the canvas is the right one. As soon as scenes got untangled between imageviewer.bitmap and everything else, the edits worked as one would expect.
This happens in all Delphi up to XE3:
Create a form and put a panel on it. Anchor the panel to [akLeft, akTop, akRight, akBottom], but leave space between it and the borders.
Add a button which calls RecreateWnd()
Run the app. Resize the form so that the panel is hidden because it's less than 0 pixels in size due to anchoring. Press the RecreateWnd button.
Resize the form back and note that the panel's anchoring is broken.
As long as I can remember myself using Delphi, anchors were always impossible to use because of this. Resize the form, then dock it: the window is recreated, your layout is broken.
I wonder if there's some sort of workaround?
Update
Two workarounds are available in the comments, one proven and stable but with form blinking, another one experimental but potentially more thorough and clean.
I'm not going to vote for either one for a while, as one of those is mine and I'm not even sure it is stable. Instead, I'll wait for some public input.
The two options I have used of which neither is really ideal for problems with the bottom and right anchors are:
Make the window big again before calling or causing to be called RecreateWnd();, then make it small again. Has to be visible before you make it small again however.
Set the Form's constraints so it can't be re-sized so small that stuff ends up hidden.
An example that flashes the larger form, use height and width large values enough so the panel is not hidden:
procedure TForm1.Button1Click(Sender: TObject);
Var
OldWidth, OldHeight : integer;
begin
OldWidth := Form1.Width;
OldHeight := Form1.Height;
Form1.Visible := false;
Form1.Width := 1000;
Form1.Height := 800;
RecreateWnd();
Form1.Visible := true;
Form1.Width := OldWidth;
Form1.Height := OldHeight;
end;
Turns out that the function which breaks everything is UpdateAnchorRules. TControl stores FOriginalParentSize and it's own original size in FAnchorRules, and uses that to auto-resize as parent resizes. UpdateAnchorRules() takes current parent size and current control Width and Height and saves those into FOriginalParentSize and FAnchorRules.
If everything worked properly that would have no effect during normal resizes, as the control and it's parent change size in accord.
But when the control Width is less than zero due to anchoring, Windows, and consequently Delphi still considers it 0. If UpdateAnchorRules is called at that point, it saves wrong, out-of-accord 0 value for original width. After this the layout is beyond repair.
(If it's not called, Width continues to be updated in proper relation to parent Width due to original sizes preserved)
Turns out anything which involves creating a window handle calls UpdateAnchorRules twice: first in WinAPI CreateWindow as it dispatches WM_SIZE before returning (and WM_SIZE handler calls UpdateAnchorRules), and second, explicitly in CreateHandle after handle creation.
It would seem that as long as we can disable UpdateAnchorRules for the duration of CreateHandle, we will succeed. But there are explicit calls to UpdateAnchorRules in CreateHandle, which means someone thought there needs to be an adjustment of Anchor rules after handle creation.
So perhaps I'm missing something, and by disabling it something will break?
Anyway, there are two ready ways to disable UpdateAnchorRules: to set FAnchorMove or to set csLoading. First one is no good as there's code which clears it midway through RecreateWnd and then calls UpdateAnchorRules again.
Second one works and here's a solution:
type
TComponentHack = class helper for TComponent
public
procedure SetCsLoading(Value: boolean);
end;
procedure TComponentHack.SetCsLoading(Value: boolean);
var i: integer;
begin
if Value then
Self.FComponentState := Self.FComponentState + [csLoading]
else
Self.FComponentState := Self.FComponentState - [csLoading];
for i := 0 to Self.ComponentCount-1 do
if Self.Components[i] is TControl then
TControl(Self.Components[i]).SetCsLoading(Value);
end;
procedure SafeRecreateWnd();
begin
MyControl.SetCsLoading(true);
try
MyControl.RecreateWnd(); //or any operation which triggers it -- such as docking or making the window visible first time after RecreateWnd()
finally
MyControl.SetCsLoading(false);
end;
end;
Disclaimer:
I have no idea what else will be broken by running TControl operations with csLoading set.
Better alternative would be to hook UpdateAnchorRules procedure and add another flag check specifically for this purpose, but that'd require either reimplementing UpdateAnchorRules completely (prone to breaking on different versions of Delphi with different original UpdateAnchorRules) or inventing some way to call original UpdateAnchorRules which is usually destroyed by rewriting it with a hook.
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.
I am trying to make an analog clock, wherein I would like to make my 'seconds' Line Invisible when the Seconds Changes. I have tried to set the Pen mode to pmNotCopy but it only gives Inverse of Pen Color. What Property must be set in this Form1.Canvas.Pen.Mode:=<Blank> so that My Line Disappears?
Any other Ideas are also appreciated.
Thanks
Modern computers are very fast. If I were you, I'd definitely draw the entire clock from scratch every second. Problem solved. In addition, if you need anti-aliasing, then a simple pen mode trick will never work.
(If you are not using a remote desktop, you might want to employ double-buffering.)
I don't know anything about delphi but just some out if the box thinking:
couldn't you change the color of your line to the background color, making it 'invisible'
You were close. You need to use pmXOR.
Try this:
Create a new Delphi VCL Forms application. Drop a TButton on the bottom of the form (Button1).
Add the code below to the Button1Click event.
Run the application. Click the button, and three parallel lines will be drawn across the top. Click the button again, and the three lines will disappear.
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := clGreen;
for i := 1 to 3 do
begin
Canvas.MoveTo(50, i * 20);
Canvas.LineTo(Width - 50, i * 20);
end;
end;
All drawing should normally be done in the OnPaint event; I'm intentionally drawing in the Button1Click event for demonstration reasons only.
Just as a note: You should never use Form1 inside an event handler for that form. Referencing the Form1 variable prevents you from creating more than one instance of the form. Use Self instead, and that will automatically refer to the instance the code is running in.