Creating Non-Visual Components Overview
In this chapter, you will learn how to build a non-visual component. In particular, I will emphasize the following topics:
In particular, you will
look at one reusable non-visual component called TFindDirs, which is used in a program
that will iterate through a series of directories and will archive the
names of the files found there into a database. You could, for example, use
the program to iterate over all the files on several zip discs of CD-ROMs.
You would then have one database that could be searched when you're looking
for files that might be located on any of a number of different discs.
Why
Create Non-Visual Components?
The program shown in
this chapter uses the TFindDirs component to illustrate the concept of
component reuse. Write the TFindDirs component once, and you can reuse it in
multiple programs. The big bonus here is that TFindDirs is a component and thereby
makes a relatively complex task simple enough that you can perform it by
just dropping an object onto a form and plugging it into your program.
This short chapter is
presented primarily to promote the idea of turning objects that you might
not usually think of as components into components. You can easily see why
an edit control makes a good component, but the fact that an object used to
iterate through directories would make a good component is less obvious. In
fact, I first turned this object into a component on a whim. Once I had it
around, however, I found that I could plug it into all kinds of
applications to aid with one task or another.
Here are some of my
applications that use this component:
My point here is that
writing these utilities became easy after I had the TFindDirs component in place. The core
functionality for each of these programs was easy to implement because it
was based on a component. As you will see, in just a few seconds you can
create a program that uses the component. After you have that much
functionality in place, you can easily add more features.
When
Should Non-Visual Objects Become Components?
Whether you should turn
a particular object into a component is not always clear. For example, the TStringList object has no related component
and cannot be manipulated through visual tools. The question then becomes
"Why have I taken the TFindDirs object and placed it on the Component
Palette?"
As you'll discover, the
advantages of placing TFindDirs on the Component Palette are two-fold:
Creating a component
also has the enormous advantage of forcing, or at least encouraging,
programmers to design a simple interface for an object. After I have placed
an object on the Component Palette, I always want to ensure that the user
can hook it into his or her program in only a few short seconds. I am
therefore strongly inclined to create a simple, easy-to-use interface. If I
don't place the component on the Component Palette, then I find it easier
to slip by with a complex interface that takes many lines of code for myself and others to utilize. To my mind, good
components are not only bug free, but also very easy to use.
The
SearchDirs Program
In this section, you
will find the SearchDirs program, which can be used to iterate through the
subdirectories on a hard drive looking for files with a particular name or
file mask. For example, you could look for *.cpp or m*.cpp or ole2.hpp. This program will put the
names of all the files that match the mask into a database, so you can
search for the files later.
The SearchDirs program
depends on the TFindDirs component, which ships with this book. To
use this component, you must first load it onto the Component Palette,
using the techniques described in the preceding few chapters. In general,
all you have to do is choose Component | Install and then click the Add
button. Browse the Utils subdirectory that ships with this book.
There you will find the FindDirs2.cpp unit. Add it to CMPLIB32.CCL, and you are ready to build the
SearchDirs program. As usual, you might want to run the BuildObjs project
once before installing the component. The source for this program is shown
in Listings 24.1 through 24.8. A screen shot of the program is shown in
Figure 24.1. You need to make sure the CUnleashed alias is in place before
running the program. This is a standard Paradox alias that points at the
data directory off the root directory where the files from the CD that
accompany this book are installed. See both the text right after the
listings and also the readme file for more information about the alias. The point here is that TFindDirs, like TTable and TQuery, is a non-visual component. TFindDirs completes your introduction to
the basic component types by showing you how to build nonvisual components.
You already know how to build visual components. After you understand how
to build non-visual components, most of the power of BCB will be open to
you. The TFindDirs component is also important because it shows you how to create custom event
handlers. ///////////////////////////////////////
// Main.h
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef MainH
#define MainH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\Menus.hpp>
#include <vcl\ComCtrls.hpp>
#include <vcl\DBGrids.hpp>
#include <vcl\Grids.hpp>
#include <vcl\DBCtrls.hpp>
#include <vcl\ExtCtrls.hpp>
#include "FindDirs2.h"
class TForm1 : public TForm
{
__published:
TMainMenu *MainMenu1;
TMenuItem *File1;
TMenuItem *Counter1;
TMenuItem *N1;
TMenuItem *Exit1;
TMenuItem *Run1;
TDBGrid *FileNamesGrid;
TMenuItem *Options1;
TMenuItem *Delete1;
TMenuItem *DisableGrids1;
TPanel *Panel2;
TEdit *Edit1;
TEdit *Edit2;
TMenuItem *N2;
TMenuItem *PickArchive1;
TLabel *Label1;
TLabel *Label2;
TStatusBar *StatusBar1;
TDBGrid *DirNamesGrid;
TFindDirs *FindDirs1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Exit1Click(TObject *Sender);
void __fastcall Delete1Click(TObject *Sender);
void __fastcall PickArchive1Click(TObject *Sender);
void __fastcall OnFoundDir(AnsiString NewDir);
void __fastcall FindDirs1FoundFile(AnsiString NewDir);
private:
int FStartLevel;
AnsiString FCurrentRoot;
AnsiString FDiskName;
void StartRun();
void EndRun();
public:
virtual __fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif
Listing
24.2. The main form for the SearchDirs program.
///////////////////////////////////////
// Main.cpp
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "Main.h"
#include "DiskArchive.h"
#include "FindDirsDMod.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
int GetLevel(AnsiString Source)
{
BOOL Done = False;
int i = 0;
char S[500];
strcpy(S, Source.c_str());
strtok(S, "\\");
while (!Done)
{
if (strtok(NULL, "\\") == NULL)
Done = True;
else
i += 1;
}
return i;
}
void TForm1::StartRun()
{
DirNamesGrid->DataSource = NULL;
FileNamesGrid->DataSource = NULL;
Screen->Cursor = (Controls::TCursor)crHourGlass;
FindDirs1->StartString = Edit1->Text;
FDiskName = Edit2->Text;
FStartLevel = GetLevel(FindDirs1->StartDir);
DMod->DiskNamesTable->Insert();
DMod->DiskNamesTable->FieldByName("DiskName")->AsString = FDiskName;
DMod->DiskNamesTable->Post();
}
void TForm1::EndRun()
{
Screen->Cursor = (Controls::TCursor)crDefault;
DirNamesGrid->DataSource = DMod->DirNamesSource;
FileNamesGrid->DataSource = DMod->FileNamesSource;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if (MessageBox((HWND)Handle, Edit1->Text.c_str(), "Make Run?", MB_YESNO) == ID_YES)
{
StartRun();
FindDirs1->Run();
EndRun();
}
}
void __fastcall TForm1::Exit1Click(TObject *Sender)
{
Close();
}
void __fastcall TForm1::Delete1Click(TObject *Sender)
{
if (MessageBox((HWND)Handle, "Delete", "Delete Dialog", MB_YESNO) == ID_YES)
DMod->CascadingDelete();
}
void __fastcall TForm1::PickArchive1Click(TObject *Sender)
{
ArchiveForm->ShowModal();
}
void __fastcall TForm1::OnFoundDir(AnsiString NewDir)
{
int i;
StatusBar1->SimpleText = NewDir;
StatusBar1->Update();
FCurrentRoot = NewDir;
i = GetLevel(FCurrentRoot);
DMod->DirNamesTable->Insert();
DMod->DirNamesTable->FieldByName("DirName")->AsString = NewDir;
DMod->DirNamesTable->FieldByName("ALevel")->AsInteger = i;
DMod->DirNamesTable->FieldByName("DiskCode")->AsInteger =
DMod->DiskNamesTable->FieldByName("Code")->AsInteger;
DMod->DirNamesTable->Post();
}
void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)
{
AnsiString Temp;
if (FCurrentRoot.Length() == 0)
Temp = FindDirs1->StartDir;
else
Temp = FCurrentRoot;
DMod->FileNamesTable->Insert();
DMod->FileNamesTable->FieldByName("Directory")->AsString = Temp;
DMod->FileNamesTable->FieldByName("FileName")->AsString =
ExtractFileName(NewDir);
DMod->FileNamesTable->Post();
}
Listing
24.3. The header for the programs data module.
///////////////////////////////////////
// FindDirsDMod.h
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef FindDirsDModH
#define FindDirsDModH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\DB.hpp>
#include <vcl\DBTables.hpp>
class TDMod : public TDataModule
{
__published:
TDatabase *FileData1;
TTable *DirNamesTable;
TTable *FileNamesTable;
TTable *DiskNamesTable;
TTable *DiskTypeTable;
TDataSource *DirNamesSource;
TDataSource *FileNamesSource;
TDataSource *DiskNamesSource;
TDataSource *DiskTypeSource;
TIntegerField *DirNamesTableALEVEL;
TStringField *DirNamesTableDIRNAME;
TIntegerField *DirNamesTableDISKCODE;
TIntegerField *FileNamesTableCODE;
TStringField *FileNamesTableDIRECTORY;
TStringField *FileNamesTableFILENAME;
TIntegerField *FileNamesTableDIRCODE;
TIntegerField *DirNamesTableCODE;
TQuery *DeleteFileNames;
TQuery *DeleteDirNames;
TQuery *DeleteDiskNames;
TAutoIncField *DiskTypeTableCode;
TStringField *DiskTypeTableDiskType;
TIntegerField *DiskTypeTableDiskTypeCode;
TAutoIncField *DiskNamesTableCode;
TStringField *DiskNamesTableDiskName;
TIntegerField *DiskNamesTableType;
void __fastcall DModCreate(TObject *Sender);
private:
public:
virtual __fastcall TDMod(TComponent* Owner);
void CascadingDelete(void);
};
extern TDMod *DMod;
#endif
Listing
24.4. The data module for the program.
///////////////////////////////////////
// FindDirsDMod.cpp
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "FindDirsDMod.h"
#pragma resource "*.dfm"
TDMod *DMod;
__fastcall TDMod::TDMod(TComponent* Owner)
: TDataModule(Owner)
{
}
void __fastcall TDMod::DModCreate(TObject *Sender)
{
DirNamesTable->Open();
FileNamesTable->Open();
DiskNamesTable->Open();
DiskTypeTable->Open();
}
void TDMod::CascadingDelete(void)
{
DirNamesTable->First();
DeleteFileNames->Prepare();
while (!DirNamesTable->Eof)
{
DeleteFileNames->ExecSQL();
DirNamesTable->Delete();
}
FileNamesTable->Refresh();
DirNamesTable->Refresh();
int i = DiskNamesTableCode->Value;
DeleteDiskNames->Params->Items[0]->AsInteger = i;
DeleteDiskNames->ExecSQL();
DiskNamesTable->Refresh();
}
Listing
24.5. The header for the DiskArchive form.
///////////////////////////////////////
// DiskArchive.h
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef DiskArchiveH
#define DiskArchiveH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\DBGrids.hpp>
#include <vcl\Grids.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\DBCtrls.hpp>
#include <vcl\Buttons.hpp>
class TArchiveForm : public TForm
{
__published:
TDBGrid *DBGrid1;
TPanel *Panel1;
TDBNavigator *DBNavigator1;
TBitBtn *BitBtn1;
private:
public:
virtual __fastcall TArchiveForm(TComponent* Owner);
};
extern TArchiveForm *ArchiveForm;
#endif
Listing
24.6. The DiskArchive form. This form is used only for displaying data. It
has no custom code.
///////////////////////////////////////
// DiskArchive.cpp
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "DiskArchive.h"
#include "FindDirsDMod.h"
#pragma resource "*.dfm"
TArchiveForm *ArchiveForm;
__fastcall TArchiveForm::TArchiveForm(TComponent* Owner)
: TForm(Owner)
{
}
Listing
24.7. The header for the TFindDirs components.
///////////////////////////////////////
// FindDirs2.h
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef FindDirs2H
#define FindDirs2H
#ifndef ComCtrlsHPP
#include <vcl\ComCtrls.hpp>
#endif
struct TDirInfo
{
TSearchRec SearchRec;
AnsiString CurDirectory;
};
class TDirStack : public TList
{
public:
TDirStack(): TList() {};
void Push(TDirInfo *DirInfo);
TDirInfo *Pop();
};
typedef void __fastcall (__closure *TFoundDirEvent)(AnsiString NewDir);
class TFindDirs : public TComponent
{
private:
#ifdef DEBUG_FIND_DIRS
FILE *fp;
#endif
AnsiString FStartString; // Unchanged string passed in by user.
AnsiString FFileExt;
AnsiString FStartDir; // The directory where the search starts
AnsiString FCurDirectory; // the current directory
AnsiString FFileMask; // The file mask of files to search for
AnsiString FSearchString; // Combine last three into a search string
TDirStack *FDirStack; // Stack of directories in the current dir
TFoundDirEvent FOnFoundDir;
TFoundDirEvent FOnFoundFile;
void GetAllFiles(AnsiString *StartDir);
void FoundAFile(TSearchRec *FileData);
void FoundADir(TSearchRec *FileData);
void __fastcall Initialize(void);
void SetupSearchString();
void GetNextDirectory();
BOOL SetupFirstDirectory();
protected:
__fastcall virtual ~TFindDirs();
virtual void ProcessFile(TSearchRec FileData, AnsiString FileName);
virtual void ProcessDir(TSearchRec FileData, AnsiString DirName);
virtual void __fastcall SetStartString(AnsiString AStartString);
public:
virtual __fastcall TFindDirs(TComponent *AOwner)
: TComponent(AOwner) { FDirStack = NULL; FOnFoundDir = NULL; }
virtual __fastcall TFindDirs(TComponent *AOwner, AnsiString AStartString);
virtual void Run(void);
__property AnsiString StartDir = {read = FStartDir};
__property AnsiString CurDirectory = {read = FCurDirectory};
__published:
__property AnsiString StartString={read=FStartString, write=SetStartString};
__property TFoundDirEvent OnFoundFile={read=FOnFoundFile, write=FOnFoundFile};
__property TFoundDirEvent OnFoundDirf={read=FOnFoundDir, write=FOnFoundDir};
};
class TFindDirsList : public TFindDirs
{
private:
TStringList *FFileList;
TStringList *FDirList;
protected:
__fastcall virtual ~TFindDirsList();
virtual void ProcessFile(TSearchRec FileData, AnsiString FileName);
virtual void ProcessDir(TSearchRec FileData, AnsiString DirName);
public:
virtual __fastcall TFindDirsList(TComponent *AOwner): TFindDirs(AOwner) {}
virtual __fastcall TFindDirsList(TComponent *AOwner, AnsiString AStartString);
__published:
__property TStringList *FileList = {read = FFileList, nodefault};
__property TStringList *DirList = {read = FDirList, nodefault};
};
#endif
Listing
24.8. The main source file for the TFindDirs
component.
///////////////////////////////////////
// FindDirs2.cpp
// SearchDirs
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#include <vcl\ComCtrls.hpp>
#pragma hdrstop
#include "FindDirs2.h"
// -- TDirStack ----------------
void TDirStack::Push(TDirInfo *DirInfo)
{
Add(DirInfo);
}
TDirInfo *TDirStack::Pop()
{
void *Temp = Items[0];
Delete(0);
return (TDirInfo *)Temp;
}
// -- TFindDirs ----------------
__fastcall TFindDirs::TFindDirs(TComponent *AOwner, AnsiString AStartString)
: TComponent(AOwner)
{
SetStartString(AStartString); // Don't set data store directly!
FDirStack = NULL;
FOnFoundDir = NULL;
}
void __fastcall TFindDirs::SetStartString(AnsiString AStartString)
{
FStartString = AStartString;
FStartDir = ExtractFilePath(FStartString);
FFileExt = ExtractFileExt(FStartString);
}
void __fastcall TFindDirs::Initialize(void)
{
#ifdef DEBUG_FIND_DIRS
if ((fp = fopen("c:\searchdirs.txt", "w+")) == NULL)
{
ShowMessage("Can't open debug file");
}
#endif
if (FDirStack)
delete FDirStack;
FDirStack = new TDirStack;
FCurDirectory = "";
FFileMask = "*.*";
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "%s %s %s \n", FStartDir.c_str(), FFileMask.c_str(), FFileExt.c_str());
#endif
}
__fastcall TFindDirs::~TFindDirs()
{
#ifdef DEBUG_FIND_DIRS
fclose(fp);
#endif
}
void TFindDirs::ProcessFile(TSearchRec FileData, AnsiString FileName)
{
if (FOnFoundFile != NULL)
FOnFoundFile(FileName);
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "File found: %s\n", FileName);
#endif
}
void TFindDirs::ProcessDir(TSearchRec FileData, AnsiString DirName)
{
if (FOnFoundDir != NULL)
FOnFoundDir(DirName);
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "Dir found: %s\n", DirName);
#endif
}
void TFindDirs::FoundADir(TSearchRec *FileData)
{
AnsiString FullName;
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "Dir found: %s\n", FileData->Name);
#endif
if ((FileData->Name != ".") &&
(FileData->Name != ".."))
{
TDirInfo *DirInfo = new TDirInfo;
DirInfo->CurDirectory = AnsiString(FCurDirectory + FileData->Name + "\\");
DirInfo->SearchRec = *FileData;
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "DirInfo: %s\n", DirInfo->SearchRec.Name);
fflush(fp);
#endif
FDirStack->Push(DirInfo);
}
}
///////////////////////////////////////
// FoundAFile
///////////////////////////////////////
void TFindDirs::FoundAFile(TSearchRec *FileData)
{
AnsiString FullName;
if ((FFileExt == ".*") ||
(UpperCase(ExtractFileExt(FileData->Name)) == UpperCase(FFileExt)))
{
FullName = FStartDir + FCurDirectory + FileData->Name;
ProcessFile(*FileData, FullName);
}
}
///////////////////////////////////////
// GetAllFiles
///////////////////////////////////////
void TFindDirs::GetAllFiles(AnsiString *StartDir)
{
TSearchRec FileData;
int Info;
Info = FindFirst(StartDir->c_str(), faDirectory, FileData);
while (Info == 0)
{
if (FileData.Attr == faDirectory)
FoundADir(&FileData);
else
FoundAFile(&FileData);
Info = FindNext(FileData);
}
FindClose(&FileData.FindData);
}
///////////////////////////////////////
// SetupSearchString
///////////////////////////////////////
void TFindDirs::SetupSearchString()
{
FSearchString = FStartDir + FCurDirectory + FFileMask;
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "FSearchString: %s \n", FSearchString);
#endif
}
///////////////////////////////////////
// GetNextDirectory
///////////////////////////////////////
void TFindDirs::GetNextDirectory()
{
TDirInfo *FDirInfo = FDirStack->Pop();
FCurDirectory = FDirInfo->CurDirectory;
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "Next Directory: %s\n", FCurDirectory);
fflush(fp);
#endif
ProcessDir(FDirInfo->SearchRec, FStartDir + FCurDirectory);
delete FDirInfo;
}
BOOL TFindDirs::SetupFirstDirectory()
{
TSearchRec FileData;
AnsiString SearchStr = FStartDir + FFileMask;
int Info = FindFirst(SearchStr.c_str(), faDirectory, FileData);
FindClose(&FileData.FindData);
if (Info == 0)
{
TDirInfo *DirInfo = new TDirInfo;
DirInfo->CurDirectory = FCurDirectory;
FileData.Name = FStartDir;
DirInfo->SearchRec = FileData;
FDirStack->Push(DirInfo);
return TRUE;
}
else
return FALSE;
}
///////////////////////////////////////
// Run: FindFilesAndDirs
///////////////////////////////////////
void TFindDirs::Run(void)
{
BOOL FDone = False;
BOOL FirstTime = TRUE;
Initialize();
if (!SetupFirstDirectory())
{
ShowMessage("Invalid Search String");
return;
}
while (!FDone)
{
SetupSearchString();
if (!FirstTime)
GetAllFiles(&FSearchString);
if (FDirStack->Count > 0)
GetNextDirectory();
else
FDone = True;
FirstTime = FALSE;
}
FDirStack->Free();
FDirStack = NULL;
}
///////////////////////////////////////
// TFindDirsList //////////////////////
///////////////////////////////////////
__fastcall TFindDirsList::TFindDirsList(TComponent *AOwner,
AnsiString AStartDir): TFindDirs(AOwner, AStartDir)
{
FFileList = new TStringList;
FFileList->Sorted = True;
FDirList = new TStringList;
FDirList->Sorted = True;
}
__fastcall TFindDirsList::~TFindDirsList()
{
FFileList->Free();
FDirList->Free();
}
void TFindDirsList::ProcessFile(TSearchRec FileData, AnsiString FileName)
{
FFileList->Add(FileName);
}
void TFindDirsList::ProcessDir(TSearchRec FileData, AnsiString DirName)
{
FDirList->Add(DirName);
}
namespace Finddirs2
{
void __fastcall Register()
{
TComponentClass classes[2] = {__classid(TFindDirs),
__classid(TFindDirsList)};
RegisterComponents("Unleash", classes, 1);
}
}
The database aspects of
this program are important. You will find the files used by the program in
the Data directory on the CD that ships with this book. As
I explain in the readme file that accompanies the CD, you should set up an
alias called CUnleashed that points to
these files. Needless to say, you should recreate the data directory on
your hard drive, and should not use the read-only directory found on the
CD, but should make sure they've been copied onto your hard drive. The DatabaseName for the TDatabase object used in my version of
the program contains the string FileData, so you might get an error
about that alias if you try to run the program. However, you do not want to
try to fix the FileData alias, rather the one called CUnleashed. The data module for the
program is shown in Figure 24.2.
To use the program,
first point it to a subdirectory on your hard disk. Then type in a file
mask in the edit control at the top of the form. For example, you might
type in c:\temp\*.cpp or simply c:\temp\*.*. Be sure to type in the file mask. It
would cause an error if you typed I:\ instead of I:\*.*. (In general, the program is
not robust enough to check for many user errors.) When you click the button
at the bottom of the program, the code iterates through all the directories
beneath the one you pointed to and finds all the files that have the
extension you designated. The program then places these files in a list
database.
NOTE: I tested the TFindDirs component fairly extensively. For
instance, I aimed it at the root of my C drive, which contains 1.12GB of
space, with all but about 100MB used. The program ran fine against the
thousands of files on that drive. I also aimed the component at nested
directories containing long filenames, and again it handled the challenge
without incident. To use the FindDirs component, you need do nothing
more than assign it a string containing the file mask you want to use. You
can do so via a property in the Object Inspector; you can insert the
information at runtime:
if (MessageBox((HWND)Handle, Edit1->Text.c_str(), "Make Run?", MB_YESNO) == ID_YES)
{
FindDirs1->StartString = Edit1->Text;
FindDirs1->Run();
}
That's all you have to
do to use the component, other than respond to events when files or
directories are found. You can set up these event handlers automatically,
as described in the next section.
Iterating
Through Directories with TFindDirs
The SearchDirs program
uses the TFindDirs component to iterate through directories. The TFindDirs component sends events to your
program whenever a new directory or a new file is found. The events include
the name of the new directory or file, as well as information about the
size and date of the files the component finds. You can respond to these
events in any way you want. For example, this program stores the names in a
Paradox file.
NOTE: The SearchDirs program tends to create huge
database files fairly quickly. The main problem here is that I need to
store long filenames, which means I need to set aside large fields in the
table. This problem is severe enough that I am going to eventually need to
come up with some kind of custom solution to storing these strings. For
now, however, I am just living with some very big DB files on my hard
drive. The SearchDirs program
responds as follows when a file with the proper extension is found:
void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)
{
AnsiString Temp;
if (FCurrentRoot.Length() == 0)
Temp = FindDirs1->StartDir;
else
Temp = FCurrentRoot;
DMod->FileNamesTable->Insert();
DMod->FileNamesTable->FieldByName("Directory")->AsString = Temp;
DMod->FileNamesTable->FieldByName("FileName")->AsString =
ExtractFileName(NewDir);
DMod->FileNamesTable->Post();
}
As you can see, the
code does nothing more than shove the filename in a table.
To set up this method,
you merely have to click the TFindDirs OnFoundFile event in the Object Inspector. The OnFoundFile and OnFoundDir events of TFindDirs are of type TFoundDirEvent:
typedef void __fastcall (__closure *TFoundDirEvent)(AnsiString NewDir);
I created two private
events for TFindDirs that are of this type:
TFoundDirEvent FOnFoundDir;
TFoundDirEvent FOnFoundFile;
I then make these
events into published properties that can be seen in the Object Inspector:
__property TFoundDirEvent OnFoundFile={read=FOnFoundFile, write=FOnFoundFile};
__property TFoundDirEvent OnFoundDirf={read=FOnFoundDir, write=FOnFoundDir};
If you're unclear about
what is happening here, study the code in FindDirs2.h, or turn to the section about
creating and using events covered in depth in Chapter 4,
"Events."
If you have events like
this one set up, then you need merely to click them in the Object
Inspector, and the outline for your code will be created for you
automatically. The following, for example, is the code BCB will produce if
you click the OnFoundDir event:
void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)
{
}
The great thing about
events is that they not only save you time when you're typing, but they
also help to show how to use the component. After you see a method like FindDirs1FoundFile, you don't have to worry about
going to the online help to find out how to get the directories found by
the component! What you're supposed to do is obvious.
The following code in FoundDirs2.cpp calls the event handlers:
void TFindDirs::ProcessDir(TSearchRec FileData, AnsiString DirName)
{
if (FOnFoundDir != NULL)
FOnFoundDir(DirName);
#ifdef DEBUG_FIND_DIRS
fprintf(fp, "Dir found: %s\n", DirName);
#endif
}
This code checks to see whether the FOnFoundDir event is set to NULL. If it is not, the event is called.
NOTE: Notice that the code for the ProcessDir method uses conditional
compilation to leave you the option of writing debug output to a file. I
used this code when I was creating the unit. My goal was to find a way to
write out a list of the directories and files found during a run of the
program. I could then study the list to make sure my algorithm was working
correctly.
Layering
Your Objects
TFindDirs has a descendant object called TFindDirsList. Part of the built-in
functionality of the TFindDirsList unit is to maintain lists of the files it
finds. After you finish searching all the directories, the list is ready
for you to do with as you want. This list is kept in a TStringList object, so you can just assign
it to the Items property in a list box, as shown in this code excerpt:
ListBox1->Items = FileIterator1->FileList;
This idea of layering
your components so that you can create different objects, descending from
different parents, under different circumstances, is key to object-oriented design. You don't want to push too much functionality up
too high in the object hierarchy; otherwise, you will be forced to rewrite
the object to get access to a subset of its functionality. For example, if
the BCB developers had not created a TDataSet component but had instead
created one component called TTable, they would have had to duplicate that
same functionality in the TQuery component. This approach is wasteful. The
smart thing to do is to build a component called TDataSet and end its functionality at the
point at which the specific attributes of TQuery and TTable need to be defined. That way, TQuery and TTable can both reuse the
functionality of TDataSet rather than have to rewrite that same
functionality for both objects.
Before I close this
section, let me reiterate some key points. The TFindDirs object is the brains of this
particular operation. It knows how to iterate through directories, how to
find all the files in each directory, and how to notify the user when new
directories or files are found. The SearchDirs program is just a wrapper
around this core functionality.
Iterating
Through Directories
The task of iterating
through directories has a simple recursive solution. However, recursion is
a slow and time-consuming technique that is also wasteful of stack space. As
a result, TFindDirs creates its own stacks and pushes the directories it finds onto them.
NOTE: BCB includes some built-in tools for creating stacks and lists. For example, the TList and TStringList objects are available. I use these tools here because they are simple objects specific to the VCL. Another alternative would have been to use the STL. You can find the
following objects in FindDirs2.h: TDirInfo: This simple structure keeps
track of the current directory and of the complete set of information
describing a particular file.
TDirStack: I need a place to push each
directory after I find it. That leaves me free to iterate through all the
files in the directory first and then go back and pop each subdirectory off
the stack when I am free to examine it.
TFindDirs: This object provides the
ability to iterate through directories.
TFindDirsList: This object adds TStringList objects to TFindDirs. These objects are accessible
as properties, and they are maintained automatically by the object. I do
not use the TFindDirsList object in the SearchDirs example. However, you'll find it very
helpful when you're experimenting with these objects on your own.
To dust off the classic
analogy used in these situations, the stacks implemented here are like
piles of plates in a kitchen cabinet. You can put one plate down and then
add another one to it. When you need one of the plates, you take the first
one off either the top or the bottom, depending on whether it's a FIFO or a
LIFO stack. Putting a new plate on the top of a stack is called pushing the
object onto the stack, and removing a plate is called popping the object
off the stack. For more information on stacks, refer to any book on basic
programming theory. Books that cover the STL (Standard Template Library) in
depth also usually cover this subject in the process.
Look at the
implementation of the following FIFO stack:
void TDirStack::Push(TDirInfo *DirInfo)
{
Add(DirInfo);
}
TDirInfo *TDirStack::Pop()
{
void *Temp = Items[0];
Delete(0);
return (TDirInfo *)Temp;
}
The code is so simple
because it is built on top of the TList object that is part of the VCL:
class TDirStack : public TList
{
public:
TDirStack(): TList() {};
void Push(TDirInfo *DirInfo);
TDirInfo *Pop();
};
One look at this simple code, and you can see why I was drawn to the TList object rather than the STL. If
I had had any doubt in my mind, then, of course, I would have turned to the
VCL, because this book is about BCB, not about the Standard Template
Library.
Using FindFirst, FindNext, and FindClose
In this section, I
continue the examination of the stacks created in the TFindDirs units. The cores of these
stacks are the calls to FindFirst, FindNext, and FindClose that search through directories
looking for particular files.
Using FindFirst, FindNext, and FindClose is like typing DIR in a directory at the DOS
prompt. FindFirst finds the first file in the directory, and FindNext finds the remaining files. You
should call FindClose when you're finished with the process. FindFirst and the others are found in the SysUtils unit.
These calls enable you
to specify a directory and file mask, as if you were issuing a command of
the following type at the DOS prompt:
dir c:\aut*.bat
This command would, of
course, show all files beginning with aut and ending in .bat. This particular command would
typically find AUTOEXEC.BAT and perhaps one or two other files.
When you call FindFirst, you pass in three parameters:
extern int __fastcall FindFirst(const System::AnsiString Path,
int Attr, TSearchRec &F);
The first parameter
contains the path and file mask that specify the files you want to find. For
example, you might pass in "c:\\BCB\\include\\vcl\\*.hpp" in this parameter. The second
parameter lists the type of files you want to see:
faReadOnly 0x01 Read-only files
faHidden 0x02 Hidden files
faSysFile 0x04 System files
faVolumeID 0x08 Volume ID files
faDirectory 0x10 Directory files
faArchive 0x20 Archive files
faAnyFile 0x3F Any file
Most of the time, you
should pass in faArchive in this parameter. However, if you want to
see directories, pass in faDirectory. The Attribute parameter is not a filter. No
matter what flags you use, you will always get all normal files in the
directory. Passing faDirectory causes directories to be included in the
list of normal files; it does not limit the list to directories. You can
use OR to
concatenate several different faXXX constants, if you want. The final
parameter is a variable of type TSearchRec, which is declared as follows:
struct TSearchRec {
int Time;
int Size;
int Attr;
System::AnsiString Name;
int ExcludeAttr;
int FindHandle;
WIN32_FIND_DATAA FindData; };
The most important
value in TSearchRec is the Name field, which on success specifies the name of the file found. FindFirst returns zero if it finds a file
and nonzero if the call fails. However, I rely heavily on the FindData portion of the record. FindData is the original structure
passed back from the operating system. The rest of the fields are derived
from it and are presented here in this form so as to present a simple,
easy-to-use interface to VCL programmers.
WIN32_FIND_DATA looks like this:
typedef struct _WIN32_FIND_DATA { // wfd
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
FindNext works exactly like FindFirst, except that you have to pass
in only a variable of type TSearchRec because it is assumed that the mask and
file attribute are the same. Once again, FindNext returns zero if all goes well, and a nonzero value if it can't find a file. You
should call FindClose after completing a FindFirst/FindNext sequence.
Given this information,
here is a simple way to call FindFirst, FindNext, and FindClose:
void TFindDirs::GetAllFiles(AnsiString *StartDir)
{
TSearchRec FileData;
int Info;
Info = FindFirst(StartDir->c_str(), faDirectory, FileData);
while (Info == 0)
{
if (FileData.Attr == faDirectory)
FoundADir(&FileData);
else
FoundAFile(&FileData);
Info = FindNext(FileData);
}
FindClose(&FileData.FindData);
}
That's all I'm going to
say about the basic structure of the TFindDirs object. As I said earlier, you
can learn more about stacks by studying a book on basic programming data
structures. This book, however, is about BCB, so I'm going to move on to a
discussion of creating event handlers.
SummaryThe SearchDirs program,
along with the TFindDirs component, points the way toward an
understanding of BCB's greatest strengths. TFindDirs is not a particularly difficult
piece of code, but it is sufficiently complex to highlight the fact that
you can place almost any kind of logic inside a BCB object. If you want to
write multimedia code, or code that enables conversations on a network or
simulates the behavior of a submarine, you can write a BCB component or set
of components that will encapsulate the logic needed to reach your goal.
More importantly, these components can then be placed on the Component
Palette and dropped onto a form where they can easily be manipulated
through the Object Inspector. Objects help you hide complexity and help you
reuse code.
The Object
Inspector--and its related property editors and component editors--provide
an elegant, easy-to-use interface to any object. Component architectures
represent one of the most important tools in programming today, and BCB has
by far the best implementation of a component architecture currently available in the C++ market. In fact, the VCL is
several orders of magnitude better at creating components than any other
existing Windows-based technology.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++
Builder.
|