📄 ch09.htm
字号:
<BR>
You should recall that you can pass either a string or a number into a field when
using the <TT>Value</TT> property. The <TT>Value</TT> property
is of type <TT>Variant</TT>,
and so you can, to a considerable degree, ignore type issues in this situation. For
instance, there is no need to write code that looks like this:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall
TForm1::FInsertClick(TObject *Sender)
{
FCountry->Insert();
FCountry->FieldByName("Name")->AsString = "Erehwon";
FCountry->FieldByName("Capital")->AsString = "Nowhere";
FCountry->FieldByName("Continent")->AsString = "Imagination";
FCountry->FieldByName("Area")->AsInteger = 0;
FCountry->FieldByName("Population")->AsInteger = 0;
FCountry->Post();
}</FONT></PRE>
<P>Instead of calls to <TT>AsString</TT> and <TT>AsInteger</TT>, you can just use
<TT>Value</TT>.
<HR>
</BLOCKQUOTE>
<P>Looking at the code shown previously, you will see that the mere act of inserting
a record and of filling out
its fields is not enough to change the physical data
that resides on disk. If you want the information to be written to disk, you must
call <TT>Post</TT>.</P>
<P>After calling <TT>Insert</TT>, if you change your mind and decide to abandon the
current
record, you should call <TT>Cancel</TT>. As long as you do this before you
call <TT>Post</TT>, everything you have entered is discarded, and the dataset is
restored to the condition it was in before you called <TT>Insert</TT>.</P>
<P>One last related
property that you should keep in mind is called <TT>CanModify</TT>.
A table might be set to <TT>ReadOnly</TT>, in which case <TT>CanModify</TT> would
return <TT>False</TT>. Otherwise <TT>CanModify</TT> returns <TT>True</TT> and you
can enter edit or
insert mode at will. <TT>CanModify</TT> is itself a read-only property.
If you want to set a dataset to read-only, you should use the <TT>ReadOnly</TT> property,
not <TT>CanModify</TT>.</P>
<P>In this section, you have learned how to use the
<TT>Insert</TT>, <TT>Delete</TT>,
<TT>Edit</TT>, <TT>Post</TT>, <TT>Cancel</TT>, and <TT>Append</TT> commands. Most
of the actions associated with these commands are fairly intuitive, though it can
take a little thought to see how they interact with
the <TT>Fields</TT> property.
<H3><A NAME="Heading24"></A><FONT COLOR="#000077">Using SetKey or FindKey to Search
through a File</FONT></H3>
<P>If you want to search for a value in a dataset, you can call on five <TT>TDataSet</TT>
methods, called
<TT>FindKey</TT>, <TT>FindNearest</TT>, <TT>SetKey</TT>, <TT>GotoNearest</TT>,
and <TT>GotoKey</TT>. These routines assume that the field you are searching on is
indexed. You should note that the BCB includes two methods for searching for values
in a
table. The <TT>SetKey</TT>, <TT>GotoNearest</TT>, and <TT>GotoKey</TT> methods
comprise one technique, and <TT>FindKey</TT> and <TT>FindNearest</TT> comprise a
second technique. I discuss both techniques in the next few paragraphs.</P>
<P>This book's
CD-ROM contains a demonstration program called Search that shows how
to use these calls. You should open up this program and run it once to see how it
works, or else follow the steps shown here to create the program yourself.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>There is actually a third method
for searching for values in a table. I demonstrate that technique in the upcoming
section from this chapter called "Filtering with the
<TT>OnFilterRecord</TT>
Event." The technique in question uses a series of routines called <TT>FindFirst</TT>,
<TT>FindLast</TT>, <TT>FindNext</TT> and <TT>FindPrior</TT>. Unlike the routines
used in this section, <TT>FindFirst</TT> and the
like are not tied to the index fields
of your table.
<HR>
</BLOCKQUOTE>
<P>To create the Search program, place <TT>TTable</TT> and <TT>TDataSource</TT> objects
on a data module, and <TT>TDBGrid</TT>, <TT>TButton</TT>, <TT>TLabel</TT>, and
<TT>TEdit</TT>
controls on a form. Arrange the visual controls so the result looks like the image
shown in Figure 9.6. Be sure to set the caption of the button to Search, and then
to wire the database controls so you can view the <TT>Customer</TT>
table in the
grid control.<BR>
<BR>
<A NAME="Heading26"></A><A HREF="09ebu06.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/09/09ebu06.jpg">FIGURE 9.6.</A><FONT COLOR="#000077">
</FONT><I>The Search program enables you to enter a customer number and then search
for it by pressing a button.</I></P>
<P><BR>
One set of functionality for the Search program is encapsulated in a single method
that is attached to the Search button. This function retrieves the string entered
in the edit control, searches the <TT>CustNo</TT> column until it finds the
value,
and finally switches the focus to the record it found.</P>
<P>In this program, I observe more carefully the rules of object-oriented programming.
In particular, I create a data module, store the non-visual database tools on it,
and then create
methods for manipulating the data within the data module itself,
rather than inside the object for the main form. This has only theoretical benefits
in a simple program like Search. However, in a more complex program, this shows the
way to create a
single data module--with some built in rules and functionality--that
can be reused in multiple programs. It also shows how to create programs that are
easy to maintain, and easy to understand.</P>
<P>In particular, the main form contains a method
response routine for the Search
button that looks like this:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::bSearchClick(TObject *Sender)
{
DMod->Search(Edit1->Text);
}
</FONT></PRE>
<P>In its turn, the data module has a search
method that looks like this:</P>
<PRE><FONT COLOR="#0066FF">void TDMod::Search(AnsiString S)
{
tblCustomer->SetKey();
tblCustomer->Fields[0]->AsString = S;
tblCustomer->GotoKey();
}
</FONT></PRE>
<P>As you can see, the
<TT>TDMod</TT> object protects its data and exposes its functionality
to the outside world through a single easy-to-call method. This way, <TT>TDMod</TT>
could totally change the internal technique it has for searching through a table
without forcing
you to modify the <TT>TForm1</TT> object in any way.</P>
<P>The first call in this function sets <TT>Table1</TT> into search mode. BCB needs
to be told to switch into search mode simply because you use the <TT>Fields</TT>
property in a special way
when BCB is in search mode. Specifically, you can index
into the <TT>Fields</TT> property and assign it to the value you want to find.</P>
<P>In the example shown here, the <TT>CustNo</TT> field is the first column in the
database, so you set the
<TT>Fields</TT> index to zero. To actually carry out the
search, simply call <TT>Table->GotoKey()</TT>. <TT>GotoKey</TT> is a Boolean function,
so you could write code that looks like this:</P>
<PRE><FONT COLOR="#0066FF">if (!Table->GotoKey)
DatabaseError("Error searching for data!");
</FONT></PRE>
<P>The <TT>DatabaseError</TT> routine might raise a database exception of some kind.</P>
<P>If you are not sure of the value you are looking for, call <TT>Table->GotoNearest</TT>.
<TT>GotoNearest</TT> will take you to the numerical or string value closest to the
one you specify.</P>
<P>The <TT>FindKey</TT> and <TT>FindNearest</TT> routines perform the same function
<TT>GotoKey</TT> or <TT>GotoNearest</TT>, but they are much
easier to use. Here,
for instance, is the technique for using <TT>FindKey</TT>:</P>
<PRE><FONT COLOR="#0066FF">tblCustomer->FindKey(OPENARRAY(TVarRec, (S)));
</FONT></PRE>
<P>In this case, <TT>S</TT> is assumed to be a valid <TT>AnsiString</TT>
containing
a <TT>CustNo</TT> for which you want to search.</P>
<P>Here's how <TT>FindNearest</TT> looks:</P>
<PRE><FONT COLOR="#0066FF">tblCustomer->FindNearest(OPENARRAY(TVarRec, (S)));
</FONT></PRE>
<P>When using <TT>FindKey</TT> or
<TT>FindNearest</TT> there is no need to first
call <TT>SetKey</TT> or to use the <TT>FieldByName</TT> property. (Needless to say,
internally <TT>FindKey</TT> and <TT>FindNearest</TT> end up calling <TT>SetKey</TT>
and <TT>FieldByName</TT>, before
making calls to either <TT>GotoKey</TT> or <TT>GotoNearest</TT>.)</P>
<P><TT>FindKey</TT> and <TT>FindNearest</TT> take an array of values in their sole
parameter. You would pass multiple parameters to <TT>FindKey</TT> or <TT>FindNearest</TT>
if you
have a table that was indexed on multiple fields. In this case, the <TT>Customer</TT>
table has a primary index on one field, called <TT>CustNo</TT>. But if it had multiple
fields in the primary index, you could specify one or more of the values for
these
fields in this parameter:</P>
<PRE><FONT COLOR="#0066FF">tblCustomer->FindNearest(OPENARRAY(TVarRec, (S1, S2, S3)));
</FONT></PRE>
<P>This is one of those cases where Object Pascal provides a built in, easy-to-use
method for working with a
dynamically created array of values, while there is no
such native type in C++. C++, of course, is nothing if not flexible, and it is rare
that you cannot find a way to make the language conform to your desires. For instance,
in this case, one simple
way for C++ to accommodate the VCL's request for a dynamically
constructed array of values is by using the <TT>OPENARRAY</TT> macro and its associated
template class from <TT>Sysdefs.h</TT>.</P>
<P>If you are not searching on the primary index of a
file, you must use a secondary
index and specify the name of the index you are using in the <TT>IndexName</TT> property
for the current table. For instance, the <TT>Customer</TT> table has a secondary
index called <TT>ByCompany</TT> on the field
labeled <TT>Company</TT>. You would
have to set the <TT>IndexName</TT> property to the name of that index if you wanted
to search on that field. You could then use the following code when you searched
on the <TT>Company</TT> field:</P>
<PRE><FONT
COLOR="#0066FF">void TDMod::CompanySearch(AnsiString S)
{
tblCustomer->IndexName = "ByCompany";
tblCustomer->FindNearest(OPENARRAY(TVarRec, (S)));
}
</FONT></PRE>
<P>Remember: This search will fail unless you first assign the
correct value to the
<TT>IndexName</TT> property. Furthermore, you should note that <TT>IndexName</TT>
is a property of <TT>TTable</TT> and would therefore not automatically be included
in any direct descendant of <TT>TDataSet</TT> or
<TT>TDBDataSet</TT> that you might
create yourself. In particular, it's not part of <TT>TQuery</TT>. Indeed, none of
the functions discussed in this section are part of <TT>TQuery</TT>.</P>
<P>The previous code is flawed, or at least could be flawed
in some cases, in that
any attempt to search on the <TT>CustNo</TT> field after a call to <TT>CompanySearch</TT>
would fail, because the table would no longer be indexed on the <TT>CustNo</TT> field.
As a result, you might want to save the old index
in a temporary variable so it can
be restored at the end of the function:</P>
<PRE><FONT COLOR="#0066FF">void TDMod::CompanySearch(AnsiString S)
{
AnsiString Temp(tblCustomer->IndexName);
tblCustomer->IndexName = "ByCompany";
tblCustomer->FindNearest(OPENARRAY(TVarRec, (S)));
tblCustomer->IndexName = Temp;
}
</FONT></PRE>
<P>A neat trick you can use with the <TT>FindNearest</TT> method involves performing
an incremental search across a table. Start a new project
and get things rolling
quickly by simply dragging the <TT>Country</TT> table off the Database Explorer and
onto <TT>Form1</TT>. You will end up with a <TT>TTable</TT>, <TT>TDataSource</TT>,
and <TT>TDBGrid</TT> on the form, with the <TT>TTable</TT>
hooked up to the <TT>Country</TT>
table. Put a panel on the top of the form and set its <TT>Align</TT> property to
<TT>alTop</TT>. Set the <TT>Align</TT> property of the <TT>TDBGrid</TT> for the <TT>Country</TT>
table to <TT>alClient</TT>.</P>
<P>Place a <TT>TEdit</TT> component on the panel and create an <TT>OnChange</TT>
event with the following code attached to it:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Edit1Change(TObject *Sender)
{
tblCountry->FindNearest(OPENARRAY(TVarRec, (Edit1->Text)));
}
</FONT></PRE>
<P>Run the program. When you type into the edit control, you will automatically begin
incrementally searching through the table. For instance, if you type <TT>C</TT>,
you will go to the record for Canada, if you type <TT>Cu</TT>, you will go to the
record for Cuba.</P>
<P>The incremental search example is available on disk as the IncrementalSearch program.
It's perhaps worth pointing out that this program is
interesting in part because
it shows how you can use the built-in features of BCB to easily implement additional
features that were never planned by the developers. For instance, there is no Incremental
Search component in BCB. However, if you need to
build one, the tools come readily
to hand.
<H3><A NAME="Heading27"></A><FONT COLOR="#000077">Filtering the Records in a Dataset
with ApplyRange</FONT></H3>
<P>The next two sections cover two different ways to filter data. Of these two techniques,
the
second is preferable on several counts; so if you are in a hurry, you can skip
this section. The key difference between the two methods is that the one explained
in this section uses keyed fields, while the next technique will work on any type
of
field. The technique used in the next section is very highly optimized, but the
conservatives in the crowd might find the technique outlined in this section appealing
because it relies on indexes, which are a tried and true database technology,
guaranteed
to execute in a highly optimized manner.</P>
<P>The <TT>ApplyRange</TT> function lets you set a filter that limits the range of
the records you view. For instance, in the <TT>Customers</TT> database, the <TT>CustNo</TT>
field ranges from
just over 1,000 to a little under 10,000. If you wanted to see
only those records that had a customer number between 2000 and 3000, you would use
the <TT>ApplyRange</TT> method from <TT>TTable</TT> and two related routines. When
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -