Lazarus

Programming => LCL => Topic started by: ArminLinder on December 30, 2016, 05:28:53 pm

Title: Setting Focus on own Application
Post by: ArminLinder 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;


Title: Re: Setting Focus on own Application
Post by: Cyrax 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.
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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.
Title: Re: Setting Focus on own Application
Post by: ASerge 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 (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.
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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.
Title: Re: Setting Focus on own Application
Post by: ASerge 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.  
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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.
Title: Re: Setting Focus on own Application
Post by: CM630 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.
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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.
Title: Re: Setting Focus on own Application
Post by: CM630 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.
Title: Re: Setting Focus on own Application
Post by: ArminLinder 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.
Title: Re: Setting Focus on own Application
Post by: CM630 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).

TinyPortal © 2005-2018