Recent

Author Topic: Setting Focus on own Application  (Read 17795 times)

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Setting Focus on own Application
« on: December 30, 2016, 05:28:53 pm »
Hi experts,

I am seeking your help to overcome the following problem: I used the UniqueInstance library to ensure that only one instance of my application runs. In my experience, the main reason for a user starting a second instance is that the program is either hidden behind something else, or minimized to the taskbar. So to make uniqueinstance work like one can expect, I used the OnOtherInstance Event to bring the running instance back on screen.

Unfortunately, Microsoft has made "stealing" the focus from an application hard, even if, in my case, this is the best solution for a common user problem. Without a little hack with formstyle my application would neither come back from the Taskbar, nor would it get into the foreground, instead the Taskbar icon would start to blink.

The code I have now brings my application to the foregound, may it be minimized or just hidden behind another application, but it won't get the focus.

This problem has been widely described for Delphi, and there were two solutions suggested, using "SetActiveWindow" or "SetForegroundWindow" (Unit: LCLIntf). Both of them don't seem to do anything.

Question: how can I get my application window focused after I restored it and moved it to the foreground?

Any ideas welcome,

Armin.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Form1_OnCreate(Sender: TObject);
  2.  
  3. begin
  4.   // Application.restore hack #1
  5.   Self.formstyle := fsStayOnTop;
  6. end;
  7.  
  8. procedure TForm1.UniqueInstance1_OnOtherInstance(Sender: TObject;
  9.   ParamCount: Integer; Parameters: array of String);
  10.  
  11. begin
  12.   // Revert Application.restore hack #1
  13.   Self.formstyle := fsNormal;
  14.   // Restore and bring to front - works
  15.   Application.Restore;
  16.   Application.BringToFront;
  17.   // move focus - neither call does do anything
  18.   SetActiveWindow(Application.MainForm.Handle);
  19.   SetForegroundWindow(Application.MainForm.Handle);
  20. end;


Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

Cyrax

  • Hero Member
  • *****
  • Posts: 836
Re: Setting Focus on own Application
« Reply #1 on: January 01, 2017, 04:28:11 pm »
How about change
Code: Pascal  [Select][+][-]
  1. SetActiveWindow(Application.MainForm.Handle);
to
Code: Pascal  [Select][+][-]
  1. SetActiveWindow(Self.Handle);
?

Your TForm1 class instance is normally the main form.

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #2 on: January 08, 2017, 07:00:43 pm »
Hm, seems this doesn't do anything. If the Application is minimized, it stays minimized, if it is hidden behind some other application, it does not come to the foreground.

Tested with and without the "Formstyle" Hack, just to make sure.

Did you ever test this with Windows 10, or any other Windows Version, and find your code working?

Armin
« Last Edit: January 08, 2017, 07:02:16 pm by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #3 on: January 08, 2017, 07:38:05 pm »
Finally, I got a working solution (Windows 7 and Windows 10 tested OK).

If one looks at the documenations for the SetForegroundWindow in Microsft MSDN, there is a "Remarks" section with a list of restrictions, when the command would work, and when it would not. There is a note that SetForegroundWindow would work, if "The process received the last input event." So I send my own process a dummy input, and gee, now everything works like expected, when OnOtherInstance fires, my application will either restore itself from the Taskbar, and/or come to the foreground and grab the input focus.

Here is the code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Form1_OnCreate(Sender: TObject);
  2.  
  3. begin
  4.     // Application.restore hack #1
  5.   Self.formstyle := fsStayOnTop;
  6. end;
  7.  
  8. procedure TForm1.UniqueInstance1_OnOtherInstance(Sender: TObject;
  9.   ParamCount: Integer; Parameters: array of String);
  10.  
  11. var
  12.   I: LPInput;
  13.  
  14. begin
  15.   // Revert Application.restore hack #1
  16.   Self.formstyle := fsNormal;
  17.   // Restore and bring to front
  18.   Application.Restore;
  19.   Application.BringToFront;
  20.   // Grab focus
  21.   I := nil;
  22.   try
  23.     // Hack #2: send myself some dummy input
  24.     GetMem(I,SizeOf(Input));
  25.     FillChar(I^, SizeOf(Input),$00); // empty input structure
  26.     SendInput(1, I, SizeOf(Input));
  27.     SetForegroundWindow(Application.MainForm.Handle);
  28.   finally
  29.     if I <> nil then FreeMem(I,SizeOf(Input));
  30.   end;
  31. end;
  32.  

Summary: Windows does not honor the formstyle attribute, so at first glance it is pointless to set it to any value. Application.Restore will just flash the application icon but not restore it, and Application.BringToFront and SetForegroundWindow (grab Focus) won't work at all either.

Hack #1: If the formstyle attribute is set to fsStayOnTop initally, the Window won't stay on top, but if the attribute is set to fsNormal later, Application.Restore and Application.BringToFront work like expected.

Hack #2: the "dummy input" hack entitles my App to call SetForegroundWindow to grab the focus.

Tested and found working with Windows 10 x86 and Windows 7 x64, Lazarus 1.6.

Armin.
« Last Edit: January 09, 2017, 01:57:51 pm by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Setting Focus on own Application
« Reply #4 on: January 10, 2017, 01:33:19 am »
According to the documentation https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539%28v=vs.85%29.aspx
Quote
A process can set the foreground window only if one of the following conditions is true:
    The process is the foreground process.
    The process was started by the foreground process.
    The process received the last input event.
    There is no foreground process.
    The process is being debugged.
    The foreground process is not a Modern Application or the Start Screen.
    The foreground is not locked (see LockSetForegroundWindow).
    The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
    No menus are active.
You do third case.
Better, in my opinion, to fix UniqueInstance:
Code: Pascal  [Select][+][-]
  1. {$IFDEF WINDOWS}
  2. function AllowSetForegroundWindow(dwProcessId: LongInt): BOOL; stdcall;
  3.   external user32;
  4. {$ENDIF}
  5. //...
  6. procedure TUniqueInstance.Loaded;
  7. //...
  8.         IPCClient.Active := True;
  9.         {$IFDEF WINDOWS} AllowSetForegroundWindow(-1); {$ENDIF} // Lines added
  10. //...
Because the second instance corresponds to the requirements of case 1, it allows any app to make SetForegroundWindow, for example first instance.

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #5 on: January 10, 2017, 11:27:40 pm »
Hi Serge,

thanks for that nice piece of elegant code ... I modified UniqueInstance like you suggested, and it works like a charm, without all that "hack" stuff I had before.

Unfortunately I spent today having another weird WIndows problem (did I mention that at times I really hate this time wasting, bloated, cryptic, awful, ill documented,  >:( >:( >:().

A problem occurs if the first instance has a modal MessageBox open, neither I can bring it to the front, not can I change focus to the messagebox. there are the variants I have tried:

Code: Pascal  [Select][+][-]
  1.      // restore from taskbar
  2.      Application.Restore;
  3.      // bring to front
  4.      Application.BringToFront;
  5.      // give focus to topmost window
  6.      // hMyWindow := GetForegroundWindow(); returns 0 if messagebox on top
  7.      // hMyWindow := GetActiveWindow(); returns other instance handle
  8.      hTopWindow := GetTopWindow(hMyWindow); // returns handle of explorer
  9.      if hTopWindow <> NULL then SetForegroundWindow(hTopWindow);
  10.  

Do you have an idea what I can do? Think it should be possible to code this, since an application having a modal dialog box open can be brought to the foreground using Task Manager (or Alt-Tab), so there must be a way to find the Messagebox and pass the focus to it.

Any clues welcome,

Armin.
« Last Edit: January 10, 2017, 11:30:37 pm by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Setting Focus on own Application
« Reply #6 on: January 11, 2017, 08:47:12 pm »
A problem occurs if the first instance has a modal MessageBox open, neither I can bring it to the front, not can I change focus to the messagebox. there are the variants I have tried:
Try this code:
Code: Pascal  [Select][+][-]
  1. uses InterfaceBase;
  2. //...
  3. var
  4.   AppWnd: HWND;
  5. //...
  6.   AppWnd := Widgetset.AppHandle;
  7.   if IsIconic(AppWnd) then
  8.     Application.Restore;
  9.   SetForegroundWindow(AppWnd);
  10.  

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #7 on: January 17, 2017, 09:00:22 pm »
Hi Serge,

putting all your valuable input together, I did, indeed, get my perfect version of UniqueInstance running.

Thanks very much for your help,

Armin.
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

CM630

  • Hero Member
  • *****
  • Posts: 1082
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Setting Focus on own Application
« Reply #8 on: May 10, 2018, 10:18:51 am »

Finally, I got a working solution (Windows 7 and Windows 10 tested OK).
...
Here is the code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Form1_OnCreate(Sender: TObject);
  2.  
  3. begin
  4.     // Application.restore hack #1
  5.   Self.formstyle := fsStayOnTop;
  6. end;
  7.  
  8. procedure TForm1.UniqueInstance1_OnOtherInstance(Sender: TObject;
  9.   ParamCount: Integer; Parameters: array of String);
  10.  
  11. var
  12.   I: LPInput;
  13.  
  14. begin
  15.   // Revert Application.restore hack #1
  16.   Self.formstyle := fsNormal;
  17.   // Restore and bring to front
  18.   Application.Restore;
  19.   Application.BringToFront;
  20.   // Grab focus
  21.   I := nil;
  22.   try
  23.     // Hack #2: send myself some dummy input
  24.     GetMem(I,SizeOf(Input));
  25.     FillChar(I^, SizeOf(Input),$00); // empty input structure
  26.     SendInput(1, I, SizeOf(Input));
  27.     SetForegroundWindow(Application.MainForm.Handle);
  28.   finally
  29.     if I <> nil then FreeMem(I,SizeOf(Input));
  30.   end;
  31. end;
  32.  


That won't compile, because it cannot find LPInput. I have searched the entire Lazarus for it, but I did not find a reasonable source for it.
Лазар 3,2 32 bit (sometimes 64 bit); FPC3,2,2; rev: Lazarus_3_0 on Win10 64bit.

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #9 on: May 10, 2018, 11:44:39 am »
Hi,

I cannot remember either, must be from a library wrapping the sendinput windows API. I have abandoned the hacky approach to grab the focus shortly after, in favor of this one:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.UniqueInstanceOtherInstance(Sender: TObject;
  2.   ParamCount: integer; Parameters: array of string);
  3.  
  4. // Instance Uniqueness handler. Brings own instance to foreground
  5. // Requires: Windows, InterfaceBase
  6.  
  7. var
  8.   AppWnd: HWND;
  9.   fsOld: TFormStyle;
  10.  
  11. begin
  12.   try
  13.     // Application.restore hack #1
  14.     fsOld := Self.FormStyle;
  15.     Self.FormStyle := fsStayOnTop;
  16.     Self.FormStyle := fsOld;
  17.     AppWnd := Widgetset.AppHandle;
  18.     if IsIconic(AppWnd) then
  19.     begin
  20.       // Restore from taskbar
  21.       Application.Restore;
  22.     end;
  23.     // Bring to front & set focus
  24.     SetForegroundWindow(AppWnd);
  25.   except
  26.     on e: Exception do
  27.       ;
  28.   end;
  29. end;
  30.  

Does this snippet help?

If not ... I am quite sure I can dig up my sources from 2017 in my Subversion server if you still need it, please let me know.

Greetz, Armin.
« Last Edit: May 10, 2018, 11:53:42 am by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

CM630

  • Hero Member
  • *****
  • Posts: 1082
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Setting Focus on own Application
« Reply #10 on: May 10, 2018, 12:19:57 pm »
Thanks!
It sort of works after adding InterfaceBase in uses.
But now my application gets in front and I cannot show another application in front of it, without clicking on my application first.
Лазар 3,2 32 bit (sometimes 64 bit); FPC3,2,2; rev: Lazarus_3_0 on Win10 64bit.

ArminLinder

  • Sr. Member
  • ****
  • Posts: 314
  • Keep it simple.
Re: Setting Focus on own Application
« Reply #11 on: May 10, 2018, 01:30:45 pm »
Interesting problem you have. Never noticed this in my applications, but frankly I am not sure whether this use-case has ever been tried.

Before I fire up Lazarus to check (currently my development environment is not available) ... let me stab in the dark a little ... did you notice the "identifier" (?) property of the UniqueInstance class?

It distinguishes one application from another. As long as there is only one, it won't matter. My first approach would be to check whether the identifiers for both applications are set to different values, a random GUID maybe.

Armin.
« Last Edit: May 10, 2018, 01:37:37 pm by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

CM630

  • Hero Member
  • *****
  • Posts: 1082
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: Setting Focus on own Application
« Reply #12 on: May 10, 2018, 03:30:22 pm »
It will take some time to understand you.
Meanwhile, I found that these line do nothing but cause a single flicker of the screen:


Code: Pascal  [Select][+][-]
  1. fsOld := Self.FormStyle;
  2. Self.FormStyle := fsStayOnTop;
  3. Self.FormStyle := fsOld;

App stays in front of all the rest without them.


EDIT: I have found the reason for the misbehaviour. I have left
Code: Pascal  [Select][+][-]
  1.    // Application.restore hack #1
  2.   Self.formstyle := fsStayOnTop;
in Form.Create (from attempting to use the first code).

« Last Edit: May 11, 2018, 08:01:43 am by CM630 »
Лазар 3,2 32 bit (sometimes 64 bit); FPC3,2,2; rev: Lazarus_3_0 on Win10 64bit.

 

TinyPortal © 2005-2018