Delphi: How to use ShowWindow properly on external application
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
.
Comments
-
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 over 11 yearsSW_RESTORE always "re-sized" version of the application's main window, It's not quite the expected behavior.
-
jachguate over 11 years@ertx What is the expected behavior?
-
ertx over 11 yearsTo 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 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 over 11 years@bummi, i am sorry but your solution does not work either, minimize key still remains inactive
-
ertx over 11 yearsIt 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 over 11 yearsWhat OS do you have ? I've tested this on Windows 7 and it seems to work fine.
-
ertx over 11 yearsThis issue persists on Windows 7 Professional, not administrator user (that might have some affect)
-
David Heffernan over 11 years@ertx My updated answer addresses the point you made in your first comment.
-
jachguate over 11 yearsSertac, TApplication.Restore takes into account MainFormOnTaskBar and ShowMainForm, so it's better to resort to it.
-
jachguate over 11 years@Sertac, I added a clarification, thanks.
-
TLama over 11 yearsTry the project from the update...
-
ertx over 11 yearsThank 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 over 10 years@TLama - Most complete answer as usually!