📄 ch24.htm
字号:
incident. <BR>
<BR>
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 <TT>TFindDirs</TT> 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. <BR>
<BR>
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.
<HR>
</BLOCKQUOTE>
<P>To use the <TT>FindDirs</TT> 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:</P>
<PRE><FONT COLOR="#0066FF"> if (MessageBox((HWND)Handle, Edit1->Text.c_str(), "Make Run?", MB_YESNO) == ID_YES)
{
FindDirs1->StartString = Edit1->Text;
FindDirs1->Run();
}
</FONT></PRE>
<P>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.
<H3><A NAME="Heading18"></A><FONT COLOR="#000077">Iterating Through Directories with
TFindDirs</FONT></H3>
<P>The SearchDirs program uses the <TT>TFindDirs</TT> component to iterate through
directories.
The <TT>TFindDirs</TT> 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.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>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. <BR>
<BR>
One possible solution to this problem would be to save all the information from a
directory in a
<TT>TStringList</TT> and then save the <TT>TStringList</TT> 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 <TT>TStringList</TT> to read
the string in again and store it in a <TT>TMemoryStream</TT>.
<HR>
</BLOCKQUOTE>
<P>The SearchDirs program responds as follows when a file with the proper extension
is
found:</P>
<PRE><FONT COLOR="#0066FF">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();
}
</FONT></PRE>
<P>As you can see, the code does nothing more than shove the filename in a table.</P>
<P>To set up this method, you merely have to click the <TT>TFindDirs</TT> <TT>OnFoundFile</TT>
event in the
Object Inspector. The <TT>OnFoundFile</TT> and <TT>OnFoundDir</TT> events
of <TT>TFindDirs</TT> are of type <TT>TFoundDirEvent</TT>:</P>
<PRE><FONT COLOR="#0066FF">typedef void __fastcall (__closure *TFoundDirEvent)(AnsiString NewDir);
</FONT></PRE>
<P>I created two private events for <TT>TFindDirs</TT> that are of this type:</P>
<PRE><FONT COLOR="#0066FF">TFoundDirEvent FOnFoundDir;
</FONT></PRE>
<PRE><FONT COLOR="#0066FF">TFoundDirEvent FOnFoundFile;
</FONT></PRE>
<P>I then make these events
into published properties that can be seen in the Object
Inspector:</P>
<PRE><FONT COLOR="#0066FF">__property TFoundDirEvent OnFoundFile={read=FOnFoundFile, write=FOnFoundFile};
__property TFoundDirEvent OnFoundDirf={read=FOnFoundDir,
write=FOnFoundDir};
</FONT></PRE>
<P>If you're unclear about what is happening here, study the code in <TT>FindDirs2.h</TT>,
or turn to the section about creating and using events covered in depth in Chapter
4, "Events."</P>
<P>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 <TT>OnFoundDir</TT>
event:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)
{
}
</FONT></PRE>
<P>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
<TT>FindDirs1FoundFile</TT>, 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.</P>
<P>The following code
in <TT>FoundDirs2.cpp</TT> calls the event handlers:</P>
<PRE><FONT COLOR="#0066FF">void TFindDirs::ProcessDir(TSearchRec FileData, AnsiString DirName)
{
if (FOnFoundDir != NULL)
FOnFoundDir(DirName);
#ifdef DEBUG_FIND_DIRS
fprintf(fp,
"Dir found: %s\n", DirName);
#endif
}
</FONT></PRE>
<P>This code checks to see whether the <TT>FOnFoundDir</TT> event is set to <TT>NULL</TT>.
If it is not, the event is called.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Notice that the code for the <TT>ProcessDir</TT>
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.
<HR>
</BLOCKQUOTE>
<H4><A NAME="Heading21"></A><FONT
COLOR="#000077">Layering Your Objects</FONT></H4>
<P><TT>TFindDirs</TT> has a descendant object called <TT>TFindDirsList</TT>. Part
of the built-in functionality of the <TT>TFindDirsList</TT> 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 <TT>TStringList</TT>
object, so you can just assign it to the <TT>Items</TT> property in a list box, as
shown in this code excerpt:</P>
<PRE><FONT COLOR="#0066FF">ListBox1->Items = FileIterator1->FileList;
</FONT></PRE>
<P>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 <TT>TDataSet</TT>
component but had instead created one component called <TT>TTable</TT>, they would
have had to duplicate that same functionality in the <TT>TQuery</TT> component. This
approach is wasteful. The smart thing to do is to build
a component called <TT>TDataSet</TT>
and end its functionality at the point at which the specific attributes of <TT>TQuery</TT>
and <TT>TTable</TT> need to be defined. That way, <TT>TQuery</TT> and <TT>TTable</TT>
can both reuse the functionality of
<TT>TDataSet</TT> rather than have to rewrite
that same functionality for both objects.</P>
<P>Before I close this section, let me reiterate some key points. The <TT>TFindDirs</TT>
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.
<H3><A NAME="Heading22"></A><FONT
COLOR="#000077">Iterating Through Directories</FONT></H3>
<P>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,
<TT>TFindDirs</TT> creates its own stacks and pushes the directories
it finds onto them.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>BCB includes some built-in tools
for creating stacks and lists.
For example, the <TT>TList</TT> and <TT>TStringList</TT>
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.
<HR>
</BLOCKQUOTE>
<P>You can find
the following objects in <TT>FindDirs2.h</TT>: <TT>TDirInfo</TT>:
This simple structure keeps track of the current directory and of the complete set
of information describing a particular file.</P>
<P><TT>TDirStack</TT>: 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.</P>
<P><TT>TFindDirs</TT>: This object provides the ability
to iterate through directories.
<BLOCKQUOTE>
<P><TT>TFindDirsList</TT>: This object adds <TT>TStringList</TT> objects to <TT>TFindDirs</TT>.
These objects are accessible as properties, and they are maintained automatically
by the object. I do not
use the <TT>TFindDirsList</TT> object in the <TT>SearchDirs</TT>
example. However, you'll find it very helpful when you're experimenting with these
objects on your own.
</BLOCKQUOTE>
<P>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.</P>
<P>Look at the implementation of the following FIFO stack:</P>
<PRE><FONT COLOR="#0066FF">void TDirStack::Push(TDirInfo *DirInfo)
{
Add(DirInfo);
}
TDirInfo *TDirStack::Pop()
{
void *Temp = Items[0];
Delete(0);
return (TDirInfo *)Temp;
}
</FONT></PRE>
<P>The code is so simple because it is built on top of the <TT>TList</TT> object
that is part of the VCL:</P>
<PRE><FONT COLOR="#0066FF">class TDirStack : public TList
{
public:
TDirStack(): TList() {};
void Push(TDirInfo *DirInfo);
TDirInfo *Pop();
};
</FONT></PRE>
<P>One look at this simple code, and you can see why I was drawn to the
<TT>TList</TT>
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.
<H3><A NAME="Heading24"></A><FONT
COLOR="#000077">Using </FONT><FONT SIZE="4" COLOR="#000077"><TT>FindFirst</TT></FONT><FONT
COLOR="#000077">, <TT>FindNext</TT>, and <TT>FindClose</TT></FONT></H3>
<P>In this section, I continue the examination of the stacks created in the
<TT>TFindDirs</TT>
units. The cores of these stacks are the calls to <TT>FindFirst</TT>, <TT>FindNext</TT>,
and <TT>FindClose</TT> that search through directories looking for particular files.</P>
<P>Using <TT>FindFirst</TT>, <TT>FindNext</TT>, and
<TT>FindClose</TT> is like typing
<TT>DIR</TT> in a directory at the DOS prompt. <TT>FindFirst</TT> finds the first
file in the directory, and <TT>FindNext</TT> finds the remaining files. You should
call <TT>FindClose</TT> when you're finished with
the process. <TT>FindFirst</TT>
and the others are found in the <TT>SysUtils</TT> unit.</P>
<P>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:</P>
<PRE><FONT
COLOR="#0066FF">dir c:\aut*.bat
</FONT></PRE>
<P>This command would, of course, show all files beginning with <TT>aut</TT> and
ending in <TT>.bat</TT>. This particular command would typically find <TT>AUTOEXEC.BAT</TT>
and perhaps one or two other
files.</P>
<P>When you call <TT>FindFirst</TT>, you pass in three parameters:</P>
<PRE><FONT COLOR="#0066FF">extern int __fastcall FindFirst(const System::AnsiString Path,
int Attr, TSearchRec &F);
</FONT></PRE>
<P>The first parameter contains the path and file mask that specify the files you
want to find. For example, you might pass in <TT>"c:\\BCB\\include\\vcl\\*.hpp"</TT>
in this parameter. The second parameter lists the type of files you want
to see:</P>
<PRE><FONT COLOR="#0066FF">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
</FONT></PRE>
<P>Most of the time, you should pass in <TT>faArchive</TT> in this parameter. However,
if you want to see directories, pass in <TT>faDirectory</TT>. The <TT>Attribute</TT>
parameter is not a filter. No matter what flags you use, you will always get all
normal files in the directory. Passing <TT>faDirectory</TT> causes directories to
be included in the list of normal files; it does not limit the list to directories.
You
can use <TT>OR</TT> to concatenate several different <TT>fa</TT>XXX constants,
if you want. The final parameter is a variable of type <TT>TSearchRec</TT>, which
is declared as follows:</P>
<PRE><FONT COLOR="#0066FF">struct TSearchRec {
int Time;
int Size;
int Attr;
System::AnsiString Name;
int ExcludeAttr;
int FindHandle;
WIN32_FIND_DATAA FindData; };
</FONT></PRE>
<P>The most important value in <TT>TSearchRec</TT> is the <TT>Name</TT> field, which
on success specifies the
name of the file found. <TT>FindFirst</TT> returns zero
if it finds a file and nonzero if the call fails. However, I rely heavily on the
<TT>FindData</TT> portion of the record. <TT>FindData</TT> 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.</P>
<P><TT>WIN32_FIND_DATA</TT> looks like this:</P>
<PRE><FONT
COLOR="#0066FF">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;
</FONT></PRE>
<P><TT>FindNext</TT> works exactly like <TT>FindFirst</TT>, except that you have
to pass in only a
variable of type <TT>TSearchRec</TT> because it is assumed that
the mask and file attribute are the same. Once again, <TT>FindNext</TT> returns zero
if all goes well, and a nonzero value if it can't find a file. You should call <TT>FindClose</TT>
after completing a <TT>FindFirst</TT>/<TT>FindNext </TT>sequence.</P>
<P>Given this information, here is a simple way to call <TT>FindFirst</TT>, <TT>FindNext</TT>,
and <TT>FindClose</TT>:</P>
<PRE><FONT COLOR="#0066FF">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);
}
</FONT></PRE>
<P>That's all I'm going to say about the basic structure of the <TT>TFindDirs</TT>
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.
<H3><A NAME="Heading25"></A><FONT
COLOR="#000077">Summary</FONT></H3>
<P>The SearchDirs program, along with the <TT>TFindDirs</TT> component, points the
way toward an understanding of BCB's greatest strengths. <TT>TFindDirs</TT> 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.</P>
<P>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.</P>
<P ALIGN="CENTER"><A HREF="index-3.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/index.htm"><IMG SRC="toc.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/toc.gif" WIDTH="41" HEIGHT="41"
ALIGN="BOTTOM" ALT="TOC" BORDER="0" NAME="toc8"></A><A HREF="ch23.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/ch23.htm"><IMG SRC="back-1.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/back.gif"
WIDTH="41"
HEIGHT="41" ALIGN="BOTTOM" ALT="BACK" BORDER="0" NAME="toc6"></A><A HREF="ch25.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/ch25.htm"><IMG
SRC="forward.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/forward.gif" WIDTH="41" HEIGHT="41" ALIGN="BOTTOM" ALT="FORWARD" BORDER="0"
NAME="toc7"></A></P>
<P>
<P ALIGN="CENTER"><FONT
COLOR="#000000">©</FONT><A HREF="copy.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/copy.htm">Copyright</A><FONT
COLOR="#000000">, Macmillan Computer Publishing. All rights reserved.</FONT>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -