Link to home
Start Free TrialLog in
Avatar of henryreynolds
henryreynoldsFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Closing Multiple Open Delphi exe.

Hi

I have a Main Form that acts like a Main Menu. This main menu call a program  .exe when a user click on the menu item. The program are then run in a seperate window. This mean the user can have ONE MAIN menu and several other windows  exe OPEN at the same time.

** NOW what I want to do is.. If a user close the MAIN MENU... all the other open windows must also be closed at the same time.

How do I keep track of what windows (.exe) are open from my MAIN MENU, and how CAN I closed them ALL AT ONCE...IS their a easy way to identify open windows
created by a application and then to close them..

Thanx
Henry
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

If you use ShellExecuteEx or CreateProcess to start the Exe's then you should have a process ID or process handle which you can use to control the child processes. All you would need to do is call TerminateProcess() with the Process HANDLE of these child processes. Keep in mind that there's a difference between process ID and Process Handle.

There are ways to check for any process (including the current one) to see what his child processes are. This is some deeper API stuff which you won't need if you just keep track of all process handles yourself.
Avatar of henryreynolds

ASKER

Hi how can I get all the processes, I am Using the following code to create a new process.

if (not CreateProcess(nil, PChar(RunPath + sExeName), nil, nil, False,
     NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
         MessageDlg(MsgMenuExec + #13 + '(' + sProgName + ')', mtWarning, [mbOK], 0);

SORRY but i really dont no how to get all the proceeses when a call a fuction on the close event of my main form.

HEnry
Let's see. The user clicks on a menu-item. That menuitem calls CreateProcess. CreateProcess returns you a process ID and process handle in lpProcessInformation. You store the process handle in an array. Once your application closes, you go through this array and terminate every running process. This way you keep track of all the processes.
here is a demo I wrote for you on 1 way to do it using a list of process ids from your CreateProcess, and then finding their form's and closing them when your main menu application closes

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
    MyProcessList: TList;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//This function finds the windows and closes them
function EnumWindowsProc(Handle: THandle; List: TList): boolean; stdcall;
    var
        i: integer;
        found: boolean;
        wndPid: THandle;
    begin
        Result := True;
        found := false;
        i := 0;
        GetWindowThreadProcessId(Handle, @wndPid);
        while (not found) and (i < List.Count) do
        begin
           if (wndPid = Integer(List[i]) ) then
           begin
                found := true;
                PostMessage(Handle, WM_Close, 0, 0);
// or  PostMessage(Handle, WM_CancelMode, 0, 0); first in case it has a dialog, or wm_quit, etc..
           end
           else inc(i);
        end;
    end;

procedure TForm1.Button1Click(Sender: TObject);
    const // some test data for this demo
        runpath = 'c:\windowst\'; // or 'c:\winnt\' etc. just a demo
        sExename = 'notepad.exe';
        sProgName = 'Notepad';
        MsgMenuExec = 'MsgMenuExec';
    var
        StartupInfo: TStartupInfo;
        ProcessInfo: TProcessInformation;
    begin
        fillChar( startupInfo, sizeof( startupInfo ), 0 );
        StartupInfo.cb := sizeof( startupInfo );
        StartupInfo.wShowWindow := SW_SHOW;

        if (CreateProcess(nil, PChar(RunPath + sExeName), nil, nil, False,
         NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
         begin
            MyProcessList.Add(pointer(ProcessInfo.dwProcessId));
         end
         else
           MessageDlg(MsgMenuExec + #13 + '(' + sProgName + ')', mtWarning, [mbOK], 0)

    end;

procedure TForm1.FormCreate(Sender: TObject);
    begin
        MyProcessList := TList.Create;
    end;

procedure TForm1.FormDestroy(Sender: TObject);
    begin
        MyProcessList.Clear;
        MyProcessList.Free;
    end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
        EnumWindows(@EnumWindowsProc, integer(MyProcessList));
    end;

end.
small typo
  runpath = 'c:\windowst\';
should be
  runpath = 'c:\windows\';
but you'll be using your own code for those variables anyway
Hi
thanx i have a look at it now, and will let you know..thanx for helping me with the example

Henry
Hi

THANK YOU THIS IS EXACTLY what I want to DO...
If it is possible can I ask you one more thing, because I really dont UNDERSTAND THIS...

Is it possible to restrict a user to open a number of certain windows, let me expalain with a example..

I got a menu with 3 categories NL.

SALES
STOCK
EMPLOYEES

each category has a number of sub programs, THIS SUBPROGRAM ARE THE EXE I AM CALLING.
NOW I want to restrict a user to open only one exe of each category, this mean a user can have the following open windows
1.Main Menu
2.Categorie 1 -> Add sales Transaction
3.Categorie 2 -> View Stock Levels
4.Categorie 3 -> View Employees Details

SORRY but I dont know WHY they want it like this, i think te reason is to RESTRICT the number of open windows on the windows taskbar.

PLEASE If you understand what I want to do, can you help me, BECAUSE THE CLOSING OF ALL OPEN EXE ARE 100000%.

If you cant help me then its 100% I will close this question and give you all the points..

Thank You

Henry
The simplest solution would be to disable the buttons when the user clicks on them, and you start the application

e.g.
procedure TForm1.bAddSalesTransaction(sender; tobject);
  begin
// do createprocess bit...
    bAddSalesTransaction.Enabled := false;
  end;
Hi

I also thought about it. BUT what I dont understand is.. HOW DO I DETERMINE which exe the user have close. Because i am confused now..
When you create a process, you add the process id to MyProcessList. HOW do you determine that the user have close the window he has open a few minutes ago.

sorry but i dont think i make my self clear enough. I dont know how the process work, if i call a sub program and it create a process, how will my main menu know if the user have close that screen and there are no more exe open, AND I can ENABLE that menu item again.

Thanx for your help.
oh, if you wish to keep track of the spawned applications, then I'd recommend using a thread.
I'll rewrite the above for you to demonstrate this
ASKER CERTIFIED SOLUTION
Avatar of TheRealLoki
TheRealLoki
Flag of New Zealand image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Actually, it's even simpler than this. When you have a process handle then you can do a WaitForSingleObject API call to wait for the process to finish. And if you give it a timeout value of 0 instead of INFINITE then all it will do is quickly check if the process is still running before allowing you to continue your own process. So basically to check which of the processes are finished, you just call this wait function instead.
Okay, very simple console application showing a good way to keep track of processes:

program Process;

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  ShellAPI;

var
  ExecInfo: TShellExecuteInfo;
  I: Integer;
  Processes: array[1..10] of THandle;
begin
  if not (ParamStr(1) = 'SKIP') then
  begin
    ZeroMemory(@Processes, SizeOf(Processes));
    for I := Low(Processes) to High(Processes) do
    begin
      WriteLn('Creating process ', I, '.');
      ZeroMemory(@ExecInfo, SizeOf(ExecInfo));
      ExecInfo.cbSize := SizeOf(ExecInfo);
      ExecInfo.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_CONNECTNETDRV or SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
      ExecInfo.Wnd := GetDesktopWindow;
      ExecInfo.lpVerb := nil;
      ExecInfo.lpFile := PChar(ParamStr(0));
      ExecInfo.lpParameters := 'SKIP';
      ExecInfo.lpDirectory := PChar(ExtractFilePath(ParamStr(0)));
      ExecInfo.nShow := SW_SHOWNORMAL;
      if ShellExecuteEx(@ExecInfo) then Processes[I] := ExecInfo.hProcess;
    end;
    Write('Press <Enter> to close all child processes process.');
    ReadLn;
    for I := Low(Processes) to High(Processes) do
    begin
      if (Processes[I] = 0) then
      begin
        WriteLn('Process ', I, ' is never started');
      end
      else if (WaitForSingleObject(Processes[I], 0) = WAIT_TIMEOUT) then
      begin
        WriteLn('Process ', I, ' is still running. KILL!!!');
        TerminateProcess(Processes[I], 0);
        CloseHandle(Processes[I]);
      end
      else
      begin
        WriteLn('Process ', I, ' has terminated already');
        CloseHandle(Processes[I]);
      end;
    end;
  end;
  Write('Press <Enter> to close this process.');
  ReadLn;
end.

Of course, you would not do ReadLns to wait for the next 'event' so this code would be divided over multiple event handlers. One event that calls CreateProcess to create the new process and add it to the list. And in the end an event that walks through the list and detects which processes are still running. Of course, you could also use a timer to do regular clean-ups of your process list by removing processes that have finished. Or maybe use threads for every process that have to wait for the process to finish and then trigger some event. There are so many options to use here.
Plenty of options to kill a process, btw. The solution used here was simply because console applications don't have a form that can respond to any window messages.

Basically, this demo will create 10 new instances when started. These child processes are started with a special commandline parameter to prevent them from spawning even more processes. This way I could keep it all in one sourcefile without dependencies to other applications. :-) It's a fully working demo.
Hi I am out of the office I have a look at it tonight THANX EVERYONE.............
Hi TheRealLoki

Thanx Its perfect, I am closing this question now and except your answer, because I want to continue still with this topic and I dont think its fair
to keep this question open. I am going to create a new question after I accept your anwser, IF YOU can help me will you please,
because it is still on the same topic but I want to simplified the whole story.

Thank you for your help

Henry