Solved

Closing Multiple Open Delphi exe.

Posted on 2006-07-17
15
284 Views
Last Modified: 2010-04-04
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
0
Comment
Question by:henryreynolds
  • 6
  • 5
  • 4
15 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17120245
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.
0
 

Author Comment

by:henryreynolds
ID: 17120830
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
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17121234
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.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 17126374
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.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 17126381
small typo
  runpath = 'c:\windowst\';
should be
  runpath = 'c:\windows\';
but you'll be using your own code for those variables anyway
0
 

Author Comment

by:henryreynolds
ID: 17128326
Hi
thanx i have a look at it now, and will let you know..thanx for helping me with the example

Henry
0
 

Author Comment

by:henryreynolds
ID: 17129608
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
0
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 
LVL 17

Expert Comment

by:TheRealLoki
ID: 17133938
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;
0
 

Author Comment

by:henryreynolds
ID: 17135765
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.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 17135821
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
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 500 total points
ID: 17135988
actually, with a small change, you can use the existing code, I wont scare you with the thread method just yet :-)

unit Unit1;

interface

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

type
    TMyProcessObject = class
    public
        MsgMenuExec, ProgName, RunPath, Exename: string;
        ProcessID: cardinal;
        StillRunning: boolean;
        constructor Create(MsgMenuExec_, ProgName_, RunPath_, Exename_: string; ProcessID_: cardinal);
    end;

type
  TForm1 = class(TForm)
    bStartNotepad: TButton;
    bStartCalc: TButton;
    Timer1: TTimer;
    procedure bStartNotepadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bStartCalcClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    function SpawnProcess(MsgMenuExec_, ProgName_, RunPath_, Exename_: string): boolean;
  public
    { Public declarations }
    MyProcessList: TList;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//This function finds the windows and closes them
function EnumWindowsProcClose(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 = TMyProcessObject(List[i]).ProcessID) 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;

//This function finds the windows and sets the StillRunning flag
function EnumWindowsProcCheckRunning(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 = TMyProcessObject(List[i]).ProcessID) then
           begin
                found := true;
                TMyProcessObject(List[i]).StillRunning := True;
           end
           else inc(i);
        end;
    end;

procedure TForm1.bStartNotepadClick(Sender: TObject);
    begin
        SpawnProcess('MsgMenuExec', 'Notepad', 'c:\windows\', 'notepad.exe');
//        SpawnProcess('MsgMenuExec', 'Notepad', 'c:\winnt\', 'notepad.exe');
        (sender as TButton).Enabled := False;
    end;

procedure TForm1.bStartCalcClick(Sender: TObject);
    begin
        SpawnProcess('MsgMenuExec', 'Calc', 'c:\windows\system32\', 'calc.exe');
//        SpawnProcess('MsgMenuExec', 'Calc', 'c:\winnt\system32\', 'calc.exe');
        (sender as TButton).Enabled := False;
    end;

function TForm1.SpawnProcess(MsgMenuExec_, ProgName_, RunPath_, Exename_: string): boolean;
    var
        StartupInfo: TStartupInfo;
        ProcessInfo: TProcessInformation;
    begin
        result := false;
        Timer1.Enabled := False;
        try
            fillChar( startupInfo, sizeof( startupInfo ), 0 );
            StartupInfo.cb := sizeof( startupInfo );
            StartupInfo.wShowWindow := SW_SHOW;

            if (CreateProcess(nil, PChar(RunPath_ + ExeName_), nil, nil, False,
             NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
             begin
                MyProcessList.Add(TMyProcessObject.Create(MsgMenuExec_, ProgName_, RunPath_,
                  Exename_,ProcessInfo.dwProcessId));
                result := true;
             end
             else
               MessageDlg(MsgMenuExec_ + #13 + '(' + ProgName_ + ')', mtWarning, [mbOK], 0)
        finally
            Timer1.Enabled := True;
        end;
    end;

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

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

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
        Timer1.Enabled := False;
        EnumWindows(@EnumWindowsProcClose, integer(MyProcessList));
    end;

procedure TForm1.Timer1Timer(Sender: TObject);
    var
        i: integer;
    begin
        Timer1.Enabled := False;
        try
            for i:= 0 to pred(MyProcessList.Count) do
              TMyProcessObject(MyProcessList[i]).StillRunning := False;
// this next part marks any processes as "StillRunning"
            EnumWindows(@EnumWindowsProcCheckRunning, integer(MyProcessList));
            for i:= pred(MyProcessList.Count) downto 0 do
            begin
                if not TMyProcessObject(MyProcessList[i]).StillRunning then
                begin // this process is no longer running, re-enable the button for it
                    if (
                         (TMyProcessObject(MyProcessList[i]).MsgMenuExec = 'MsgMenuExec') and
                         (TMyProcessObject(MyProcessList[i]).ProgName = 'Notepad')
                       ) then bStartNotepad.Enabled := True
                    else if (
                         (TMyProcessObject(MyProcessList[i]).MsgMenuExec = 'MsgMenuExec') and
                         (TMyProcessObject(MyProcessList[i]).ProgName = 'Calc')
                       ) then bStartCalc.Enabled := True;
// Delete this process from our list since it is no longer running
                    MyProcessList.Delete(i);
                end;
            end;

        finally
            Timer1.Enabled := True;
        end;
    end;

{ TMyProcessObject }

constructor TMyProcessObject.Create(MsgMenuExec_, ProgName_, RunPath_,
  Exename_: string; ProcessID_: cardinal);
begin
    inherited Create;
    MsgMenuExec := MsgMenuExec_;
    ProgName := ProgName_;
    RunPath := RunPath_;
    Exename := Exename_;
    ProcessID := ProcessID_;
end;


end.



******** FORM FOLLOWS *********


object Form1: TForm1
  Left = 367
  Top = 247
  Width = 249
  Height = 141
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnClose = FormClose
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object bStartNotepad: TButton
    Left = 72
    Top = 24
    Width = 91
    Height = 25
    Caption = 'bStartNotepad'
    TabOrder = 0
    OnClick = bStartNotepadClick
  end
  object bStartCalc: TButton
    Left = 72
    Top = 56
    Width = 91
    Height = 25
    Caption = 'bStartCalc'
    TabOrder = 1
    OnClick = bStartCalcClick
  end
  object Timer1: TTimer
    Enabled = False
    OnTimer = Timer1Timer
    Left = 192
    Top = 32
  end
end
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17136401
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.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17136495
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.
0
 

Author Comment

by:henryreynolds
ID: 17136922
Hi I am out of the office I have a look at it tonight THANX EVERYONE.............
0
 

Author Comment

by:henryreynolds
ID: 17143718
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
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Copy file in dll not working but working on exe ! 18 88
Working with hours 3 46
Multiple image collision 13 69
I want to use librsync in my Delphi backup application. 3 31
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Windows 10 is mostly good. However the one thing that annoys me is how many clicks you have to do to dial a VPN connection. You have to go to settings from the start menu, (2 clicks), Network and Internet (1 click), Click VPN (another click) then fi…
Internet Business Fax to Email Made Easy - With  eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, f…

863 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now