Delphi: How to use ShowWindow properly on external application

11,702

Solution 1

Here is a complete project, which keeps running only one instance of the application, and which should bring already running instance window to front.

You can download a testing project or try the code, which follows:

Project1.dpr

program Project1;

uses
  Forms,
  Windows,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

var
  Mutex: THandle;
const
  AppID = '{0AEEDBAF-2643-4576-83B1-8C9422726E98}';
begin
  MessageID := RegisterWindowMessage(AppID);

  Mutex := CreateMutex(nil, False, AppID);
  if (Mutex <> 0) and (GetLastError = ERROR_ALREADY_EXISTS) then
  begin
    PostMessage(HWND_BROADCAST, MessageID, 0, 0);
    Exit;
  end;

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StrUtils, StdCtrls;

type
  TForm1 = class(TForm)
  private
    function ForceForegroundWindow(WndHandle: HWND): Boolean;
    function ForceRestoreWindow(WndHandle: HWND; Immediate: Boolean): Boolean;
  protected
    procedure WndProc(var AMessage: TMessage); override;
  end;

var
  Form1: TForm1;
  MessageID: UINT;

implementation

{$R *.dfm}

{ TForm1 }

function TForm1.ForceForegroundWindow(WndHandle: HWND): Boolean;
var
  CurrThreadID: DWORD;
  ForeThreadID: DWORD;
begin
  Result := True;
  if (GetForegroundWindow <> WndHandle) then
  begin
    CurrThreadID := GetWindowThreadProcessId(WndHandle, nil);
    ForeThreadID := GetWindowThreadProcessId(GetForegroundWindow, nil);
    if (ForeThreadID <> CurrThreadID) then
    begin
      AttachThreadInput(ForeThreadID, CurrThreadID, True);
      Result := SetForegroundWindow(WndHandle);
      AttachThreadInput(ForeThreadID, CurrThreadID, False);
      if Result then
        Result := SetForegroundWindow(WndHandle);
    end
    else
      Result := SetForegroundWindow(WndHandle);
  end;
end;

function TForm1.ForceRestoreWindow(WndHandle: HWND;
  Immediate: Boolean): Boolean;
var
  WindowPlacement: TWindowPlacement;
begin
  Result := False;
  if Immediate then
  begin
    WindowPlacement.length := SizeOf(WindowPlacement);
    if GetWindowPlacement(WndHandle, @WindowPlacement) then
    begin
      if (WindowPlacement.flags and WPF_RESTORETOMAXIMIZED) <> 0 then
        WindowPlacement.showCmd := SW_MAXIMIZE
      else
        WindowPlacement.showCmd := SW_RESTORE;
      Result := SetWindowPlacement(WndHandle, @WindowPlacement);
    end;
  end
  else
    Result := SendMessage(WndHandle, WM_SYSCOMMAND, SC_RESTORE, 0) = 0;
end;

procedure TForm1.WndProc(var AMessage: TMessage);
begin
  inherited;
  if AMessage.Msg = MessageID then
  begin
    if IsIconic(Handle) then
      ForceRestoreWindow(Handle, True);
    ForceForegroundWindow(Application.Handle);
  end;
end;

end.

Tested on OS versions:

  • Windows 8.1 64-bit
  • Windows 7 SP1 64-bit Home Premium
  • Windows XP SP 3 32-bit Professional

Known issues and limitations:

  • The MainFormOnTaskbar is not taken into account at all; it must be set to True at this time

Solution 2

You're asking your Main form to show, but it may occur the application hidden window itself is minimized when you minimize the application to the task bar, in case of MainFormOnTaskBar being false.

Don't call the ShowWindow method from the oustide. IMHO it's better if you pass a message to the application and respond from inside, calling the Application.Restore` method, which performs the proper ShowWindow calls among other things.

Solution 3

This is a very common problem with VCL apps, and has been asked and answered many many times in the Borland/CodeGear/Embarcadero forums over the years. Using ShowWindow() in this manner does not work for VCL windows very well because of the way the MainForm interacts with the TApplication object at runtime, especially in different versions of Delphi. What you should do instead is have the second instance send a custom message to the first instance, and then let the first instance restore itself as needed when it receives the message, such as by setting its MainForm.WindowState property, or calling Application.Restore(), etc, and let the VCL work out the details for you, like @jachguate suggested.

Solution 4

The following works well for me. I'm not 100% certain I have fully understood the question though, so do let me know if I've got it wrong.

var
  _PreviousHandle: HWND;
  WindowPlacement: TWindowPlacement;
....
WindowPlacement.length := SizeOf(WindowPlacement);
GetWindowPlacement(_PreviousHandle, WindowPlacement);
if WindowPlacement.flags and WPF_RESTORETOMAXIMIZED<>0 then
  WindowPlacement.showCmd := SW_MAXIMIZE
else
  WindowPlacement.showCmd := SW_RESTORE;
SetWindowPlacement(_PreviousHandle, WindowPlacement);
SetForegroundWindow(_PreviousHandle);

Note that the correct type for _PreviousHandle is HWND and not THandle.

Share:
11,702
ertx
Author by

ertx

$C00000FD

Updated on June 05, 2022

Comments

  • ertx
    ertx almost 2 years

    See also:
    How can I tell if another instance of my program is already running?

    i use the following code before starting my application, to check if another instance of it is already started:

    var _PreviousHandle : THandle;
    begin
      _PreviousHandle := FindWindow('TfrmMainForm',nil);
      if _PreviousHandle <> 0 then
      begin
        ShowMessage('Application "" is already running!');
        SetForegroundWindow(_PreviousHandle);
        ShowWindow(_PreviousHandle, SW_SHOW);
        Application.Terminate;
        Exit;
      end;
    ...
    

    However, if it has started, i need to show that application. The problem is after it is shown in this way the minimize button no longer works, and when i click the icon in the taskbar, it "unminimizes" and the animation that is shown is as if it was minimized. Am i missing something? is there a proper way to activate and show external application while it's minimized?

  • ertx
    ertx over 11 years
    SW_RESTORE always "re-sized" version of the application's main window, It's not quite the expected behavior.
  • jachguate
    jachguate over 11 years
    @ertx What is the expected behavior?
  • ertx
    ertx over 11 years
    To see the other program the way as if it was activated from user. It doesn't matter which ShowWindow() command i use, the form is activated, but the windows thinks it's still minimized, so the minimize button does not work.
  • bummi
    bummi over 11 years
    _PreviousHandle := FindWindow('TfrmMainForm',nil); if _PreviousHandle <> 0 then begin ShowMessage('Application "" is already running!'); ShowWindow(_PreviousHandle, SW_RESTORE); SetForegroundWindow(_PreviousHandle); end else begin Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TfrmMainForm, frmMainForm); Application.Run; end;
  • ertx
    ertx over 11 years
    @bummi, i am sorry but your solution does not work either, minimize key still remains inactive
  • ertx
    ertx over 11 years
    It does the same as my code. Seems that the problem is not with acquiring handle in a different way, but calling ShowWindow from a different application. (minimize button remains inactive)
  • TLama
    TLama over 11 years
    What OS do you have ? I've tested this on Windows 7 and it seems to work fine.
  • ertx
    ertx over 11 years
    This issue persists on Windows 7 Professional, not administrator user (that might have some affect)
  • David Heffernan
    David Heffernan over 11 years
    @ertx My updated answer addresses the point you made in your first comment.
  • jachguate
    jachguate over 11 years
    Sertac, TApplication.Restore takes into account MainFormOnTaskBar and ShowMainForm, so it's better to resort to it.
  • jachguate
    jachguate over 11 years
    @Sertac, I added a clarification, thanks.
  • TLama
    TLama over 11 years
    Try the project from the update...
  • ertx
    ertx over 11 years
    Thank you, i think my mutex didn't work because i didn't had 'MainFormOnTaskbar' set to true, well i thought i did, but that seems to have fixed everything for me. Also good point about using GUID for uniqueness.
  • Server Overflow
    Server Overflow over 10 years
    @TLama - Most complete answer as usually!