📄 ch13.htm
字号:
a list of your friends. To get started, you should create a category called <TT>Friend</TT>
and assign it to all the members of the list that fit that
description. If you always
choose this category from a drop-down list, it will presumably always be spelled
the same. However, if you rely on users to type this word, you might get a series
of related entries that look like this:</P>
<PRE><FONT
COLOR="#0066FF">Friend
Friends
Frends
Acquaintances
Buddies
Buds
Homies
HomeBoys
Amigos
Chums
Cronies
Companions
</FONT></PRE>
<P>This mishmash of spellings and synonyms won't do you any good when you want to
search for the group of
records that fits into the category called <TT>Friend</TT>.</P>
<P>The simplest way to make this happen is to use not a <TT>TDBLookupComboBox</TT>,
but a <TT>TDBLookupCombo</TT>. To use this control, simply pop open the Property
Editor for the
<TT>Items</TT> property and type in a list of categories such as the
following:</P>
<PRE><FONT COLOR="#0066FF">Home
Work
Family
Local Business
Friend
</FONT></PRE>
<P>Now when you run the program and drop down the Category combo box, you will
find
that it contains the preceding list.</P>
<P>The only problem with typing names directly into the <TT>Items</TT> property for
the <TT>TDBLookupCombo</TT> is that changing this list at runtime is impractical.
To do away with this difficulty, the
program stores the list in a separate table,
called <TT>CATS.DB</TT>. This table has a single character field that is 20 characters
wide. After creating the table in the Database Desktop, you can enter the following
five strings into five separate
records:</P>
<PRE><FONT COLOR="#0066FF">Home
Work
Family
Local Business
Friend
</FONT></PRE>
<P>Now that you have two tables, it's best to switch away from the <TT>TDBLookupCombo</TT>
and go instead with the <TT>TDBLookupComboBox</TT>. You make
the basic connection
to the <TT>TDBLookupComboBox</TT> by setting its <TT>DataSource</TT> field to <TT>AddressSource</TT>
and its <TT>DataField</TT> to <TT>Category</TT>. Then set the <TT>ListSource</TT>
for the control to <TT>CatSource</TT>, and set
<TT>ListField</TT> and <TT>KeyField</TT>
to <TT>Category</TT>.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>The addition of the <TT>TDBLookupComboBox</TT>
into the program begs the question of
whether <TT>Address2</TT> is really a flat-file
database because lookups at least give the feel commonly associated with relational
databases. The lookup described in the preceding few paragraphs is not, however,
a pure relational technique, in
that the <TT>CATS</TT> and <TT>Address</TT> tables
are not bound by a primary and a foreign key. <BR>
<BR>
It is, however, a cheat in the design of the program, since my goal was to create
a pure flat-file database. The facts here are simple: I
want the database to be as
simple as possible, but I also want it to be useful. Without this one feature, I
saw the program as hopelessly crippled. As stated earlier, this shows the importance
of relational database concepts in even the simplest
programs. In short, I don't
think I can get any work done without using relational techniques. Relational database
design is not a nicety; it's a necessity.
<HR>
</BLOCKQUOTE>
<P>To allow the user to change the contents of the <TT>CATS</TT>
table, you can create
a form like the one shown in Figure 13.3. This form needs only minimal functionality
because discouraging the user from changing the list except when absolutely necessary
is best. Note that you need to add the
<TT>CategoryDlg</TT> module's header to the
list of files included in the main form. You can do so simply by choosing File |
Include Unit Header.<BR>
<BR>
<A NAME="Heading12"></A><A HREF="13ebu03.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/13/13ebu03.jpg">FIGURE 13.3.</A><FONT COLOR="#000077">
</FONT><I>The Category form enables the user to alter the contents of <TT>CATS.DB</TT>.</I></P>
<P>At program startup, the Category dialog, and the memory associated with it, does
not need to be created and allocated. As a result, you should choose
Options | Project,
select the Forms page, and move the Category dialog into the Available Forms column.
In response to a selection of the Set Category menu item from the main form of the
Address2 program, you can write the following code:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Category1Click(TObject *Sender)
{
CategoryDlg = new TCategoryDlg(this);
CategoryDlg->ShowModal();
CategoryDlg->Free();
}
</FONT></PRE>
<P>This code creates the Category dialog, shows
it to the user, and finally deallocates
its memory after the user is done. You can take this approach because it assures
that the Category dialog is in memory only when absolutely necessary.
<H3><A NAME="Heading13"></A><FONT COLOR="#000077">Setting Up
the Command Structure
for the Program</FONT></H3>
<P>The skeletal structure of the Address2 program is starting to come together. However,
you must complete one remaining task before the core of the program is complete.
A number of basic commands are
issued by the program, and they can be defined in
a single enumerated type:</P>
<PRE><FONT COLOR="#0066FF">enum TCommandType {ctClose, ctInsert, ctPrior,
ctEdit, ctNext, ctCancel,
ctPrint, ctFirst, ctLast,
ctPrintPhone, ctPrintAddress,
ctPrintAll, ctDelete};
</FONT></PRE>
<P>This type enables you to associate each of the program's commands with the <TT>Tag</TT>
field of the appropriate button or menu item, and then to associate all
these buttons
or menu items with a single method that looks like this:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::CommandClick(TObject *Sender)
{
switch (dynamic_cast<TComponent*>(Sender)->Tag)
{
case ctClose: Close();
break;
case ctInsert: DMod->AddressTable->Insert(); break;
case ctPrior: DMod->AddressTable->Prior(); break;
case ctEdit: HandleEditMode(); break;
case ctNext: DMod->AddressTable->Next(); break;
case
ctCancel: DMod->AddressTable->Cancel(); break;
case ctPrint: PrintData(ctPrint); break;
case ctFirst: DMod->AddressTable->First(); break;
case ctLast: DMod->AddressTable->Last(); break;
case ctPrintPhone:
PrintData(ctPrintPhone); break;
case ctPrintAddress: PrintData(ctPrintAddress); break;
case ctPrintAll: PrintData(ctPrintAll); break;
case ctDelete:
AnsiString S = DMod->AddressTableLName->AsString;
if
(MessageBox(Handle, "Delete?", S.c_str(), MB_YESNO) == ID_YES)
DMod->AddressTable->Delete();
break;
}
}
</FONT></PRE>
<P>This code performs a simple typecast to allow you to access the <TT>Tag</TT> field
of the
component that generated the command. This kind of typecast was explained
in depth in Chapter 4, "Events."</P>
<P>There is no reason why you can't have a different method associated with each
of the buttons and menu items in the program.
However, handling things this way is
neater and simpler, and the code you create is much easier to read. The key point
here is to be sure that the <TT>Tag</TT> property of the appropriate control gets
the correct value and that all the controls listed
here have the <TT>OnClick</TT>
method manually set to the <TT>CommandClick</TT> method. I took all these steps while
in design mode, being careful to associate the proper value with the <TT>Tag</TT>
property of each control.</P>
<P>Table 13.3 gives a
brief summary of the commands passed to the <TT>CommandClick</TT>
method. <BR>
<BR>
<B>Table 13.3. Commands passed to CommandClick. </B>
<TABLE BORDER="1">
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><B>Command</B></TD>
<TD
WIDTH="82" ALIGN="LEFT"><B>Type</B></TD>
<TD WIDTH="88" ALIGN="LEFT"><B>Name</B></TD>
<TD WIDTH="45" ALIGN="LEFT"><B>Tag</B></TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Exit</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TMenuItem</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btClose</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">0</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Insert</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btInsert</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">1</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Prior</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btPrior</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">2</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Edit</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btEdit</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">3</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Next</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btNext</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">4</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Cancel</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btCancel</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">5</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Print</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TMenuItem</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btPrint</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">6</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>First</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btFirst</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">7</TD>
</TR>
<TR ALIGN="LEFT" rowspan="1">
<TD WIDTH="82" ALIGN="LEFT"><TT>Last</TT></TD>
<TD WIDTH="82"
ALIGN="LEFT"><TT>TButton</TT></TD>
<TD WIDTH="88" ALIGN="LEFT"><TT>btLast</TT></TD>
<TD WIDTH="45" ALIGN="LEFT">8</TD>
</TR>
</TABLE>
<BR>
<BR>
The task of filling in the <TT>Tag</TT> properties and setting the <TT>OnClick</TT>
events for all
these controls is a bit tedious, but I like the easy-to-read code
produced by following this technique. In particular, I like having all the major
commands send to one method, thereby giving me a single point from which to moderate
the flow of the
program. This is particularly useful when you can handle most of
the commands with a single line of code. Look, for example, at the <TT>ctNext</TT>
and <TT>ctCancel</TT> portions of the <TT>case</TT> statement in the <TT>CommandClick</TT>
method.</P>
<P>All the code in this program will compile at this stage except for the references
in <TT>CommandClick</TT> to <TT>HandleEditMode</TT> and <TT>PrintData</TT>. For now,
you can simply create dummy <TT>HandleEditMode</TT> and <TT>PrintData</TT>
private
methods and leave their contents blank.</P>
<P>At this stage, you're ready to run the Address2 program. You can now insert new
data, iterate through the records you create, cancel accidental changes, and shut
down the program from the menu.
These capabilities create the bare functionality
needed to run the program.
<H3><A NAME="Heading14"></A><FONT COLOR="#000077">Examining the "Rough Draft"
of an Application</FONT></H3>
<P>The program as it exists now is what I mean by
creating a "rough draft"
of a program. The rough draft gets the raw functionality of the program up and running
with minimum fuss, and it lets you take a look at the program to see if it passes
muster.</P>
<P>If you were working for a
third-party client, or for a demanding boss, now would
be the time to call the person or persons in question and have them critique your
work.</P>
<P>"Is this what you're looking for?" you might ask. "Do you think
any fields need to be
there that aren't yet visible? Do you feel that the project
is headed in the right direction?"</P>
<P>Nine times out of ten, these people will come back to you with a slew of suggestions,
most of which have never occurred to you. If they have
irreconcilable differences
of opinion about the project, now is the time to find out. If they have some good
ideas you never considered, now is the time to add them.</P>
<P>Now you also have your chance to let everyone know that after this point
making
major design changes may become impossible. Let everyone know that you're about to
start doing the kind of detail work that is very hard to undo. If people need a day
or two to think about your proposed design, give it to them. Making changes
now,
at the start, is better than after you have everything polished and spit-shined.
By presenting people with a prototype, you give them a sense of participating in
the project, which at least potentially puts them on your side when you turn in the
finished project.</P>
<P>To help illustrate the purpose of this portion of the project development, I have
waited until this time to point out that it might be helpful to add a grid to the
program so that the user can see a list of names from which to
make a selection.
This kind of option may make no sense if you're working with huge datasets, but if
you have only a few hundred or a few thousand records, then a grid can be useful.
(The <TT>TDBGrid</TT> is powerful enough to display huge datasets,
but there is a
reasonable debate over whether grids are the right interface element for tables that
contain hundreds of thousands or millions of records.)</P>
<P>When using the grid, you have to choose which fields will be shown in it. If you
choose
the last name field, then you have a problem for records that include only
the company name, and if you use the company name, then the reverse problem kicks
in. To solve this dilemma, I create a calculated field called <TT>FirstLastCompany</TT>
that
looks like this:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TDMod::AddressTableCalcFields(TDataSet *DataSet)
{
if ((!AddressTableFName->IsNull) || (!AddressTableLName->IsNull))
AddressTableFirstLast->Value =
AddressTableFName->Value + " " + AddressTableLName->Value;
else if (!AddressTableCompany->IsNull)
AddressTableFirstLast->Value = AddressTableCompany->Value;
else
AddressTableFirstLast->Value = "Blank
Record";
}
</FONT></PRE>
<P>The code specifies that if a first or last name appears in the record, then that
name should be used to fill in the value for the calculated field. However, if they
are both blank, then the program will supply the
company name instead. As an afterthought,
I decided that if all three fields are blank, then the string <TT>"Blank Record"</TT>
should appear in the calculated field.</P>
<P>I hope that you can now see why I feel that optimization issues
should always
be put off until the interface, design, and basic coding of the program are taken
through at least one draft. It would be foolish to spend days or weeks optimizing
routines that you, or a client, ultimately do not believe are necessary,
or even
wanted, in the final release version of the program. Get the program up and running,
and then, if everyone agrees that it looks right, you can decide if it needs to be
optimized or if you have time for optimization. Program development is
usually an
iterative process, with a heavy focus on design issues. I don't think that working
on the assumption you'll get it right the first time is wise.
<H3><A NAME="Heading15"></A><FONT COLOR="#000077">Creating a Finished Program</FONT></H3>
<P>The remaining portions of this chapter will tackle the issues that improve this
program to the point that it might be useful in a real-world situation. All but the
most obvious or irrelevant portions of the code for the Address2 program are
explained
in detail in the remainder of this chapter.</P>
<P>Listings 13.1 through 13.7 show the code for the finished program. I discuss most
of this code in one place or another in this chapter. Once again, the goal of this
program is to show you
something that is reasonably close to being useful in a real-world
situation. The gap between the sketchy outline of a program, as discussed earlier,
and a product that is actually usable forms the heart of the discussion that follows.
In fact, most
of my discussion of databases that you have read in the preceding chapters
has concentrated on the bare outlines of a real database program. You have to know
those raw tools to be able to write any kind of database program. However, they are
not
enough, and at some point you have to start putting together something that might
be useful to actual human beings. (Remember them?) That sticky issue of dealing with
human beings, and their often indiscriminate foibles, forms the subtext for much
of
what is said in the rest of this chapter.<BR>
<BR>
<A NAME="Heading16"></A><FONT COLOR="#000077"><B>Listing 13.1. The source code for
the header of the main form of the Address2 program.</B></FONT></P>
<PRE><FONT
COLOR="#0066FF">///////////////////////////////////////
// File: Main.h
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -