📄 ch14.htm
字号:
AnsiString __fastcall TDMod::GetAddress()
{
return
DMod->AddressTableAddress1->AsString + `\r' +
DMod->AddressTableAddress2->AsString + `\r' +
DMod->AddressTableCity->AsString + `\r' +
DMod->AddressTableState->AsString + `\r' +
DMod->AddressTableZip->AsString;
}
AnsiString __fastcall TDMod::GetPhone()
{
return DMod->PhoneTableDescription->AsString + `\r' +
DMod->PhoneTableNumber->AsString + `\r' +
DMod->PhoneTableExt->AsString;
}
AnsiString __fastcall TDMod::GetEMail()
{
return DMod->EMailTableAddress->AsString + `\r' +
DMod->EMailTableDescription->AsString + `\r' +
DMod->EMailTableService->AsString;
}
</FONT></PRE>
<P><A
NAME="Heading14"></A><FONT COLOR="#000077"><B>Listing 14.5. The header for
the Globals unit.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">///////////////////////////////////////
// File: Globals.h
// Project: KdAdd
// Copyright (c) 1997 by Charlie
Calvert
//
#ifndef GlobalsH
#define GlobalsH
AnsiString GetError(int ErrNo, AnsiString &S);
#endif
</FONT></PRE>
<P><A NAME="Heading15"></A><FONT COLOR="#000077"><B>Listing 14.6. The main module
for the Globals unit.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">///////////////////////////////////////
// File: Globals.cpp
// Project: KdAdd
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "Globals.h"
#define
ERR_STRING_SIZE 255
AnsiString GetError(int ErrNo, AnsiString &S)
{
S.SetLength(ERR_STRING_SIZE);
LoadString(HINSTANCE(HInstance), 1, S.c_str(), ERR_STRING_SIZE);
return S;
}
</FONT></PRE>
<P><A NAME="Heading16"></A><FONT
COLOR="#000077"><B>Listing 14.7. The custom RC file
for the project. This is a stub to be filled out later.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">#include "kderrs.inc"
STRINGTABLE
{
KDERR_CASESTATEMENT, "Command fell through
case statement"
}
</FONT></PRE>
<P><A NAME="Heading17"></A><FONT COLOR="#000077"><B>Listing 14.8. The include file
for the project has only one entry. This is a stub to be filled out later.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">#define
KDERR_CASESTATEMENT 1
</FONT></PRE>
<P>The pages in the <TT>TPageControl</TT> are hidden from view in Figure 14.1. In
Figure 14.3 through Figure 14.6, you can see the remaining <TT>TTabSheet</TT> objects.<BR>
<BR>
<A NAME="Heading18"></A><A
HREF="14ebu03.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu03.jpg">FIGURE 14.3.</A><FONT COLOR="#000077">
</FONT><I>The tab sheet for the Address table.<BR>
<BR>
<A NAME="Heading19"></A></I><A HREF="14ebu04.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu04.jpg">FIGURE 14.4.</A><FONT COLOR="#000077">
</FONT><I>The tab sheet for the
Phone table.<BR>
<BR>
<A NAME="Heading20"></A></I><A HREF="14ebu05.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu05.jpg">FIGURE 14.5.</A><FONT COLOR="#000077">
</FONT><I>The tab sheet for the E-mail table.</I>
<H4 ALIGN="CENTER"></H4>
<H4><A NAME="Heading21"></A><FONT COLOR="#000077">Using
the kdAdd Program</FONT></H4>
<P>The kdAdd program has the minimal functionality needed to support the user's needs.
For example, you can perform Insert, Post, Delete, and Cancel operations on all the
tables. Access to these features is provided
through both the menus and a speedbar.
You can also set the index to the <TT>Company</TT>, <TT>First</TT>, or <TT>Last</TT>
fields of the <TT>kdNames</TT> table. Finally, you can search on either the <TT>Company</TT>,
<TT>First</TT>, or <TT>Last</TT>
fields.<BR>
<BR>
<A NAME="Heading22"></A><A HREF="14ebu06.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu06.jpg">FIGURE 14.6.</A><FONT COLOR="#000077">
</FONT><I>The tab sheet for Memo.</I>
<H4 ALIGN="CENTER"></H4>
<H4><A NAME="Heading23"></A><FONT COLOR="#000077">Setting Up the Index for
kdAdd</FONT></H4>
<P>One of the hubs around which the kdAdd program revolves involves the code that
controls the index for the program. This code is called from several different places
in the program. The obvious place to start studying it, however,
is in the response
method for the menu items that let the user change the index:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::SetupIndex(TObject *Sender)
{
switch (dynamic_cast<TComponent *>(Sender)->Tag)
{
case 100:
DMod->NamesTable->IndexName = "idxLastName";
break;
case 101:
DMod->NamesTable->IndexName = "idxFirstName";
break;
case 102:
DMod->NamesTable->IndexName =
"idxCompany";
break;
case 103:
DMod->NamesTable->IndexName = "";
break;
}
}
void __fastcall TForm1::IndexClick(TObject *Sender)
{
SetupIndex(Sender);
DMod->NamesTable->FindNearest(OPENARRAY(TVarRec, ("AAAA")));
}
</FONT></PRE>
<P>The code has three menu choices for changing the index. The first lets the user
set the index to the last name; the second, to the first name; and the
third, to
the company name. All three menu items are attached to the <TT>IndexClick</TT> method
shown here.</P>
<P><TT>IndexClick</TT> calls <TT>SetupIndex</TT> to do the real work. You use the
tag property of the <TT>TMenuItem</TT> that is clicked to
decide which index to choose:</P>
<PRE><FONT COLOR="#0066FF">switch (dynamic_cast<TComponent *>(Sender)->Tag)
</FONT></PRE>
<P>This way, you can call the function with a simple one-line command:</P>
<PRE><FONT
COLOR="#0066FF">SetupIndex(Sender);
</FONT></PRE>
<P>After the index has been set up properly, you can search for the first relevant
record in the database:</P>
<PRE><FONT COLOR="#0066FF">DMod->NamesTable->FindNearest(OPENARRAY(TVarRec,
("AAAA")));
</FONT></PRE>
<P>The goal of this line is to skip over all the records that contain blanks in the
field on which you're searching. For example, if you switch to the company index,
you might find 20, 100, or even 5,000 records in
the table that have no information
in the <TT>Company</TT> field. To skip over these records, you can search for the
first row that begins with the letter A.
<H4><A NAME="Heading24"></A><FONT COLOR="#000077">Searching for Records</FONT></H4>
<P>The
program also uses the <TT>SetupIndex</TT> method when it is conducting searches.
As I stated previously, you can use three possible menu items to start a search.
The first searches on last names; the second, on first names; and the third, on a
company
name. I have assigned the same values to the <TT>Tag</TT> fields of these
<TT>TMenuItems</TT> that I did to the <TT>Tag</TT> fields of the <TT>TMenuItems</TT>
concerned with switching indexes. That way, I can set up the index properly with
a simple
call to <TT>SetupIndex</TT>:</P>
<PRE><FONT COLOR="#0066FF"> AnsiString IndexName, S;
if (InputQuery("Search for Name", "Enter Name: ", S))
{
IndexName = DMod->NamesTable->IndexName;
SetupIndex(Sender);
DMod->NamesTable->FindNearest(OPENARRAY(TVarRec, (S)));
DMod->NamesTable->IndexName = IndexName;
}
</FONT></PRE>
<P>As you can see, the code also saves the current index so that the current state
of the index can be restored after
the search:</P>
<PRE><FONT COLOR="#0066FF"> AnsiString IndexName;
IndexName = DMod->NamesTable->IndexName;
... // Code omitted here
DMod->NamesTable->IndexName = IndexName;
</FONT></PRE>
<P>The big point to notice here is how
easily you can take care of these chores by
using the VCL. BCB makes database programming easy, even when you're working with
a fairly complex program.
<H4><A NAME="Heading25"></A><FONT COLOR="#000077">Inserting Data, Canceling Operations</FONT></H4>
<P>Because this database has five tables, you have to devise a technique for specifying
the name of the table on which you want to perform an insertion, deletion, or post.
I use the <TT>TPageControl</TT> to handle these chores. In particular, I assume
that
if the user is looking at the Address page, then he or she wants to perform an action
on the <TT>kdAdds</TT> table, and if the user is looking at the first page, then
he or she wants to perform an operation on the <TT>kdNames</TT> table, and so
on:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Insert1Click(TObject *Sender)
{
switch (dynamic_cast<TComponent&>(*PageControl1->ActivePage).Tag)
{
case 1:
DMod->NamesTable->Insert();
break;
case 2:
DMod->AddressTable->Insert();
break;
case 3:
DMod->PhoneTable->Insert();
break;
case 4:
DMod->EMailTable->Insert();
break;
}
}
</FONT></PRE>
<P>As you can see, I have
set the <TT>Tag</TT> field for each of the pages to a unique
value so that I can easily determine the current page:</P>
<PRE><FONT COLOR="#0066FF">switch (dynamic_cast<TComponent&>(*PageControl1->ActivePage).Tag)
</FONT></PRE>
<P>If the
user accidentally makes a wrong decision, he or she can undo the most recent
operation on the currently selected table by clicking Cancel:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Cancel1Click(TObject *Sender)
{
AnsiString S;
switch (dynamic_cast<TComponent&>(*PageControl1->ActivePage).Tag)
{
case 1: DMod->NamesTable->Cancel(); break;
case 2: DMod->AddressTable->Cancel(); break;
case 3: DMod->PhoneTable->Cancel(); break;
case 4: DMod->EMailTable->Cancel(); break;
default:
ShowMessage(GetError(1, S));
}
}
</FONT></PRE>
<P>This system is easy to implement, but it can be a bit confusing to the user when
he or she is looking at the first page, which
holds information about not only the
<TT>kdNames</TT> table, but also the <TT>kdAdds</TT> and <TT>kdPhone</TT> tables.
The issue here is that the database itself won't be much fun if you have to flip
pages to get even the most basic information about
a name. To remedy this problem,
I put the Name, Address, and Phone information on the first page but really expect
the user to perform edits on these fields by turning to the Address or Phone page.</P>
<P>To enforce this rule, you can set up an
options menu that can be turned on and
off:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::SetAddressPhoneClick(TObject *Sender)
{
SetAddressPhone->Checked = !SetAddressPhone->Checked;
DBEdit6->ReadOnly =
SetAddressPhone->Checked;
DBEdit17->ReadOnly = SetAddressPhone->Checked;
DBEdit18->ReadOnly = SetAddressPhone->Checked;
DBEdit19->ReadOnly = SetAddressPhone->Checked;
DBEdit20->ReadOnly =
SetAddressPhone->Checked;
DBEdit8->ReadOnly = SetAddressPhone->Checked;
DBEdit17->ReadOnly = SetAddressPhone->Checked;
DBEdit7->ReadOnly = SetAddressPhone->Checked;
}
</FONT></PRE>
<P>If you include this code in the
program as a response to a <TT>TMenuItem</TT>
called <TT>SetAddressPhone</TT>, then the user can decide whether the views on the
<TT>kdAdds</TT> and <TT>kdPhones</TT> table will be editable. By default, this function
should be turned off, because it
really is better that the user does not input information
in these fields.</P>
<P>I should perhaps add that the user does not have to understand that he or she
is entering data in separate tables. For example, the user doesn't have to know that
the
<TT>kdAdds</TT> and <TT>kdPhones</TT> tables exist. All he or she has to understand
is that inputting information about phones is a separate operation from entering
data about addresses or names. This much conceptual background the user must have;
otherwise, he or she will not be able to use the tool at all.</P>
<P>The design of the program helps the user by putting each table on a separate page.
That way, the user can rely on the page metaphor when thinking about the underlying
structure of
the database. Providing metaphors for the user is a useful way to simplify
the operation of an application.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>As you might recall, when I first
talked
about the Address2 program, I said that for many users, a simple flat-file
database is best. When I said that, I was thinking particularly about the kinds of
problems currently under discussion. Using a relational database takes a certain
amount of
conceptual ability that some users may not have the patience to master.
The actual ideas involved are simple, but many users are still so overwhelmed by
the very idea of computers that they can't clear their heads sufficiently to grasp
concepts
that would be easy for them to assimilate in some other field with which
they are more familiar. <BR>
<BR>
It may sound as though I am being overly polite in this note, but I'm trying to state
the facts as I see them. Many intelligent people's
minds really do become inexplicably
opaque when it comes to thinking about computers. This problem will disappear as
more and more children grow up using these machines, but for now programmers have
to think seriously every time they add any level
of complexity to their programs.
Programmers will understand relational databases, and so will the small subset of
users that are targeted by a program of this type; but it is important to understand
that at this point in history, many users will
find relational databases perhaps
too difficult to understand. <BR>
<BR>
My point is simply that not everyone is ready to work with relational databases.
Some people need them, but others will be confused by them. It is good to use relational
databases when necessary, but programmers should also be aware that some users might
not properly understand them.
<HR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -