Delphi Newsletter #2 (now Pascal Newsletter)
The full source code examples of this issue are available for download.
![]() |
![]() |
Delphi Newsletter #2 INDEX 1. A FEW WORDS FROM THE EDITOR 2. A SIMPLE FILE FINDER - TAnimate - TStatusBar - TListView - TStringList - TSearchRec, FindFirst, FindNext, FindClose - A Recursive function - The full example 3. THE WINDOWS REGISTRY - What is the Registry? - TRegistry - Example: Launching the associated application 4. WHAT'S NEXT? ________________________________________________________________________ 1. A FEW WORDS FROM THE EDITOR Mistakes, mistakes, mistakes... We can never get rid of them, right? Well, in the first issue we made a few. First, in the first sample call to ShellExecute we checked if the return value was less than 32 ("< 32") to show an error box, while we should have checked to see if that value was less than OR EQUAL to 32 ("<= 32") instead, as we had explained in the introduction. Then we passed the string "'Couldn't execute the application'" as a parameter to the MessageBox function, but to put a single quote inside a string we have to use TWO singles quotes, so we should have written "'Couldn''t execute the application'" instead. Sorry. At least we do not have to make a telescope fall back to earth... :) In this issue we will convert the simple threaded application we showed in the past issue into a simple file finder application. We will be working on this application in future issues, adding new things, like intelligent keyword search, context menus, open and save dialogs, multipath search, compressed files search and much, much more... Also in this issue we will cover the basics of the Windows Registry and we will use it to access the information about the associated applica- tion to open a document and wait till it ends. We have been suggested to merge our Kylix and Delphi newsletters into a bigger and more generic Pascal Newsletter that would also give coverage to Free Pascal, Lazarus and other projects. We believe the proposal could be of interest to our subscribers, so we are considering it and we would like to hear your opinions about it. This newsletter is copyrighted, but please forward it to friends, acquaintances and colleagues that you know might be interested in this publication as long as you send it in full and without modifications. Please remember that to have this newsletter going and growing in contents we need to keep it growing in audience. Your contributions, comments, critics and questions are welcome. Please stay in touch! Regards, Ernesto De Spirito eds2004 @ latiumsoftware.com ________________________________________________________________________ JfControls Library. Multi-language. Multi-appearance. Skins. Privileges. More than 40 integrated and customizable components. Impressive GUI. Centralized resources administration. Multiple programming problems solved. For Delphi 3-6 and C++ Builder 3-5. http://www.jfactivesoft.com/ ________________________________________________________________________ 2. A SIMPLE FILE FINDER TAnimate ======== You have probably seen in the Windows File Find dialog that when it is looking for files a magnifying glass moves over a sheet of paper. You can use a component of TAnimate class to play silent AVI files on your form to mimic this behavior. It is as simple as placing one of these components on your form, setting the FileName property to reference an AVI file and then set the Active property to True or False to start or stop playing the file respectively. Of course there are more properties and methods to specify the start and end frames, the number of times the animation is going to repeat before stopping automatically and many more things, but right now, that the AVI plays continuously is enough for our purpose. Place a TAnimate component (Animate1) in the form we created in the last issue. Locate a small AVI file in your hard drive and set the FileName property. In the procedure TForm1.Button1Click insert this line: Animate1.Active := True; This will start the animation when the thread starts. The animation will stop adding this line to the TForm1.Thread1Done procedure: Animate1.Active := False; That's it! Now you can test the example to see it working. TStatusBar ========== The status bar is that line at the bottom of many forms used to show extended information like tools information, the action an application is performing, the Caps Lock and Num Lock status and many things, depending on the application. Place a TStatusBar component on your form. It will align itself at the bottom of the form. Normally a status bar is divided in a few panels, but in our example we will just use only one. The easiest way is setting the SimplePanel property to True. Now, when we want to show something, we will just set the SimpleText property. In our example (shown later) we will use it to display the search status (number of items found). TListView ========= This is a common control in Windows 32-bit. Items (normally files) can be shown in many styles: large icons, small icons, list and detail, like in Windows Explorer. Place a TListView control at the bottom of your form and set its ViewStyle property to vsReport. Go to Columns property editor and create two columns: Name and Folder. To add an item to a ListView at run-time normally we use the Add method of the Items property of the ListView. Add adds a new item to the Items list and returns a reference to it that we can store in a variable. Then we can set the properties of the element, being Caption the most important one. To set the value of the captions of the other columns, we add lines to the SubItems property (a TStringList). For example: var ListItem: TListItem; // Reference to the newly added item begin ListItem := ListView1.Items.Add(); // Adds a new row ListItem.Caption := 'WIN.INI'; // First column ListItem.SubItems.Add('C:\WINDOWS\'); // Second column end; TStringList =========== String lists are very common in Delphi programming. Many objects have properties of type TStringList, so you should be familiar with them. Also, they are very useful for other purposes. In our case, we will create a string list, load the contents of a file to it, and after we use it we will dispose the object. For example: var Content: TStringList; // Declares a string List begin Content := TStringList.Create(); // Creates the object ... Content.LoadFromFile('C:\WINDOWS\WIN.INI'); ... Content.Free; // Releases the object end; We will be using a code like this one in our example. After loading the string list with the contents of a file, we can access it line by line using the Strings array property or as a whole using the Text property, which is the one we will be using. TSearchRec, FindFirst, FindNext, FindClose ========================================== To find files matching a certain filename specification (like 'C:\WINDOWS\*.INI') we use the FindFirst and FindNext functions and the FindClose procedure. For them to work, we need a TSearchRec record that among other things will contain information of a file matching the search criteria. We use a code like the following: var SearchRec: TSearchRec; begin if FindFirst('C:\WINDOWS\*.INI', <attributes>, SearchRec) = 0 then begin repeat // Here we process each fond file. // Its info is in SearchRec. until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; end; If FindFirst returns 0, it means it found at least one file matching the specified criteria. Then we use FindNext to retrieve the info of the next files that match the criteria one by one. FindNext also returns 0 if it finds a file. After we processed all files, we should close the search calling FindClose that releases some resources in SearchRec that were allocated by FindFirst. If <attributes> is 0, FindFirst and then FindNext will only find normal files, meaning that files marked as Archive, Read-only, Hidden, System, Directory or Volume will be excluded from the search. To include those files in the search, add their corresponding constants: faArchive, faReadOnly, faHidden, faSysFile, faDirectory and faVolumeID respectively. A recursive function ==================== Recursion is often a nightmare for beginner programmers, but it provides simple and elegant solutions to certain problems like the one we have at hand. Our intention is to find files in a directory (folder) AND ITS SUBDIRECTORIES (and the subdirectories of those subdirectories and so on). So, how do we do it? Well, we write a procedure that takes as a parameter the directory path to search (we assume it ends with a colon or backslash): procedure ScanFolder(const folder: string); We perform the file search as shown above, if FindFirst(folder + '*.INI', faReadOnly Or faHidden Or faSysFile Or faArchive, SearchRec) = 0 then begin repeat // Process each file until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; and then we perform a directory search (using the faDirectory attribute) to retrieve the names of the subdirectories contained in the directory passed as parameter. if FindFirst(folder + '*', faReadOnly Or faHidden Or faSysFile Or faArchive Or faDirectory, SearchRec) = 0 then begin repeat // Process each subdirectory // Its name is in SearchRec.Name until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; But, how do we search for files in those subdirectories? Well, we have a procedure (the one we are writing!) that takes a directory path as a parameter and searches the files in that directory, so let's call it passing it the directory path we want to scan (the current directory path plus the name of the found subdirectory plus a backslash). repeat ScanFolder(folder + SearchRec.Name + '\'); until FindNext(SearchRec) <> 0; And that's it! Well, almost. Before we should guarantee that the found file is a directory checking its attribute field, and that it that its name is not '.' or '..' (current directory and parent directory respectively), so the code would be: repeat if ((SearchRec.Attr and faDirectory) <> 0) and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then ScanFolder(folder + SearchRec.Name + '\'); until FindNext(SearchRec) <> 0; And now we are done. The full example ================ Well, it is time to put all the pieces together. To start we need a 467x354 and the following controls: 3 TEdit 3 TLabel 1 TCheckBox 2 TButton 1 TAnimate 1 TListView 1 TStatusBar Set their properties in the Object Inspector: Label1 Left = 3 Top = 13 Width = 55 Height = 13 Alignment = taRightJustify Caption = 'File &Names:' FocusControl = Edit1 Label2 Left = 5 Top = 42 Width = 53 Height = 13 Alignment = taRightJustify Caption = '&Containing:' FocusControl = Edit2 Label3 Left = 18 Top = 72 Width = 41 Height = 13 Alignment = taRightJustify Caption = 'In f&older:' FocusControl = Edit3 Button1 Left = 376 Top = 6 Width = 78 Height = 24 Anchors = [akTop, akRight] Caption = '&Find' Default = True TabOrder = 0 OnClick = Button1Click Button2 Left = 376 Top = 38 Width = 78 Height = 24 Anchors = [akTop, akRight] Cancel = True Caption = '&Cancel' Enabled = False TabOrder = 1 OnClick = Button2Click StatusBar1 Left = 0 Top = 308 Width = 459 Height = 19 Panels = <> SimplePanel = True Edit1 Left = 64 Top = 8 Width = 302 Height = 21 Anchors = [akLeft, akTop, akRight] TabOrder = 3 Text = '*.ini' Edit2 Left = 64 Top = 37 Width = 302 Height = 21 Anchors = [akLeft, akTop, akRight] TabOrder = 4 Text = '' Edit3 Left = 65 Top = 67 Width = 301 Height = 21 Anchors = [akLeft, akTop, akRight] TabOrder = 5 Text = 'C:\WINDOWS' CheckBox1 Left = 65 Top = 97 Width = 111 Height = 13 Caption = '&Include subfolders' TabOrder = 6 ListView1 Left = 0 Top = 120 Width = 459 Height = 188 Anchors = [akLeft, akTop, akRight, akBottom] Columns = < item Caption = 'Name' Width = 150 end item Caption = 'Folder' Width = 300 end> TabOrder = 7 ViewStyle = vsReport OnDblClick = ListView1DblClick OnMouseDown = ListView1MouseDown Animate1 Left = 393 Top = 66 Width = 48 Height = 50 Active = False Anchors = [akTop, akRight] FileName = 'FileSerch.avi' The OnXxxxx means you should generate those event handlers. The full source of the unit is here: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls, ShellAPI; const WM_ThreadDoneMsg = WM_User + 8; type TForm1 = class; TThread1 = class(TThread) private OwnerForm: TForm1; Location: string; FileName: string; Count: cardinal; procedure Initialize; procedure AddFileName; procedure Finalize; protected procedure Execute; override; published constructor Create(Owner: TForm1); destructor Destroy; override; end; TForm1 = class(TForm) Button1: TButton; Button2: TButton; StatusBar1: TStatusBar; Edit1: TEdit; Label1: TLabel; Edit2: TEdit; Label2: TLabel; Edit3: TEdit; Label3: TLabel; CheckBox1: TCheckBox; ListView1: TListView; Animate1: TAnimate; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure ListView1DblClick(Sender: TObject); procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private { Private declarations } Last: TPoint; Thread1: TThread1; procedure Thread1Done(var AMessage: TMessage); message WM_ThreadDoneMsg; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} // -------------------------------------------------------------- procedure TForm1.Button1Click(Sender: TObject); var c: char; begin if Edit1.Text = '' then begin MessageDlg('Enter file spec', mtWarning, [mbOK], 0); Edit1.SetFocus; end else if Edit2.Text = '' then begin MessageDlg('Enter keywords', mtWarning, [mbOK], 0); Edit2.SetFocus; end else if Edit3.Text = '' then begin MessageDlg('Enter folder', mtWarning, [mbOK], 0); Edit3.SetFocus; end else begin c := Edit3.Text[Length(Edit3.Text)]; if (c <> '\') and (c <> ':') then Edit3.Text := Edit3.Text + '\'; Button1.Enabled := False; Edit1.Enabled := False; Edit2.Enabled := False; Edit3.Enabled := False; Checkbox1.Enabled := False; Button2.Enabled := True; Thread1 := TThread1.Create(Self); Animate1.Active := True; end; // if end; procedure TForm1.Button2Click(Sender: TObject); begin Thread1.Terminate; end; procedure TForm1.Thread1Done(var AMessage: TMessage); begin Animate1.Active := False; Button1.Enabled := True; Edit1.Enabled := True; Edit2.Enabled := True; Edit3.Enabled := True; Checkbox1.Enabled := True; Button2.Enabled := False; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if Button2.Enabled then begin Thread1.Terminate; Thread1.WaitFor; end; // if Action := caFree; end; // -------------------------------------------------------------- constructor TThread1.Create(Owner: TForm1); begin inherited Create(True); OwnerForm := Owner; Priority := tpHigher; FreeOnTerminate := True; Suspended := False; end; destructor TThread1.Destroy; begin PostMessage(OwnerForm.Handle, WM_ThreadDoneMsg, Self.ThreadID, 0); inherited destroy; end; procedure TThread1.Execute; var Content: TStringList; Keywords: string; procedure ScanFolder(const folder: string); var SearchRec: TSearchRec; begin if FindFirst(folder + OwnerForm.Edit1.Text, faReadOnly Or faHidden Or faSysFile Or faArchive, SearchRec) = 0 then begin repeat try FileName := SearchRec.Name; Content.LoadFromFile(folder + FileName); if AnsiPos(Keywords, AnsiUpperCase(Content.Text)) <> 0 then begin Inc(Count); Location := folder; Synchronize(AddFileName); end; // if except end; // try until Terminated Or (FindNext(SearchRec) <> 0); FindClose(SearchRec); end; // if if (not Terminated) and OwnerForm.Checkbox1.Checked then begin if FindFirst(folder + '*.*', faReadOnly Or faHidden Or faSysFile Or faArchive Or faDirectory, SearchRec) = 0 then begin repeat try if ((SearchRec.Attr and faDirectory) <> 0) and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then ScanFolder(folder + SearchRec.Name + '\'); except end; // try until Terminated Or (FindNext(SearchRec) <> 0); end; // if FindClose(SearchRec); end; // if end; begin // procedure TThread1.Execute; Synchronize(Initialize); Count := 0; Content := TStringList.Create(); Keywords := AnsiUpperCase(OwnerForm.Edit2.Text); ScanFolder(OwnerForm.Edit3.Text); Content.Free; Synchronize(Finalize); end; procedure TThread1.Initialize; begin OwnerForm.StatusBar1.SimpleText := 'Buscando... (0 files found)'; OwnerForm.ListView1.Items.Clear; end; procedure TThread1.AddFileName; var ListItem: TListItem; begin OwnerForm.StatusBar1.SimpleText := 'Searching... (' + IntToStr(Count) + ' files found)'; ListItem := OwnerForm.ListView1.Items.Add(); ListItem.Caption := FileName; ListItem.SubItems.Add(Location); end; procedure TThread1.Finalize; begin if Terminated then OwnerForm.StatusBar1.SimpleText := 'Search cancelled (' + IntToStr(Count) + ' files found).' else OwnerForm.StatusBar1.SimpleText := 'Search done (' + IntToStr(Count) + ' files found).' end; procedure TForm1.ListView1DblClick(Sender: TObject); var i, n, RelativeX, StartX: Integer; ListItem: TListItem; begin ListItem := ListView1.GetItemAt(Last.X, Last.Y); if ListItem <> nil then begin if ShellExecute(Self.Handle, nil, PChar(ListItem.SubItems.Strings[0] + ListItem.Caption), nil, nil, SW_SHOWMAXIMIZED) <= 32 then Application.MessageBox('Couldn''t execute the application', 'Error', MB_ICONEXCLAMATION); end else begin i := ListView1.TopItem.Index; n := i + ListView1.VisibleRowCount - 1; if ListView1.TopItem.Position.Y >= Last.Y then begin while (i <= n) and (ListView1.Items[i+1].Position.Y < Last.Y) do inc(i); if (i <= n) then begin ListItem := ListView1.Items[i]; RelativeX := Last.X - ListItem.Position.X - 2; StartX := ListView1.Columns[0].Width; if (RelativeX >= StartX) and (RelativeX <= StartX + ListView1.StringWidth(ListItem.SubItems[0])) then if ShellExecute(Self.Handle, 'Explore', PChar(ListItem.SubItems.Strings[0]), nil, nil, SW_SHOWMAXIMIZED) <= 32 then Application.MessageBox('Couldn''t execute the ' + 'application', 'Error', MB_ICONEXCLAMATION); end; // if end; // if end; // if end; procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Last.X := X; Last.Y := Y; end; end. Now let's take a look at a few things... To begin with, in the type declarations the first line is TForm1 = class; This declares the class as "forward", meaning the class will be fully declared later, but this allow it to be referenced before that in type declarations. We do it this way because TThread1 refers to TForm1 and vice versa, so we should declare TForm1 before TThread1 and vice versa, what is impossible, so Object Pascal provides us with this solution. We will use the thread to search for files and as files are found they will be added to the list view and the status bar will be updated to show the number of files found. Instead of having a reference to these two components, this time we will simply have a reference to the thread's owner form (OwnerForm property) that we will use to access its components and their properties. This time we will use three synchronized methods: Initialize, AddFileName and Finalize, and three variables to communicate with these methods: Location, FileName and Count. Their meanings should be obvious after watching the source code. The events of ListView1 are a bit complicated. In the MouseDown event we save the position where the user clicked the mouse to use it later in the DblClick event because DblClick does not report the position where the user double-clicked the mouse. We call the GetItemAt method of the ListView to get a pointer to the ListItem on which the user clicked, so we call the associated application for the file. The problem is that GetItemAt returns NIL if the user didn't click on the caption or icon of the element, so if the user clicked on the folder (second column), for example, we do not have a way to know... to know directly, but we can use a complicated code like the above to determine that. If the user clicked on a folder name, then we open the Windows Explorer to explore that folder, something the Windows Find dialog does not do! The full sources of this application are available in our web site: http://www.latiumsoftware.com/download/delphi-2.zip ________________________________________________________________________ 3. THE WINDOWS REGISTRY What is the Registry? ===================== It is where Windows stores many of its configuration options and also allows applications to access this data as well as save their own data. If you want to take a look at the registry, just execute the REGEDIT.EXE application located in the Windows directory. Be careful not to change anything or you could end up ruining your installation! Now, the data in the registry is stored in a tree structure. There are many roots (many trees): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS HKEY_PERFORMANCE_DATA HKEY_CURRENT_CONFIG HKEY_DYN_DATA Each root can have values and key. The values are data stored under item names (right panel of RegEdit). Keys can have values and other keys, forming a tree structure (left panel of RegEdit). TRegistry ========= The TRegistry class is declared in the Registry unit, so you will have to add this unit to the uses clause of the unit or program where you want to use it. To access a value in the registry first you should create an object of this class, assign the root to its RootKey property (the values are defined in the Windows unit) and then try to open a key with the OpenKey function method, which will return True if successful. Then you can read (with the ReadXxxx functions) or write (with the WriteXxxx procedures) the values of the open key and, after that, you should close the key with CloseKey. When you are done with the registry, you should free the registry object you created. Let's see an example of how to obtain the name of the processor in our computer. First, create a new application, add Registry to the uses clause, place a button on the form and generate its OnClick event: procedure TForm1.Button1Click(Sender: TObject); var Reg: TRegistry; begin Reg := TRegistry.Create; Reg.RootKey := HKEY_LOCAL_MACHINE; if Reg.OpenKey('\Hardware\Description\System' + '\CentralProcessor\0', False) then begin ShowMessage(Reg.ReadString('Identifier')); Reg.CloseKey; end; // if Reg.Free; end; Of course, there are many more things you can do with the registry, like creating and deleting keys and values, but let's leave them for the future. The TRegistryIniFile class makes it simpler for applications to write and read their configuration information to and from the registry, while TRegistry operates at a lower level. Example: Launching the associated application ============================================= Now let's get to something a bit more complicated. What we have to do is access the registry to find out which is the associated application for a given document and execute it waiting till it ends. In the last newsletter we saw the latter part, so now we will concentrate on how to locate an association in the registry. In the form of the previous example, place an edit box to write the filename, add ShellAPI to the uses clause of the unit and change the code: procedure ExecAndWait(Command: String); forward; procedure TForm1.Button1Click(Sender: TObject); var ContentType, Command: String; p: integer; Reg: TRegistry; begin Reg := TRegistry.Create(KEY_EXECUTE); Reg.RootKey := HKEY_CLASSES_ROOT; ContentType := ''; if Reg.OpenKeyReadOnly(ExtractFileExt(Edit1.Text)) then begin ContentType := Reg.ReadString(''); Reg.CloseKey; end; // if if ContentType <> '' then begin Command := ''; if Reg.OpenKeyReadOnly(ContentType + '\Shell\Open\Command') then begin Command := Reg.ReadString(''); Reg.CloseKey; end; Reg.Free; if Command <> '' then begin p := pos('%1', Command); if p = 0 then Command := Command + ' "' + Edit1.Text + '"' else Command := Copy(Command, 1, p-1) + Edit1.Text + Copy(Command, p+2, Length(Command) - p - 1); ExecAndWait(Command); end; // if end else begin Reg.Free; end; // if end; procedure ExecAndWait(Command: String); var proc_info: TProcessInformation; startinfo: TStartupInfo; ExitCode: longword; begin FillChar(proc_info, sizeof(TProcessInformation), 0); FillChar(startinfo, sizeof(TStartupInfo), 0); startinfo.cb := sizeof(TStartupInfo); if CreateProcess(nil, PChar(Command), nil, nil, false, CREATE_DEFAULT_ERROR_MODE + NORMAL_PRIORITY_CLASS, nil, nil, startinfo, proc_info) <> False then begin WaitForSingleObject(proc_info.hProcess, INFINITE); GetExitCodeProcess(proc_info.hProcess, ExitCode); // Optional CloseHandle(proc_info.hProcess); Application.MessageBox( PChar(Format('Application finished! (Exit code=%d)', [ExitCode])), 'Info', MB_ICONINFORMATION); end else begin Application.MessageBox('Couldn''t execute the application', 'Error', MB_ICONEXCLAMATION); end; // if end; The KEY_EXECUTE parameter passed to the Create constructor means the registry is opened with minimum (read-only) access rights. We used OpenKeyReadOnly instead of OpenKey since it is the preferred way to open a key if the intention is not to write values or make any changes to it. The empty string ('') passed as a parameter to ReadString means we want to get the default value of the open key. What we did is first get the file "kind" associated with the extension of the file name entered in the edit box. Those kinds are under HKEY_CLASSES_ROOT\.ext\(default) where ".ext" is the file extension you want (like ".txt", ".bmp", etc.). Then we get the command line used to open that kind of files. To do that, we retrieve the data under HKEY_CLASSES_ROOT\kind\Shell\Open\Command\(default) where "kind" is the file kind an extension is associated to (that we have just retrieved before). That string usually has the form "D:\PATH\APPNAME.EXT" "%1" -OPTIONS where '%1' is a placeholder for the document file to open with the application, so we find its position within the string and replace it with the filename in the edit box to obtain the full command line we need to open the application, and then we call the ExecAndWait procedure to do that. This procedure uses almost the same code we used in the last issue to execute an application and wait till it ends. We have to warn you that we experienced problems with WaitForSingleObject in combination with certain applications and we had to finish them with the task manager. Other applications fail to finish correctly and WaitForSingleObject never returns. There doesn't seem to be an easy workaround to this problem, but here we will try to rehearse one. First add a TTimer and another TButton to the form. Set their Enabled properties to False and set the Interval of the timer to 200. Now let's add a few declarations in the private section of the form: type TForm1 = class(TForm) ... private { Private declarations } proc_info: TProcessInformation; startinfo: TStartupInfo; ExitCode: LongWord; procedure ExecAndWait(Command: String); ... Now erase that forward declaration we had and change ExecAndWait: procedure TForm1.ExecAndWait(Command: String); begin FillChar(proc_info, sizeof(TProcessInformation), 0); FillChar(startinfo, sizeof(TStartupInfo), 0); startinfo.cb := sizeof(TStartupInfo); if CreateProcess(nil, PChar(Command), nil, nil, false, CREATE_DEFAULT_ERROR_MODE + NORMAL_PRIORITY_CLASS, nil, nil, startinfo, proc_info) then begin Button1.Enabled := False; Button2.Enabled := True; Timer1.Enabled := True; end else begin CloseHandle(proc_info.hProcess); Application.MessageBox('Couldn''t execute the application', 'Error', MB_ICONEXCLAMATION); end; // if end; Generate the OnTimer event for Timer1: procedure TForm1.Timer1Timer(Sender: TObject); begin Timer1.Enabled := False; if GetExitCodeProcess(proc_info.hProcess, ExitCode) then if ExitCode = STILL_ACTIVE then Timer1.Enabled := True else begin Button2.Enabled := False; Button1.Enabled := True; CloseHandle(proc_info.hProcess); end else begin Button2.Enabled := False; Button1.Enabled := True; TerminateProcess(proc_info.hProcess, 0); CloseHandle(proc_info.hProcess); end; end; Generate the OnClick event for Button2: procedure TForm1.Button2Click(Sender: TObject); begin Timer1.Enabled := False; if Application.MessageBox('You should try to finish the ' + 'application normally.'#13#13'¿Terminate it anyway?', 'Warning', MB_YESNO + MB_DEFBUTTON2 + MB_ICONQUESTION + MB_TASKMODAL) = ID_YES then begin TerminateProcess(proc_info.hProcess, 0); CloseHandle(proc_info.hProcess); Button2.Enabled := False; Button1.Enabled := True; end else begin Timer1.Enabled := True; end; end; Generate the OncloseQuery event for Form1: procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if Button2.Enabled then begin Button2Click(Button2); if Button2.Enabled then CanClose := False; end; end; The purpose of all this code is to check the status of the launched application (calling GetExitCodeProcess) at intervals (in a timer event). If the process has terminated we close it. The implementation we chose also allows the process to be terminated from our application by calling TerminateProcess after prompting the user (TerminateProcess is not recommended and should be the last resort to finish an application). ________________________________________________________________________ 4. WHAT'S NEXT? With our subscribers approval, this could be the last issue of the Delphi Newsletter, since in the future it would become the Pascal Newsletter. In a couple of weeks we expect to have our web site ready. It is going to be too amateurish perhaps, but there you will find mailing lists, articles, examples, source code, components, applications and other resources. We think all this will make this newsletter become more dynamic, allowing it to respond to your programming needs. See you then! ________________________________________________________________________ YOU CAN HELP US We need your help to keep this newsletter going and growing. You can help by referring the newsletter to your colleagues: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Or you can help by voting for us in some or all of these rankings to give more visibility to our web site and thus increase the number of subscriptions to this newsletter: http://www.sandbrooksoftware.com/cgi-bin/TopSite2/rankem.cgi?id=latium http://news.optimax.com/delphi/links/links.exe/click?id=70C517ECAE6E http://www.programmingpages.com/?r=latiumsoftwarecomenpascal http://www.top219.org/cgi-bin/vote.cgi?delphi&83 http://top100borland.com/in.php?who=20 http://top200.jazarsoft.com/delphi/rank.php3?id=latium http://213.65.224.200/cgi-bin/toplist.cgi/hits?Id=80 It's just a few seconds for you that REALLY mean a lot to us. ________________________________________________________________________ If you haven't received the archive with the source code for this issue, you can get it from http://www.latiumsoftware.com/download/delphi-2.zip ________________________________________________________________________ This newsletter is provided "AS IS" without warranty of any kind. Its use implies the acceptance of our licensing terms and disclaimer of warranty you can read at http://www.latiumsoftware.com/en/legal.php where you will also find a note about legal trademarks. Articles are copyright of their respective authors and they are reproduced here with their permission. You can redistribute this newsletter as long as you do it in full (including copyright notices), without changes, and gratis. ________________________________________________________________________ Main page: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Group home page: http://groups.yahoo.com/group/pascal-newsletter/ Subscribe/join: pascal-newsletter-subscribe@yahoogroups.com Unsubscribe/leave: pascal-newsletter-unsubscribe@yahoogroups.com Problems with your subscription? eds2004 @ latiumsoftware.com ________________________________________________________________________ Latium Software http://www.latiumsoftware.com/en/index.php Copyright (c) 2000 by Ernesto De Spirito. All rights reserved. ________________________________________________________________________ |
The full source code examples of this issue are available for download.
![]() |
Errors? Omissions? Comments? Please contact us!






