developer.com - Reference
Click here to support our advertisers
SOFTWARE FOR SALE
BOOKS FOR SALE
SEARCH CENTRAL
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE
Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US
Journal:
Get the weekly email highlights from the most popular journal for developers!
Current issue
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com
All Categories :
C/C++
Ch 24 -- Creating Non-Visual Components
Charlie Calvert's C++ Builder Unleashed
- 24 -
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:
Descending directly from TComponent
Using and designing non-visual components
Using FindFirst and FindNext to iterate through the files in
directories
Creating your own stacks
Pushing and popping items off a stack
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:
A program for iterating through directories to delete files that I no longer
need.
A program for "touching" all the files in a series of directories so
that they have the same date.
A file backup utility for comparing two directory structures to be sure they
are identical. This same program will also produce scripts that copy files from one
directory structure to the other if a mismatch occurs.
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:
You might need to tweak several options before you use this object. In particular,
you need to decide whether you want to have the lists of directories and files that
you find saved to memory in a TStringList. Letting the programmer decide
these matters by clicking a property can go a long way toward presenting a clean,
easy-to-use interface for an object.
The TFindDirs object has two features that can be accessed through the
Events page. Specifically, custom event handlers can be notified every time
a new file or directory has been found. However, constructing an event handler manually
can be confusing, particularly if you don't know which parameters will be passed
to the functions involved. If you place a component on the Component Palette, you
do not need to guess about how to handle an event. All it takes is a quick click
on the Events page, and the event handler is created for you automatically!
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.
FIGURE
24.1. A screen shot of the main form
of the SearchDirs program.
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.
Listing 24.1. The header for the
main form for the SearchDirs program.
///////////////////////////////////////
// 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.
FIGURE
24.2. The data module for the SearchDirs
program.
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.
One problem I have had with the component involves sharing violations. If the SearchDirs
program is iterating through a series of files, and one of which is locked by the
file system, then the TFindDirs component will probably raise a rather unsightly
access violation. Check the CD and my Web site to see if I have come up with a fix
for this problem. In the meantime, you should make sure other programs are closed
before running the SearchDirs program, or be sure to aim the program at drives that
do not have open files on them.
Note that running the SearchDirs program against huge drives will create truly monolithic
Paradox files containing lists of the files found during the search. For instance,
after running against my C drive, the main DB file and its index were both more than
9MB in size. I have not tested the program to see what would happen if I ran out
of disk space for the DB file during a run of the program.
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.
One possible solution to this problem would be to save all the information from a
directory in a TStringList and then save the TStringList as a single
string, which is one of the capabilities of this object. I could then save the whole
string in a single blob field, which would make a better use of space. When I want
to display the directory to a user, I could ask the TStringList to read
the string in again and store it in a TMemoryStream.
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.
Summary
The 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.
©Copyright, Macmillan Computer Publishing. All rights reserved.
Contact
reference@earthweb.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.
Wyszukiwarka
Podobne podstrony:
ch24 (9)ch24 (16)ch24ch24 (10)ch24ch24ch24ch24 (8)ch24CH24ch24ch24 (5)ch24 (13)CH24 (12)ch24ch24 (6)ch24!ch24!ch24więcej podobnych podstron