Flylib.com

Books Software

 
 
 

Managing Files with SysUtils

Managing Files with SysUtils

To access files and file information, you can generally rely on the standard functions available in the SysUtils unit. Relying on these fairly traditional Pascal libraries makes your code easily portable among quite different operating systems (although you'll have to consider with great care the differences in the file system architectures, particularly case sensitivity on the Linux platform).

For example, the FilesList example uses the FindFirst , FindNext , and FindClose combination to retrieve from within a folder a list of files that match a filter, with the same code you could use on Kylix and Linux (an example of the output appears in Figure 3.5).

click to expand
Figure 3.5: An example of the output of the FilesList application

The following code adds the filenames to a list box called lbFiles :


procedure

TForm1.AddFilesToList(Filter, Folder: string; Recurse: Boolean);

var

sr: TSearchRec;

begin


if

FindFirst (Folder + Filter, faAnyFile, sr) = 0

then


repeat

lbFiles.Items.Add (Folder + sr.

Name

);

until

FindNext(sr) <> 0; FindClose(sr);

If the Recurse parameter is set, the AddFilesToList procedure gets a list of subfolders by examining the local files again, and then calls itself for each of the subfolders . The list of folders is placed in a string list object, with the following code:


procedure

GetSubDirs (Folder: string; sList: TStringList);

var

sr: TSearchRec;

begin


if

FindFirst (Folder +

'*.*'

, faDirectory, sr) = 0

then


try


repeat


if

(sr.Attr

and

faDirectory) = faDirectory

then

sList.Add (sr.Name);

until

FindNext(sr) <> 0;

finally

FindClose(sr);

end;


end;

Finally, the program uses an interesting technique to ask the user to select the initial directory for the file search, by calling the SelectDirectory procedure (see Figure 3.6):


if

SelectDirectory ('Choose Folder', '', CurrentDir)

then

...


Figure 3.6: The dialog box of the SelectDirectory procedure, displayed by the FilesList application

The TObject Class

As mentioned earlier, a key element of the System unit is the definition of the TObject class, which is the mother of all Delphi classes . Every class in the system inherits from the TObject class, either directly (if you specify TObject as the base class), implicitly (if you indicate no base class), or indirectly
(when you specify another class as the ancestor ). The entire hierarchy of classes in an Object Pascal program has a single root. So, you can use the TObject data type as a replacement for the data type
of any class type in the system, according to the type compatibility rules covered in Chapter 2 in the section "Inheritance and Type Compatibility."

For example, components ' event handlers usually have a Sender parameter of type TObject . This simply means that the Sender object can be of any class, because every class is ultimately derived
from TObject . The typical drawback of such an approach is that to work on the object, you need to know its data type. In fact, when you have a variable or a parameter of the TObject type, you can apply to it only the methods and properties defined by the TObject class itself. If this variable or parameter happens to refer to an object of the TButton type, for example, you cannot directly access its Caption property. The solution to this problem lies in the use of the safe down-casting or run-time type information (RTTI) operators ( is and as ) discussed in Chapter 2.

You can also use another approach. For any object, you can call the methods defined in the TObject class. For example, the ClassName method returns a string with the name of the class. Because it is a class method (see Chapter 2 for details), you can apply it both to an object and to a class. Suppose you have defined a TButton class and a Button1 object of that class. Then the following statements have the same effect:

Text := Button1.ClassName; Text := TButton.ClassName;

On some occasions you need to use the name of a class, but it can also be useful to retrieve a class reference to the class itself or to its base class. The class reference allows you to operate on the class at run time (as you saw in the preceding chapter), whereas the class name is just a string. You can get these class references with the ClassType and ClassParent methods. The first returns a class reference to the class of the object; the second returns a class reference to the object's base class. Once you have a class reference, you can apply to it any class methods of TObject —for example, to call the ClassName method.

Another method that might be useful is InstanceSize , which returns the run-time size of an object. Although you might think that the SizeOf global function provides this information, that function actually returns the size of an object reference—a pointer, which is invariably four bytes—instead of the size of the object itself.

In Listing 3.1, you can find the complete definition of the TObject class, extracted from the System unit. In addition to the methods I've already mentioned, notice InheritsFrom , which provides a test that's similar to the is operator but that can also be applied to classes and class references (the first argument of is must be an object).

Listing 3.1: The definition of the TObject class (in the System RTL unit)

start example

type

TObject =

class


constructor

Create;

procedure

Free;

class function

InitInstance(Instance: Pointer): TObject;

procedure

CleanupInstance;

function

ClassType: TClass;

class function

ClassName: ShortString;

class function

ClassNameIs(

const

Name: string): Boolean;

class function

ClassParent: TClass;

class function

ClassInfo: Pointer;

class function

InstanceSize: Longint;

class function

InheritsFrom(AClass: TClass): Boolean;

class function

MethodAddress(

const

Name: ShortString): Pointer;

class function

MethodName(Address: Pointer): ShortString;

function

FieldAddress(

const

Name: ShortString): Pointer;

function

GetInterface(

const

IID: TGUID;

out

Obj): Boolean;

class function

GetInterfaceEntry(

const

IID: TGUID): PInterfaceEntry;

class function

GetInterfaceTable: PInterfaceTable;

function

SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult;

virtual;


procedure

AfterConstruction;

virtual;


procedure

BeforeDestruction;

virtual;


procedure

Dispatch(

var

Message);

virtual;


procedure

DefaultHandler(

var

Message);

virtual;


class function

NewInstance: TObject;

virtual;


procedure

FreeInstance;

virtual;


destructor

Destroy;

virtual;


end;

end example

Note 

The ClassInfo method returns a pointer to the internal run-time type information (RTTI) of the class, introduced in the next chapter.

{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

These methods of TObject are available for objects of every class, because TObject is the common ancestor class of every class. Here is how you can use these methods to access class information:


procedure

TSenderForm.ShowSender(Sender: TObject);

begin

Memo1.Lines.Add (

'Class Name: '

+ Sender.ClassName);

if

Sender.ClassParent <>

nil then

Memo1.Lines.Add (

'Parent Class: '

+ Sender.ClassParent.ClassName); Memo1.Lines.Add (

'Instance Size: '

+ IntToStr (Sender.InstanceSize));

end;

The code checks to see whether the ClassParent is nil , in case you are using an instance of the TObject type, which has no base type.

This ShowSender method is part of the IfSender example. The method is connected with the OnClick event of several controls: three buttons , a check box, and an edit box. When you click each control, the ShowSender method is invoked with the corresponding control as sender (more on events in Chapter 4). One of the buttons is a Bitmap button, an object of a TButton subclass. You can see an example of the output of this program at run time in Figure 3.7.

click to expand
Figure 3.7: The output of the IfSender example

You can use other methods to perform tests. For example, you can check whether the Sender object is of a specific type with the following code:


if

Sender.ClassType = TButton

then

...

You can also check whether the Sender parameter corresponds to a given object, with this test:


if

Sender = Button1

then

...

Instead of checking for a particular class or object, you'll generally need to test the type compatibility of an object with a given class; that is, you'll need to check whether the class of the object is a given class or one of its subclasses. Doing so lets you know whether you can operate on the object with the methods defined for the class. This test can be accomplished using the InheritsFrom method, which is also called when you use the is operator. The following two tests are equivalent:


if

Sender.InheritsFrom (TButton)

then

...

if

Sender

is

TButton

then

...

Showing Class Information

I've extended the IfSender example to show a complete list of base classes of a given object or class. Once you have a class reference you can add all of its base classes to the ListParent list box with the following code:


with

ListParent.Items

do


begin

Clear;

while

MyClass.ClassParent <> nil

do


begin

MyClass := MyClass.ClassParent; Add (MyClass.ClassName);

end;


end;

You'll notice that I use a class reference at the heart of the while loop, which tests for the absence of a parent class (so that the current class is TObject ). Alternatively, I could have written the while statement in either of the following ways:


while


not

MyClass.ClassNameIs (

'TObject'

)

do

...

while

MyClass <> TObject

do

...

The code in the with statement referring to the ListParent list box is part of the ClassInfo example, which displays the list of parent classes and some other information about a few components of the VCL (basically those on the Standard page of the Component Palette). These components are manually added to a dynamic array holding classes and declared as


private

ClassArray:

array of

TClass;

When the program starts, the array is used to show all the class names in a list box. Selecting an item from the list box triggers the visual presentation of its details and its base classes, as you can see in the program output in Figure 3.8.

click to expand
Figure 3.8: The output of the ClassInfo example

Note 

As a further extension of this example, you can create a tree with all the base classes of the various components in a hierarchy. To do that, I've created the VclHierarchy wizard, discussed in Appendix A ("Extra Delphi Tools by the Author").