henryreynolds
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
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
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
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(H andle, @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( ProcessInf o.dwProces sId));
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(@EnumWindowsPr oc, integer(MyProcessList));
end;
end.
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(H
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
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(
end
else
MessageDlg(MsgMenuExec + #13 + '(' + sProgName + ')', mtWarning, [mbOK], 0)
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyProcessList := TList.Create;
end;
procedure TForm1.FormDestroy(Sender:
begin
MyProcessList.Clear;
MyProcessList.Free;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
EnumWindows(@EnumWindowsPr
end;
end.
small typo
runpath = 'c:\windowst\';
should be
runpath = 'c:\windows\';
but you'll be using your own code for those variables anyway
runpath = 'c:\windowst\';
should be
runpath = 'c:\windows\';
but you'll be using your own code for those variables anyway
ASKER
Hi
thanx i have a look at it now, and will let you know..thanx for helping me with the example
Henry
thanx i have a look at it now, and will let you know..thanx for helping me with the example
Henry
ASKER
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
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.bAddSalesTransactio n(sender; tobject);
begin
// do createprocess bit...
bAddSalesTransaction.Enabl ed := false;
end;
e.g.
procedure TForm1.bAddSalesTransactio
begin
// do createprocess bit...
bAddSalesTransaction.Enabl
end;
ASKER
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.
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
I'll rewrite the above for you to demonstrate this
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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(Para mStr(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(Proce sses[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.
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(Para
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(Proce
begin
WriteLn('Process ', I, ' is still running. KILL!!!');
TerminateProcess(Processes
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.
ASKER
Hi I am out of the office I have a look at it tonight THANX EVERYONE.............
ASKER
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
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
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.