6

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?

Community
  • 1
  • 1
ertx
  • 1,494
  • 2
  • 15
  • 21
  • 7
    you should use `Mutex` to do this check and broadcast mesasge to notify existing instance – teran Jan 21 '13 at 14:32
  • The problem is not how to tell if my program is running, but rather how to activate it properly from another instance of that program. – ertx Jan 21 '13 at 14:42
  • 2
    send broadcast message with known code (`RegisterWindowMessage`), then handle this message in program and let program to restore itself. – teran Jan 21 '13 at 14:45
  • Sorry for that. I've seen the answer in the [`RestoreWindow`](http://stackoverflow.com/a/460480/960757) procedure there. – TLama Jan 21 '13 at 14:46
  • 2
    ertx, please check my answer here : http://stackoverflow.com/a/12949757/800214 – whosrdaddy Jan 21 '13 at 15:04
  • 2
    What would help here would be if you could produce a complete program that would allow us to reproduce what you report. Because a plain vanilla app doesn't behave the way you describe. – David Heffernan Jan 21 '13 at 15:54
  • Right now i am trying to create a Mutex, if that doesn't work i will produce a demo program. – ertx Jan 21 '13 at 16:04

4 Answers4

6

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
TLama
  • 75,147
  • 17
  • 214
  • 392
  • 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) – ertx Jan 21 '13 at 15:04
  • What OS do you have ? I've tested this on Windows 7 and it seems to work fine. – TLama Jan 21 '13 at 15:20
  • This issue persists on Windows 7 Professional, not administrator user (that might have some affect) – ertx Jan 21 '13 at 15:28
  • Try the project from the update... – TLama Feb 02 '13 at 01:58
  • 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. – ertx Feb 04 '13 at 08:09
  • 1
    @TLama - Most complete answer as usually! – Gabriel Dec 06 '13 at 10:07
4

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.

jachguate
  • 16,976
  • 3
  • 57
  • 98
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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • SW_RESTORE always "re-sized" version of the application's main window, It's not quite the expected behavior. – ertx Jan 21 '13 at 14:41
  • @ertx What is the expected behavior? – jachguate Jan 21 '13 at 14:47
  • 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. – ertx Jan 21 '13 at 14:50
  • _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; – bummi Jan 21 '13 at 14:52
  • @bummi, i am sorry but your solution does not work either, minimize key still remains inactive – ertx Jan 21 '13 at 14:58
  • @ertx My updated answer addresses the point you made in your first comment. – David Heffernan Jan 21 '13 at 16:08