views:

144

answers:

2

I have a delphi app that runs minimized to a tray icon. When the tray icon is double clicked the app opens a non-modal user interface form.

I have added logic to the app to detect whether it is already running. If it isn't running, it starts up and miminizes itself to the tray.

If it is already running, I want it to pass control to the first instance of itself and open the non-modal form, and then exit (the second instance). What's the best way to do this?

TIA R

+5  A: 

The recommended method of detecting another instance of a given application is for that application to create a named mutex or lock a file in a well known location, so that the second instance will trigger an error when you try to create the same mutex or lock the same file. Once you know there's another instance running, you can find the process handle for that instance and send it a message to restore if its minimized.

Jherico
Make sure you call AllowSetForegroundWindow before you bring the other instance to the front.
glob
I can't find where AllowSetForegroundWindow is declared...?
Wouldn't a named event (Windows Event object, not Delphi event method) be a better choice than a mutex? It could be used to signal the running application as well as for detecting it.
dummzeuch
@dummzeuch: possibly, but then you have to write the handler for listening to the event. On the other hand if you can just find the window you're trying to restore you can send it a standard window message and isolate the functionality in one place in the code.
Jherico
+2  A: 

Microsoft way is not flawless, so i do prefer old school:

const WM_KNOCK_KNOCK = WM_USER + 42;
{ or WM_USER + 265 or any number you like, consult PSDK documentation why WM_USER range }
{ or do RegisterWindowMessage }

{...}

procedure TMainForm.FormCreate(Sender: TObject);
var
  Window: HWND;
begin 
  Window := FindWindow(PChar({MainForm.}ClassName), nil);
  { 
  i neither remember how it works exactly nor have time to investigate right now, 
  so quick and dirty validity test follows:
  }
  Assert(not (HandleAllocated and (Window = Handle)), 'failed, use fallback');
  {
  if Window <> 0 then
  begin
    PostMessage(Window, WM_KNOCK_KNOCK, 0, 0);
    Halt;
  end;

  { regular initialization }

end;

Now, WM_KNOCK_KNOCK message handler of first instance performs wakeup routine.


i have little clue what exactly you do when you receive WM_LBUTTONUP (or perhaps WM_LBUTTONDBLCLK) in your Shell_NotifyIcon wrapper (Application.Restore, maybe?). As, Chris Thornton said, there is no such state as 'minimized to tray', it is artifical.


Fallback: if assertion fails, note what code depends only on class function ClassName so could be easily moved out of FormCreate and invoked before Application creates it.

Almost there. I handle the message in first instance, show the form, call BringToFront, SetFocus, but I can't get the form to show as the topmost, nor get it focused.
And why 42? Douglas Adams?
Hardcoding messages as wm_user + <constant> is a good way to screw yourself
Jherico
presumably because there is a chance of collision with another app? How do you get a guaranteed unique constant value then?
Speaking of "not flawless," here we have code that's quite likely to detect *itself* as the other instance, and certainly not guaranteed to find another instance of this same program. If *you* named your window class TMainForm, what's to say a dozen other developers didn't do the very same thing?
Rob Kennedy
@Jherico, you don't have to worry about choosing unique message values. Since you're only sending the message to other instances of your own application's windows, you are in complete control over how the message gets handled. You just need to make sure that you really only send it to your own program; FindWindow doesn't allow you to make that guarantee, though.
Rob Kennedy
@Jherico no, it is not, since i'm suggesting using WM_USER..$7FFF for exactly what it reserved for, i do not build defenses against spurious messages.
@Rob Kennedy, re: FindWindow vs. hidden window - that requires a testcase; re: dozen of Project1.exe modules showing TForm1 windows - this assumes an environment which really doesnt any more other software.
No, @User, the failure case doesn't require the EXEs to have the same name. It just requires their main forms to have the same class name. Your example used TMainForm, which in my experience is a very common choice. If another program's running using that same generic window class name, you're sunk. There's no guarantee of which window FindWindow will return when there are multiple matches, so if you call it within the OnCreate handler, there's no guarantee it won't simply find the same window you just created. There's no test case for it since there's no documented behavior to test against.
Rob Kennedy
I think you can pretty much guarantee your window class name will be unique provided you rename it from "TForm1" and call it "TAardvarkMP3PlayerTrayIconHiddenMainForm" or whatever.
I have never had an instance where the application iterating through the windows has found itself rather than a first instance of the same EXE (and it had never occurred to me that might be a problem), but that's probably because I do the check before the main form is created.
@Rob Kennedy: While I have your attention do you have a solution to me earlier comment - re bringing the form to the font and giving it focus once you have found it and shown it?
@Ken assertion fails when and only when: a) window already created when constructor invokes DoFormCreate b) FindWindow locates hidden windows, please dont tell me what it is not possible to test. Also, i did not suggest to use MainForm as good name http://www.google.com/search?q=illustrative+purposes