0

I have a function which allows me to launch a sub-process, wait until it's terminated, and return an exit code. The sub-process is Windows Installer which installs an MSI package (showing just the current progress of its install). This function is called from within a background thread.

I've wrapped this function like so:

function ShellExec(const Verb, Cmd, Params, Dir: WideString;
  const Visible: Bool): Integer;
var
  FN: String;
  SEInfo: TShellExecuteInfo;
  ExitCode: DWORD;
begin
  Result:= 1;
  FillChar(SEInfo, SizeOf(SEInfo), 0);
  SEInfo.cbSize := SizeOf(TShellExecuteInfo);
  with SEInfo do begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := Application.Handle;
    lpFile := PChar(Cmd);
    lpParameters := PChar(Params);
    lpDirectory := PChar(Dir);
    if Visible then
      nShow := SW_SHOWNORMAL
    else
      nShow := SW_HIDE;
  end;
  if ShellExecuteEx(@SEInfo) then begin
    repeat
      GetExitCodeProcess(SEInfo.hProcess, ExitCode);
    until (ExitCode <> STILL_ACTIVE) or
      Application.Terminated;
  end else begin
    ExitCode:= 1;
  end;
  Result:= ExitCode;
end;

And then I use it like this:

function InstallNodeJs(const Config: TInstConfig): Integer;
var
  FN: String;
  Params: string;
begin
  Result:= 1;
  if Config.Is64 then
    FN := 'NodeInstall_x64.msi'
  else
    FN := 'NodeInstall_x86.msi';
  FN:= Config.TmpDir + FN;
  Params:= '/i "'+FN+'" /norestart /passive';
  Result:= ShellExec('', 'msiexec.exe', Params, '', True);
end;

This works fine, however it does not suspend input from my calling app - user is still able to click on my application, despite the loop in the above function - and then my app goes on top of the sub-process. I need the sub-process to stay on top of my application.

The process is launched from within a worker thread behind the main form. The thread uses events to notify the main form of its progress. The main form has a couple animations in which I still want to continue, but I don't want the user to be able to click back on the app while this sub-app is still running. I'd like it to appear like a modal state. Essentially the MSI progress should stay on top of my application at all times.

How can I run this sub-process and prevent the user from going back to the main app and prevent the main app from going on top of the sub-app? This is the default behavior when doing the same thing from Inno Setup, but I can't figure out how to do it in a Delphi application.

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • Would you really want to do this? What happens if, for example, the user attempts to close the program and input is disabled? – Mason Wheeler May 07 '14 at 22:25
  • You cannot prevent your application coming into front, for instance by disabling forms, against the user pressing the taskbar button. The only thing, I think, that would work is to change window ownership. Not recommended, messy, not even documented since some time. – Sertac Akyuz May 07 '14 at 22:26
  • For better user experience, IMHO, disable what you have to disable, closing, pressing buttons... But let users be able to move, minimize your application. – Sertac Akyuz May 07 '14 at 22:38
  • 2
    Why are you running a busy loop? – David Heffernan May 07 '14 at 22:43
  • @Sertac Windows ownership cannot be changed. It is set when the window is created. – David Heffernan May 07 '14 at 22:46
  • @David - Of course it can be changed. See, f.i. http://stackoverflow.com/questions/6940874/use-a-window-handle-as-an-owner-for-a-vb6-form – Sertac Akyuz May 07 '14 at 22:48
  • Also, you leak a handle, and you don't check for errors on every API call. You've copied code that is rubbish. Don't do that. Write the code yourself and make sure that it is done right. – David Heffernan May 07 '14 at 22:48
  • What about providing feedback through a outlook mail style notification window and hiding your application? – Graymatter May 07 '14 at 22:48
  • @Sertac That's the parent. You talked about the owner. The owner is decided at creation time. – David Heffernan May 07 '14 at 22:49
  • @David - No. `GWL_HWNDPARENT` sets the owner. See [`SetWindowLong`](http://msdn.microsoft.com/en-us/library/ms633591%28v=vs.85%29.aspx)s documentation. But not the MS part. Search for the word 'misleading'. As I said it's not documented anymore, but it was once. – Sertac Akyuz May 07 '14 at 22:51
  • Hmm, using `ShellExec` from within Inno Setup does exactly what I'm asking, but after moving the code over to Delphi that behavior disappeared. – Jerry Dodge May 07 '14 at 22:52
  • @Jerry - In Inno you're not calling it from a thread. You can do the same in Delphi. – Sertac Akyuz May 07 '14 at 22:54
  • @SertacAkyuz So the fact that I'm using a thread makes all the difference, that's what I figured. I designed this thread around the whole concept that the form should always be responsive (animations always working). – Jerry Dodge May 07 '14 at 22:56
  • 1
    @JerryDodge: that means it is also responsive to user interactions, window activation/deactivation notifications, z-order changes, etc – Remy Lebeau May 07 '14 at 22:57
  • @sertac you are right – David Heffernan May 07 '14 at 22:58
  • @Jerry - Responsive is one thing that you don't want, it means user interaction. Maybe you can set your own message loop that would block input messages. – Sertac Akyuz May 07 '14 at 22:58
  • Honestly the original function I found was even messier, including 3 different uses of `Application.ProcessMessages`. I'm also not use to using direct API calls yet, so thanks for pointing out the state of that code. – Jerry Dodge May 07 '14 at 23:21
  • The question still remains, since this external app is launched from within a background thread, how can I suspend the input to any visible forms? – Jerry Dodge May 11 '14 at 02:02

0 Answers0