5

I have create an application with Delphi XE3. My application have a trayicon (I use TCoolTrayIcon for this) so when the user minimize it there is not a icon on taskbar but only on trayicon.

To avoid more that one istance of my application I use this code:

procedure CreateMutexes(const MutexName: String);
const
  SECURITY_DESCRIPTOR_REVISION = 1;
var
  SecurityDesc: TSecurityDescriptor;
  SecurityAttr: TSecurityAttributes;
  MutexHandle: THandle;
begin
  InitializeSecurityDescriptor(@SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
  SetSecurityDescriptorDacl(@SecurityDesc, True, nil, False);
  SecurityAttr.nLength := SizeOf(SecurityAttr);
  SecurityAttr.lpSecurityDescriptor := @SecurityDesc;
  SecurityAttr.bInheritHandle := False;
  MutexHandle := CreateMutex(@SecurityAttr, False, PChar(MutexName));

  if MutexHandle <> 0 then
    begin
      if GetLastError = ERROR_ALREADY_EXISTS then
        begin
          MessageBox(0, 'You cannot start more than one instance of ContLab.'
                      + #13#10 + 'Use the instance has already started.',
                       'ContLab', mb_IconHand);

          CloseHandle(MutexHandle);
          Halt;
        end
    end;

  CreateMutex(@SecurityAttr, False, PChar('Global\' + MutexName));
end;

In this way when the user start application 2 times he get an error message and the second instance is terminate.

Now I'd like not show the error message but open the main form of first instance of application and terminate the second instance.

Is it possible?

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
Martin
  • 1,065
  • 1
  • 17
  • 36
  • Yes, of course it's possible. What's your real question? – Rob Kennedy Mar 25 '14 at 14:54
  • Found a dupe: http://stackoverflow.com/a/460480/1970843 – GabrielF Mar 25 '14 at 15:14
  • 2
    @KenWhite, your comment is a dupe of GabrielF's comment :-) Anyway this question is about how to open the main form of first instance, while the linked question asks how to detect if another application is running. – LU RD Mar 25 '14 at 17:52
  • 1
    I agree with @Lurd. Although the supposed duplicate includes an answer to this question, they are separate questions. The answer in the other question is a case of *scope creep*. It answers the logical next question (this one), but not particularly *well*. It's much better to keep the two tasks in separate questions. That's why I've voted to reopen this question. – Rob Kennedy Mar 26 '14 at 17:07

1 Answers1

6

You need to send a message to the other application to request that it shows itself.

First of all you need to find the other application's main window. There are many ways to do that. For instance you can use FindWindow. Or you can enumerate the top-level windows with EnumWindows. Typically you'd then check for matching window text and/or class name.

Once you've found the main window of the other instance, you need to give it the ability to set itself to be the foreground window. You need to call AllowSetForegroundWindow.

var
  pid: DWORD;
....
GetWindowThreadProcessId(hwndOtherInstance, pid);
AllowSetForegroundWindow(pid);

Then send the window a user-defined message. For instance:

const
  WM_RESTOREWINDOW = WM_APP;
....
SendMessage(hwndOtherInstance, WM_RESTOREWINDOW, 0, 0);

Finally, your other instance's main form needs to listen for this message.

type
  TMainForm = class(TForm)
  ....
  protected
    procedure WMRestoreWindow(var Message: TMessage); message WM_RESTOREWINDOW;
  ....
  end;

When it encounters the message it must do this:

procedure TMainForm.WMRestoreWindow(var Message: TMessage);
begin
  inherited;
  Visible := True;
  Application.Restore;
  Application.BringToFront;
end;

I'm a little sceptical of your mutex handling code. I don't understand the need for security attributes since you are creating it in the local namespace. But then I see a second call to CreateMutex that ignores the return value, but creates an object in the global namespace.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 2
    You don't need the user-defined messages mechanism. You just need to call ShowWindow and SetForegroundWindow to the first instance's handle (I've linked a dupe with all the code). This solution is too complicated. – GabrielF Mar 25 '14 at 15:20
  • Deleted my answer, your way to send a message to the first app is the way to go. For more on the problems with my now-deleted answer, see @RemyLebeau answer [Delphi: How to use ShowWindow properly on external application](http://stackoverflow.com/a/14446381/576719). Problem with `ShowWindow()` from external application. – LU RD Mar 25 '14 at 15:50
  • @LU RD, even what Remy describes there I've tried when I was dealing with this problem. Even that was not enough... (but maybe it does in Delphi XE3) – TLama Mar 25 '14 at 15:52
  • 2
    @TLama, `TrayIcon` and oneinstance has always been trublesome. – LU RD Mar 25 '14 at 15:53
  • I don't remember why I do it this way, but this is how my program has done it for years. – David Heffernan Mar 25 '14 at 15:59
  • @LURD Yes it seems to, although it screws up my apps notification icon but I expect that is unrelated. But that's not extensively tested. Why do you think it would be a problem? – David Heffernan Mar 25 '14 at 16:08
  • I don't remember the exact details, but there were problems to get correct behaviour minimizing/maximizing when an application was started with `MainFormOnTaskbar` false, in conjuction with one app instances. It could work in one Delphi version, but not in the next, etc. – LU RD Mar 25 '14 at 16:26
  • @LURD I think modern Delphi's are better. And calling the code in my answer from the app itself I suspect is better than poking from the outside. But I don't know more details. I know that the code in my answer works fine in my setting. – David Heffernan Mar 25 '14 at 16:33
  • It's obvious why handling restoration in the target application (f.i. on receiving a message) is better, see `Application.InternalRestore`. When you force it from the outside, you bypass all that unpleasant stuff that have to run that the TApplication framework imposes, when you don't have MainFormOnTaskbar f.i. – Sertac Akyuz Mar 25 '14 at 23:38
  • @Sertac Thanks. That was my instinctive thought, but I cannot formulate it very clearly. – David Heffernan Mar 25 '14 at 23:42
  • @DavidHeffernan I have create a very small application based on your answer: a black form without trayicon. However I cannot restore the form of first instance from minimize status. [link]https://dl.dropboxusercontent.com/u/19900180/OneInstance.zip – Martin Mar 26 '14 at 10:25
  • @Martin, declare your method like this: `procedure WMRestoreWindow(var Message: TMessage); message WM_RestoreWindow;` – LU RD Mar 26 '14 at 17:41
  • @Martin Sorry, I missed that comment somehow. What LURD says is right. That's all you need to do. – David Heffernan Mar 26 '14 at 17:50