Recent

Author Topic: How to bring a Form in front over all programs and give it the focus?  (Read 3087 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 910
My program mostly runs in the background. But if some event is detected, it shall:
a) display a Form with a message and
b) display this Form over all other windows of all other programs and
c) receive the focus (so that e.g. keystrokes are noticed by my program).

What I have is this:
Code: Pascal  [Select][+][-]
  1. if event_detected then
  2.    begin
  3.    Form1.Show; {calls Form1.BringToFront}
  4.    Form1.FormStyle:=fsStayOnTop;
  5.    Form1.SetFocus; {does not work}
  6.    Form1.Label1.Caption:='This is my message';
  7.    end;

This code has 3 disadvantages:
1) If my Form had been hidden by a Button via "Form1.Hide" and then the next message shall be displayed, my Form gets visible, but does not receive the focus.
2) If my Form had been minimized and then the next message shall be displayed, my Form also gets visible, but does not receive the focus.
3) If my Form had been moved manually half behind another window and then the next message shall be displayed, my Form stays half behind the other window (does not come to front).

What I do not need (and not want) is, that after my Form has come in front, that it "stays" in front permanently, even if another window is moved over it.

I use Lazarus 2.0.10 with FPC 3.2.0 on Linux Ubuntu 22.04. Same results with Lazarus 2.2.4 and FPC 3.2.2. How can I solve the 3 disadvantages? Thanks in advance.

EDIT:
Meanwhile I tested my program on Windows and there everything is fine. So the problem is only on Linux.
« Last Edit: November 16, 2023, 06:19:15 pm by Hartmut »

Handoko

  • Hero Member
  • *****
  • Posts: 5458
  • My goal: build my own game engine using Lazarus
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #1 on: November 15, 2023, 01:54:30 pm »
Have you tried TForm.BringToFront ?

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #2 on: November 15, 2023, 02:29:00 pm »
This does not work when currently something is in edit mode.

uses LCLType, LCLIntf ...
Code: Pascal  [Select][+][-]
  1. procedure MakeFormVisible(const AForm: TForm);
  2. var
  3.   Rect: TRect;
  4.   ScreenWidth, ScreenHeight,
  5.   WindowWidth, WindowHeight,
  6.   NewX, NewY: Integer;
  7. begin
  8.   AForm.Position := poDesigned;
  9.   if IsIconic(AForm.Handle) then
  10.     ShowWindow(AForm.Handle, SW_RESTORE);
  11.   GetWindowRect(AForm.Handle, Rect);
  12.   WindowWidth := Rect.Right - Rect.Left;
  13.   WindowHeight := Rect.Bottom - Rect.Top;
  14.   ScreenWidth := Screen.Width;
  15.   ScreenHeight := Screen.Height;
  16.   NewX := (ScreenWidth - WindowWidth) div 2;
  17.   NewY := (ScreenHeight - WindowHeight) div 2;
  18.   SetWindowPos(AForm.Handle, HWND_TOP, NewX, NewY, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
  19.   SetForegroundWindow(AForm.Handle);
  20.   SetFocus(AForm.Handle);
  21. end;
« Last Edit: November 15, 2023, 02:38:10 pm by KodeZwerg »
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

Hartmut

  • Hero Member
  • *****
  • Posts: 910
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #3 on: November 15, 2023, 02:51:15 pm »
Have you tried TForm.BringToFront ?

Thank you Handoko. Yes:

If I use "Form1.BringToFront" without other commands before, then I get an error message "[TCustomForm.SetFocus] Form1:TForm1 Can not focus".

If I put a "Form1.Show" before "Form1.BringToFront", then my Form opens each time minimized, after it had been hidden by a Button via "Form1.Hide".

If I put a "Form1.Show" and a "Form1.FormStyle:=fsStayOnTop" before "Form1.BringToFront", then I have the same behaviour as without the "Form1.BringToFront" (see 3 disadvantages above).

Now I will check the suggestion from KodeZwerg...

Hartmut

  • Hero Member
  • *****
  • Posts: 910
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #4 on: November 15, 2023, 03:08:10 pm »
Thank you KodeZwerg for your suggestion. Unfortunately it does not work for me:

If I use your suggestion without other commands, then my Form is *not* displayed at all.

If I use your suggestion after a "Form1.Show", then my Form opens each time minimized, after it had been hidden by a Button via "Form1.Hide".

If I use your suggestion after a "Form1.Show" and a "Form1.FormStyle:=fsStayOnTop", then I have the same behaviour as without your suggestion (see 3 disadvantages above).

Have you seen that I'm on Linux?

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #5 on: November 15, 2023, 03:25:28 pm »
Here is my complete test, on a button press the form gets minimized and as soon the timer is fired, it is:
- visible
- centered
- on top of others
- got focus

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   LCLType, LCLIntf,
  9.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     Button1: TButton;
  17.     Timer1: TTimer;
  18.     procedure Button1Click(Sender: TObject);
  19.     procedure Timer1Timer(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure MakeFormVisible(const AForm: TForm);
  36. var
  37.   Rect: TRect;
  38.   ScreenWidth, ScreenHeight,
  39.   WindowWidth, WindowHeight,
  40.   NewX, NewY: Integer;
  41. begin
  42.   AForm.Position := poDesigned;
  43.   if IsIconic(AForm.Handle) then
  44.     ShowWindow(AForm.Handle, SW_RESTORE);
  45.   GetWindowRect(AForm.Handle, Rect);
  46.   WindowWidth := Rect.Right - Rect.Left;
  47.   WindowHeight := Rect.Bottom - Rect.Top;
  48.   ScreenWidth := Screen.Width;
  49.   ScreenHeight := Screen.Height;
  50.   NewX := (ScreenWidth - WindowWidth) div 2;
  51.   NewY := (ScreenHeight - WindowHeight) div 2;
  52.   SetWindowPos(AForm.Handle, HWND_TOP, NewX, NewY, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
  53.   SetForegroundWindow(AForm.Handle);
  54.   SetFocus(AForm.Handle);
  55. end;
  56.  
  57. procedure TForm1.Button1Click(Sender: TObject);
  58. begin
  59.   Self.WindowState := wsMinimized;
  60.   Timer1.Enabled := True;
  61. end;
  62.  
  63. procedure TForm1.Timer1Timer(Sender: TObject);
  64. begin
  65.   MakeFormVisible(Self);
  66.   Timer1.Enabled := False;
  67. end;
  68.  
  69. end.
Have you seen that I'm on Linux?
I did do the crossplatform way I thought.

Build a sample app where I can reproduce
a) what you do
b) how you do
c) why it not work
My sample you have in above source. I did not touched the Form properties at all, just created from scratch by put TTimer and TButton on.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

Hartmut

  • Hero Member
  • *****
  • Posts: 910
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #6 on: November 15, 2023, 04:17:23 pm »
Thank you KodeZwerg for your demo and for trying to help me:

Your code works for me, if I press your button and then don't click anything and only wait, until your Form becomes visible again.

Problem 1:
For the next test I increased the Timer to 3 seconds: if I press your button and then click onto another window and then wait, until your Form becomes visible, then your Form does not have the focus. In my application it can last some hours, until the event to display a message occurs. In this time the user will click a lot of other windows.

Problem 2:
In my application my Form is hidden by a Button via command "Form1.Hide", not by minimizing the Form via "Self.WindowState := wsMinimized", as you did. So I changed your code to:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3. // Self.WindowState := wsMinimized;
  4.   Self.Hide;
  5.   Timer1.Interval:=1000;
  6.   Timer1.Enabled := True;
  7. end;
Now, when I click your button (and don't click anything and only wait), your Form does never appear again.

Problem 3:
To solve problem 2, I modified your code to:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Timer1Timer(Sender: TObject);
  2. begin
  3.   self.Show;
  4.   MakeFormVisible(Self);
  5.   Timer1.Enabled := False;
  6. end;
Now it works, but only, if I don't click in the waiting time somewhere else. If I click during the waiting time onto another window, then this window will cover (hide) your Form completely or partly, depending on the position of your Form.

Can you reproduce the 3 problems?

440bx

  • Hero Member
  • *****
  • Posts: 5577
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #7 on: November 15, 2023, 04:34:00 pm »
I just wanted to mention that, generally speaking, OSs (definitely Windows) don't look kindly at an application that simply wants to steal the focus. 

This is because it's very disconcerting for a user to be working in some window and suddenly have another window steal the input.

In the situation you've described, and presuming I understood correctly what you're trying to do, I would show the window with "stay on top" enabled but nothing else.  _After_ the user clicks on the window, either to move it out of the way or do something else with it, I'd remove the "stay on top" attribute for it to behave like a normal window.

The important thing is this: I would not try to steal the focus from whatever window has it at the time because, it often doesn't work (the OS doesn't like it) and, it is "impolite" towards the user who suddenly sees his/her input sent to a window that popped in the way.

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v4.0rc3) on Windows 7 SP1 64bit.

Hartmut

  • Hero Member
  • *****
  • Posts: 910
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #8 on: November 15, 2023, 04:47:28 pm »
Hello 440bx, thank you for your thoughts. I do agree, that "stealing" the focus could be a problem to the user. I will reflect my plan. But independent from that I would like to learn, how this could be done, if it is possible with not to high efforts.

440bx

  • Hero Member
  • *****
  • Posts: 5577
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #9 on: November 15, 2023, 06:20:04 pm »
Hello 440bx, thank you for your thoughts. I do agree, that "stealing" the focus could be a problem to the user. I will reflect my plan. But independent from that I would like to learn, how this could be done, if it is possible with not to high efforts.
You're welcome.

As far as how it can be done, I've never found a way that was foolproof (mostly hacks) and, only for Windows at that.  Bottom line, I cannot be of much help (if any at all) in this case.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v4.0rc3) on Windows 7 SP1 64bit.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #10 on: November 15, 2023, 06:46:51 pm »
Thank you KodeZwerg for your demo and for trying to help me:

Your code works for me, if I press your button and then don't click anything and only wait, until your Form becomes visible again.

Problem 1:
For the next test I increased the Timer to 3 seconds: if I press your button and then click onto another window and then wait, until your Form becomes visible, then your Form does not have the focus. In my application it can last some hours, until the event to display a message occurs. In this time the user will click a lot of other windows.

Problem 2:
In my application my Form is hidden by a Button via command "Form1.Hide", not by minimizing the Form via "Self.WindowState := wsMinimized", as you did. So I changed your code to:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3. // Self.WindowState := wsMinimized;
  4.   Self.Hide;
  5.   Timer1.Interval:=1000;
  6.   Timer1.Enabled := True;
  7. end;
Now, when I click your button (and don't click anything and only wait), your Form does never appear again.

Problem 3:
To solve problem 2, I modified your code to:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Timer1Timer(Sender: TObject);
  2. begin
  3.   self.Show;
  4.   MakeFormVisible(Self);
  5.   Timer1.Enabled := False;
  6. end;
Now it works, but only, if I don't click in the waiting time somewhere else. If I click during the waiting time onto another window, then this window will cover (hide) your Form completely or partly, depending on the position of your Form.

Can you reproduce the 3 problems?

I only can help with the "show" issue, the rest I does not happen here.
Code: Pascal  [Select][+][-]
  1. procedure MakeFormVisible(const AForm: TForm);
  2. var
  3.   Rect: TRect;
  4.   ScreenWidth, ScreenHeight,
  5.   WindowWidth, WindowHeight,
  6.   NewX, NewY: Integer;
  7. begin
  8.   // give possibility to readjust position
  9.   AForm.Position := poDesigned;
  10.   // if its minimized
  11.   if IsIconic(AForm.Handle) then
  12.     ShowWindow(AForm.Handle, SW_RESTORE);
  13.   // if its hidden
  14.   if (not AForm.Visible) then
  15.     AForm.Show;
  16.   // calculate center
  17.   GetWindowRect(AForm.Handle, Rect);
  18.   WindowWidth := Rect.Right - Rect.Left;
  19.   WindowHeight := Rect.Bottom - Rect.Top;
  20.   ScreenWidth := Screen.Width;
  21.   ScreenHeight := Screen.Height;
  22.   NewX := (ScreenWidth - WindowWidth) div 2;
  23.   NewY := (ScreenHeight - WindowHeight) div 2;
  24.   // put window to calculated center
  25.   SetWindowPos(AForm.Handle, HWND_TOP, NewX, NewY, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
  26.   // set it to foreground
  27.   SetForegroundWindow(AForm.Handle);
  28.   // give focus
  29.   SetFocus(AForm.Handle);
  30. end;
I do absolute agree that when a window pop up over my current work plus stealing focus, I would delete that app.
But of course it depend on what kind of application it is.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

ASerge

  • Hero Member
  • *****
  • Posts: 2438
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #11 on: November 15, 2023, 08:14:23 pm »
My program mostly runs in the background. But if some event is detected, it shall:
a) display a Form with a message and
b) display this Form over all other windows of all other programs and
c) receive the focus (so that e.g. keystrokes are noticed by my program).
In Windows
O:-) ShowWindow + SetForegroundWindow. The form flashes on the taskbar to attract attention. Of course the form should have a button on the taskbar (not dialog). The user himself will switch to the window that attracts attention.
>:( AttachThreadInput (to emulate user input to the window thread) + ShowWindow + SetForegroundWindow. In this case, BringToFront and SetFocus are work. After that, I always want to delete such an application.

Hartmut

  • Hero Member
  • *****
  • Posts: 910
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #12 on: November 16, 2023, 12:18:28 pm »
Thank you KodeZwerg for your improvement and the comments. Now problem #2 is solved, but problems #1 and #3 still exist on my Linux system.



Thank you ASerge for your reply. But I have problems to understand, what you want me to do:

In Windows
O:-) ShowWindow + SetForegroundWindow. The form flashes on the taskbar to attract attention. Of course the form should have a button on the taskbar (not dialog). The user himself will switch to the window that attracts attention.
Code: Pascal  [Select][+][-]
  1. ShowWindow(Form1.Handle, SW_RESTORE);
  2. SetForegroundWindow(Form1.handle);
are part of KodeZwerg's code and they don't work (neither with his complete code nor only this 2 commands solely).

Quote
>:( AttachThreadInput (to emulate user input to the window thread) + ShowWindow + SetForegroundWindow. In this case, BringToFront and SetFocus are work. After that, I always want to delete such an application.
Do you want me to use Threads? I never worked with this and I think this effort would be too much for me.



BTW: the only purpose of my application is to inform the user about special events which he explicitly asked to get informed about in a way that generates maximum attention. As I wrote to 440bx:
Quote
I do agree, that "stealing" the focus could be a problem to the user. I will reflect my plan. But independent from that I would like to learn, how this could be done, if it is possible with not to high efforts.

ASerge

  • Hero Member
  • *****
  • Posts: 2438
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #13 on: November 16, 2023, 02:19:53 pm »
are part of KodeZwerg's code and they don't work (neither with his complete code nor only this 2 commands solely).
What equal Form.BorderStyle?
Quote
Do you want me to use Threads? I never worked with this and I think this effort would be too much for me.
No.
In Windows, there is only a single thread connected to the input. It is allowed to do SetFocus for it. You can force the thread of your window (MainThreadID) to be connected to the thread of the active window (possibly someone else's application).

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How to bring a Form in front over all programs and give it the focus?
« Reply #14 on: November 16, 2023, 02:37:13 pm »
How about instead of bugging users by bringing up a Form in an unfriendly way, just display a self closing Message?
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode Delphi}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     procedure Button1Click(Sender: TObject);
  17.   private
  18.     FForm: TForm;
  19.     procedure TimerEvent(Sender: TObject);
  20.     procedure ClickEvent(Sender: TObject);
  21.     procedure ShowOneliner(const AString: string; const AFont: TFont);
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. procedure TForm1.TimerEvent(Sender: TObject);
  34. begin
  35.   if (Sender is TTimer) then
  36.     (Sender as TTimer).Enabled := False;
  37.   FForm.Close;
  38. end;
  39.  
  40. procedure TForm1.ClickEvent(Sender: TObject);
  41. begin
  42.   if ((Sender is TForm) or (Sender is TLabel)) then
  43.     FForm.Close;
  44. end;
  45.  
  46. procedure TForm1.ShowOneliner(const AString: string; const AFont: TFont);
  47. var
  48.   LBMP: TBitmap;
  49.   LLabel: TLabel;
  50.   i, LWidth, LHeight: Integer;
  51.   LTimer: TTimer;
  52. begin
  53.   LBMP := TBitmap.Create;
  54.   try
  55.     LBMP.Canvas.Font := AFont;
  56.     LHeight := LBMP.Canvas.TextHeight(AString);
  57.     LWidth := LBMP.Canvas.TextWidth(AString);
  58.   finally
  59.     LBMP.Free;
  60.   end;
  61.   FForm := TForm.Create(nil);
  62.   try
  63.     FForm.Parent := nil;
  64.     FForm.Visible := False;
  65.     FForm.BorderIcons := [];
  66.     FForm.BorderStyle := bsNone;
  67.     FForm.FormStyle := fsStayOnTop;
  68.     FForm.Width := LWidth + 10;
  69.     FForm.Height := LHeight + 10;
  70.     FForm.Left := (Screen.Width - FForm.Width) div 2;
  71.     FForm.Top := (Screen.Height - FForm.Height) div 2;
  72.     FForm.OnClick := ClickEvent;
  73.     LLabel := TLabel.Create(FForm);
  74.     try
  75.       LLabel.Parent := FForm;
  76.       LLabel.Visible := False;
  77.       LLabel.Left := 5;
  78.       LLabel.Top := 5;
  79.       LLabel.Font := AFont;
  80.       LLabel.Caption := AString;
  81.       LLabel.Visible := True;
  82.       LLabel.OnClick := ClickEvent;
  83.       LTimer := TTimer.Create(FForm);
  84.       try
  85.         LTimer.Interval := 5000;
  86.         LTimer.OnTimer := TimerEvent;
  87.         LTimer.Enabled := True;
  88.       finally
  89.       end;
  90.     finally
  91.       i := FForm.ShowModal;
  92.     end;
  93.   finally
  94.     LLabel.Free;
  95.     LTimer.Free;
  96.     FForm.Free;
  97.   end;
  98. end;
  99.  
  100. procedure TForm1.Button1Click(Sender: TObject);
  101. begin
  102.   ShowOneliner('test...', Self.Font);
  103. end;
  104.  
  105. end.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

 

TinyPortal © 2005-2018