Using TTable and TDataSet - Part 02
tblCustomer->SetRange(OPENARRAY(TVarRec, (AStart)),
OPENARRAY(TVarRec, (AFinish))); } This TTable SetRange
method takes two OpenArray templates, the first to cover the
starting range, the second to cover the ending range. The idea here is that
the table might be indexed on multiple fields, and you can use an OpenArray to specify the values associated with more
than one field, if needed. Filtering with
the OnFilterRecord Event The OnFilterRecord event enables you to set up filters on
fields even if they are not keyed. You can use this event in two different
ways. The
first technique involves setting the TTable Filtered property to True. When you do this, you will see only the
records that are designated by the formula defined in the OnFilterRecord event. For instance, if you had a State field in your dataset and the OnFilterRecord event said to accept only records from The
second technique enables you to search for records even when Filtered is set to False. For instance, if you set up an OnFilterRecord event that accepted only records from An
example of the OnFilterRecord event is shown in the Filter
program found on this book's CD-ROM. The rest of this section describes how
to create that program from scratch. To see
the OnFilterRecord event in a live program, start by
dragging the Country table off the Database Explorer
onto a blank form or data module from a new project. (It sometimes helps to
close the Database Explorer after the drop operation, rather than trying to
switch between the two tools by changing their focus.) Drop down a panel and
set up the Align property for the panel and for
the TDBGrid as explained in the previous
examples from this chapter. Place a TCheckBox object on the panel and set its caption to Filtered. Associate the following method with the OnClick event for the checkbox: void __fastcall TForm1::FFilterClick(TObject
*Sender) {
FCountry->Filtered = FFilter->Checked; } This
code ensures that the table will be filtered whenever the checkbox is
checked. Use the
Fields Editor for the Table1 object to create field objects
for all the fields in the database. Drag the Continent field off the Fields Editor onto the form, as shown in Figure 9.8. FIGURE
9.8. The main form for the Filter
program includes a grid, a panel, a checkbox, a TDBEdit control, and a button. Turn to
the Events page for the TTable object, and associate the
following code with the OnFilterRecord event: void __fastcall
TForm1::FCountryFilterRecord(TDataSet *DataSet, Boolean &Accept) { Accept =
tblCountry->FieldByName("Continent")->AsString == " } This
code states that the OnFilterRecord event will accept all records
where the Continent field of the Country table contains the word " If you
wanted, you could change the code so that it always filtered on the value
currently in the Continent field: void __fastcall
TForm1::FCountryFilterRecord(TDataSet *DataSet, Boolean &Accept) { Accept =
tblCountry->FieldByName("Continent")->AsString ==
DBEdit1->Text; } For this
code to work, you must drop down a DBEdit
control, and hook it up to the Continent field of the Country table. It's
important to note that the Accept field of the OnFilterRecord event is a Boolean value. This means that
you can set up any kind of a Boolean statement in order to set the value of
this field. For instance, in addition to the = operator, you could also use the following operators: <>, >, or <`. The FindNext, FindFirst, and
FindPrior functions are extremely easy to
use. For instance, if you wanted to find the next record in the database that
satisfied the requirements specified in the OnFilterRecord event, you could write the following code to
be fired in response to clicking a button: void __fastcall TForm1::FFindNextClick(TObject
*Sender) {
tblCountry->FindNext(); } The
other functions work exactly the same way. This is a Boolean function that
will return False if the search fails. Remember
that these methods work even if the Filtered
property of the TTable object is set to False. Using the
Refresh Function As you
already know, any table that you open is always subject to change. In short,
you should regard a table as a fluid, rather than as a static, entity. Even
if you are the only person using a particular table and even if you are not
working in a networked environment, there is always the possibility that the
program you are running may have two different ways of changing a piece of
data. As a result, you should always be aware of the need to update, or
refresh, your current view of a table. Furthermore, BCB will not always
update data after you perform an action in the background. As a result, you
may need to update data by performing a refresh, particularly after certain
kinds of delete operations. The Refresh function is related to the Open function because it retrieves the data, or
some portion of the data, associated with a given table. For instance, when
you open a table, BCB retrieves data directly from a database file.
Similarly, when you refresh a table, BCB goes out and retrieves data directly
from a table. You can therefore use this function to update a table if you
think it might have changed. It is faster, and much more efficient, to call Refresh than to call Close and then Open. NOTE: In a networked environment,
refreshing a table can sometimes lead to unexpected results. For instance, if
a user is viewing a record that has been deleted, it will seem to disappear
out from under the user the moment the program calls Refresh. Similarly, if another user has edited data, a call to Refresh can result in data dynamically changing
while a user is viewing it. Of course, it is unlikely that one user will
change or delete a record while another is viewing it, but it is possible. As
a result, you should use calls to Refresh with
caution. Bookmarks It is
often useful to mark a particular location in a table so that you can quickly
return to it when desired. BCB provides this functionality through three
methods of TDataSet that use the metaphor of a
bookmark. When you use these functions, it is as if you have left a bookmark
in the dataset, and you can therefore turn back to it quickly whenever you
want: void __fastcall FreeBookmark(System::Pointer
Bookmark); System::Pointer __fastcall GetBookmark(void); void __fastcall GotoBookmark(System::Pointer
Bookmark); As you
can see, the GetBookmark call returns a variable of type
pointer, which is in fact just a pointer to a Bookmark. A Bookmark pointer contains enough
information to enable BCB to find the location to which it refers. Therefore,
you can simply pass this bookmark to the GotoBookmark function, and you will immediately be
returned to the location with which the bookmark is associated. It's
important to note that a call to GetBookmark
allocates memory for the bookmark, and so you must remember to call FreeBookmark before you exit your program and before
every attempt to reuse a bookmark. For instance, here is a typical set of
calls for freeing a bookmark, setting a bookmark, and moving to a bookmark: void __fastcall TForm1::bMarkClick(TObject *Sender) { if (Bookmark
== NULL) Bookmark =
DataMod->tblCountry->GetBookmark(); } void __fastcall TForm1::bReturnClick(TObject
*Sender) { if (Bookmark
!= NULL) {
DataMod->tblCountry->GotoBookmark(Bookmark);
DataMod->tblCountry->FreeBookmark(Bookmark); Bookmark =
NULL; } } void __fastcall TForm1::FormDestroy(TObject *Sender)
{ DataMod->tblCountry->FreeBookmark(Bookmark);
} The code
shown here is excerpted from a program called Bookmark, which comes with the CD-ROM that accompanies this book. In the
declaration for TForm1, a variable called Bookmark is declared in the private section. Every
time the MarkClick function is called, the first
step is to be sure the Bookmark is freed. It is never a mistake
to call FreeBookmark, because the function checks to
make sure Bookmark is not set to NULL. After de-allocating any existing copies of
the Bookmark, a new one is allocated. You can
then call GotoBookmark and repeat the cycle. Bookmarks
are powerful features that can be of great benefit under certain
circumstances. The developers of BCB, for instance, used bookmarks frequently
in order to develop the database components. They often have several
different bookmarks open at the same time. NOTE: Most of the features surfaced in TDataSet are built into the BDE. For instance,
filters, searching for keys, and bookmarks are available to anyone who uses
the BDE. What the developers of BCB have done is surface these features so
that they can be easily accessed using object-oriented programming
techniques. The calls from the BDE are available to people who purchase the
Borland Database Engine from Borland or to patient BCB spelunkers who spend
some time with BDE.hpp. More on TDataSet
and TTable TTable adds several frequently used
properties to TDataSet: __property System::Boolean ReadOnly; // From TTable __property System::Boolean Exclusive; __property System::AnsiString MasterFields; __property DB::TDataSource *MasterSource; __property System::AnsiString TableName; Of the
properties shown here, the most common ones are probably TableName and ReadOnly. You can use the TableName property to specify the table you
want to open, and you can set the ReadOnly
property to True or False depending on whether you want to allow the user to change the data in
a dataset. Neither of these properties can be used when a table is active. The Exclusive property lets you open up a table in a mode
that guarantees that no other user will be able to access it at the same
time. You will not be able to set Exclusive to True if another user is currently accessing the
table. The MasterSource property is used to specify a TDataSource from which the current table needs to obtain
information. For instance, if you linked two tables in a master/detail
relationship, the detail table can track the events occurring in the first
table by specifying the first table's data source in this property. This
technique is demonstrated in the following section on linked cursors. Creating
Linked Cursors Linked
cursors enable programmers to easily define a one-to-many relationship. For
instance, it is sometimes useful to link the CUSTOMER and ORDERS tables so that each time the user
views a particular customer's name, he or she can also see a list of the
orders related to that customer. In short, the user can view one customer's
record, and then see only the orders related to that customer. To
understand linked cursors, you first need to see that the CUSTOMER table and the ORDERS table are related to one another through the CustNo field. This relationship exists specifically because there needs to
be a way to find out which orders are associated with which customer. The
Links program on the book's CD-ROM demonstrates how to create a program that
uses linked cursors. To create the program on your own, place two tables, two
data sources, and two grids on a form. Wire the first set of controls to the CUSTOMER table and the second set to the ORDERS table. If you run the program at this stage,
you should be able to scroll through all the records in either table, as
shown in Figure 9.9. FIGURE
9.9. The Links program shows how to
define a relationship between two tables. The next
step is to link the ORDERS table to the CUSTOMER table so that you view only those orders
associated with the current customer record. To do this, you must take three
steps, each of which requires some explanation: 1. Set the MasterSource property of Table2 to DataSource1. If you
now run the program, you will see that both tables are linked together, and
that every time you move to a new record in the CUSTOMER table, you can see only those records in the ORDERS table that belong to that particular customer. The MasterSource property in Table2 specifies the DataSource from which Table2 can draw information. Specifically, it
allows the ORDERS table to know which record
currently has the focus in the CUSTOMER table. The
question then becomes this: what other information does Table2 need in order to properly filter the
contents of the ORDERS table? The answer to this question
is twofold: 1. It needs the name of the field that links
the two tables. In order
to correctly supply the information described here, you must first ensure
that both the CUSTOMER table and the ORDERS table have the correct indexes.
Specifically, you must ensure that there are indexes on both the CustNo field and the CustNo field in the ORDERS table. If the index in question
is a primary index, there is no need to specifically name that index, and
therefore you can leave the IndexName field blank in both tables.
However, if either of the tables is linked to the other through a secondary
index, you must explicitly designate that index in the IndexName field of the table that has a secondary
index. In the
example shown here, the CUSTOMER table has a primary index on the CustNo field, so there is no need to specify the
index name. However, the ORDERS table does not have a primary
index on the CustNo field, and so you must explicitly
declare it in the IndexName property by typing in or
selecting the word CustNo. NOTE: To simplify the process described
previously, the developers put in a dialog that appears when you click the MasterFields property. This dialog simplifies the process
and helps to automate the task of setting up a link between two tables. Some
indexes can contain multiple fields, so you must explicitly state the name of
the field you want to use to link the two tables. In this case, you should
enter the name CustNo in the MasterFields property of Table2. If you wanted to link two tables on more than one field, you should
list all the fields, placing a pipe symbol between each one: Table->MasterFields := "CustNo | SaleData |
ShipDate"; In this
particular case, however, I'm guilty of a rather shady bit of expediency. In
particular, the statement shown here makes no sense, because the SaleData and ShipDate fields are neither indexed nor duplicated in the CUSTOMER table. Therefore, you should only enter the
field called CustNo in the MasterFields property. You can specify this syntax
directly in a property editor, or else write code that performs the same
chore. It's
important to note that this section covered only one of several ways you can
create linked cursors using BCB. Chapter 10, "SQL and the TQuery Object," describes a second method that
will appeal to people who are familiar with SQL. The Database Expert provides
a third means of achieving this end. As you have seen, the Database Expert is
an easy-to-use visual tool. The Query Builder is yet a fourth way of creating
a one-to-many relationship between two tables. Like the Database Expert, the
Query Builder is a visual tool that can save you much time. However, it's
best not to rely entirely on visual tools, because there are times when you
might feel limited by their functionality. In such cases, you will be glad if
you understand the underlying technology. That way you can get the job done
yourself without being forced to wrestle with the limitations of a particular
tool. TDataSource
Basics Class TDataSource is used as a conduit between TTable or TQuery and the
data-aware controls such as TDBGrid, TDBEdit, and TDBComboBox. Under most circumstances, the
only thing you will do with a TDataSource object is to set its DataSet property to an appropriate TTable or TQuery object.
Then, on the other end, you will also want to set a data-aware control's DataSource property to the TDataSource object you are currently using. NOTE: Visual tools such as TDBEdit or TDBGrid all
have a DataSource property that connects to a TDataSource object. When reading this chapter, you need
to distinguish between a visual control's DataSource property and the TDataSource object to which it is attached.
In other words, the word DataSource can be a bit confusing. I try to
refer to a TDataSource object as a data source, and to
refer to the DataSource property as a DataSource. You should, however watch the context in
which these words are used, and be aware of the rather subtle distinctions in
meaning surrounding this issue. A TDataSource also has an Enabled property, and this can be useful whenever you want to temporarily
disconnect a table or query from its visual controls. This functionality
might be desirable if you need to programmatically iterate through all the
records in a table. For instance, if a TTable is connected to a data-aware control, each time you call TTable.Next, the visual control needs to be updated. If
you are quickly going through two or three thousand records, it can take
considerable time to perform the updates to the visual controls. In cases
like this, the best thing to do is set the TDataSource object's Enabled field to False, which will enable you to iterate through the records without having
to worry about screen updates. This single change can improve the speed of
some routines by several thousand percent. The AutoEdit property of TDataSource enables you to decide whether or not the data-aware controls attached
to it will automatically enter edit mode when you start typing inside them.
Many users prefer to keep AutoEdit set to True, but if you want to give a user more precise control over when the
database can be edited, this is the property you need. In short, if you set AutoEdit to False, you have
essentially made the table read-only. Using TDataSource to Check the
State of a Database The
events belonging to TDataSource can be extremely useful. To help
illustrate them, you will find a program on the CD-ROM that comes with this
book called Dsevents that responds to all three TDataSource events. This program shows an easy way to set up a "poor
man's" data-aware edit control that automatically shows and posts data
to and from a database at the appropriate time. This
example works with the COUNTRY database and it has a TTable, TDataSource,
five edits, six labels, eight buttons, and a panel on it. The actual layout
for the program is shown in Figure 9.10. Note that the sixth label appears on
the panel located at the bottom of the main form. FIGURE
9.10. The
Dsevents program shows how to track the current state of a table. TDataSource has three key events associated
with it: OnDataChange OnStateChange OnUpdateData A TDataSource OnStateChange
event occurs whenever the current state of the dataset changes. A dataset
always knows what state it's in. If you call Edit, Append, or Insert, the table knows that it is now in edit mode, and that fact is
reflected in the value held in the The
dataset has nine possible states, each of which are captured in the following
enumerated type: enum TDataSetState { dsInactive, dsBrowse, dsEdit,
dsInsert,
dsSetKey, dsCalcFields, dsUpdateNew,
dsUpdateOld, dsFilter }; During
the course of a normal session, the database will frequently move back and
forth between browse, edit, insert, or the other modes. If you want to track
these changes, you can respond to them by writing code that looks like this: void __fastcall TForm1::dsCountryStateChange(TObject
*Sender) { AnsiString
S; switch
(tblCountry->State) { case
dsInactive: S =
"Inactive"; break; case
dsBrowse: S =
"Browse"; break; case
dsEdit: S =
"Edit"; break; case
dsInsert: S =
"Insert"; break; case
dsSetKey: S =
"SetKey"; break; }
StatusBar1->Panels->Items[0]->Text = S; } In this
code, I am using a TStatusBar object to report the state of the
dataset to the user. To use a TStatusBar object, first drop one on a form
and then double-click on the editor for the TStatusBar Panels property to bring up the Status
Bar Panel Editor. Create a new panel in the Status Bar Panel Editor and set
its text to Browse, or to some other string. You now
have one panel the text of which you can change with the following line of code:
StatusBar1->Panels->Items[0]->Text = S; It's
time now to stop talking about the OnStateChange
event and to move on to the OnDataChange event. OnDataChange occurs whenever you move on to a new record.
In other words, if you call Next, Previous, Insert, or any other call that is likely
to lead to a change in the data associated with the current record, an OnDataChange event will get fired. If someone begins
editing the data in a data-aware control, an OnResync event occurs. Dsevents
has one small conceit that you need to understand if you want to learn how
the program works. Because there are five separate edit controls on the main
form, you need to have some way to refer to them quickly and easily. One
simple method is to declare an array of edit controls: TEdit *Edits[5]; To fill
out the array, you can respond to the forms OnCreate event: void __fastcall TForm1::FormCreate(TObject *Sender) { int i; for (i = 0;
i <= 4; i++) Edits[i] =
(TEdit*)FindComponent("Edit" + IntToStr(i + 1));
tblCountry->First(); } The code
shown here assumes that the first edit control you want to use is called Edit1, the second is called Edit2, and so on. Given
the existence of this array of controls, it is very simple to use the OnDataChange event to keep them in sync with the contents
of the current record in a dataset: void __fastcall TForm1::dsCountryDataChange(TObject
*Sender, TField *Field) { int i; if
(FUpdating) return; for (i = 0;
i <= 4; i++) Edits[i]->Text
= tblCountry->Fields[i]->AsString; } This
code iterates through each of the fields of the current record and puts its
contents in the appropriate edit control. Whenever Table->Next is called, or whenever any of the other
navigational methods are called, the function shown previously gets a chance
to strut onto the stage. Its primary raison d'etre is to ensure that the edit
controls always contain the data from the current record. Whenever
Post gets called, you will want to
perform the opposite action. That is, you will want to snag the information
from the edit controls and tuck them away inside the current record. To
execute this chore, simply respond to TDataSource.OnUpdateData events, which are generated automatically
whenever Post is called: void __fastcall TForm1::dsCountryUpdateData(TObject
*Sender) { int i; for (i = 0;
i <= 4; i++)
tblCountry->Fields[i]->Value = Edits[i]->Text; } The
Dsevents program switches into edit mode whenever you type anything in one of
the edit controls. It manages this sleight of hand by responding to OnKeyDown events: void __fastcall TForm1::Edit1KeyDown(TObject
*Sender, Word &Key,
TShiftState Shift) { if (dsCountry->State
!= dsEdit)
tblCountry->Edit(); } This
code demonstrates how to use the State variable
of a TDataSource object to find out the current
mode of the dataset. Tracking the
State of a Dataset In the
last section, you learned how to use TDataSource to keep tabs on the current state of a TDataSet and to respond just before certain events are about to take place. Using a TDataSource object is the simplest way to perform all
these functions. However, if you would like to track these events without
using TDataSource, you can respond to the following
rather intimidating list of events from TDataSet, all of which are naturally inherited by TTable or TQuery: __property TDataSetNotifyEvent BeforeOpen; __property TDataSetNotifyEvent AfterOpen; __property TDataSetNotifyEvent BeforeClose; __property TDataSetNotifyEvent AfterClose; __property TDataSetNotifyEvent BeforeInsert; __property TDataSetNotifyEvent AfterInsert; __property TDataSetNotifyEvent BeforeEdit; __property TDataSetNotifyEvent AfterEdit; __property TDataSetNotifyEvent BeforePost; __property TDataSetNotifyEvent AfterPost; __property TDataSetNotifyEvent BeforeCancel; __property TDataSetNotifyEvent AfterCancel; __property TDataSetNotifyEvent BeforeDelete; __property TDataSetNotifyEvent AfterDelete; __property TDataSetNotifyEvent OnNewRecord; __property TDataSetNotifyEvent OnCalcFields; __property TFilterRecordEvent OnFilterRecord; __property TOnServerYieldEvent OnServerYield; __property TUpdateErrorEvent OnUpdateError; __property TUpdateRecordEvent OnUpdateRecord; __property TDataSetErrorEvent OnEditError; __property TDataSetErrorEvent OnPostError; __property TDataSetErrorEvent OnDeleteError; Most of
these properties are self-explanatory. The BeforePost event, for instance, is functionally similar to the TDataSource->OnUpdateData event that is explained and
demonstrated previously. In other words, the Dsevents program would work the
same if you responded to DataSource1->OnUpdateData or to Table->BeforePost. Of course, in one case you would not need
to have a TDataSource on the form, while the other
requires it. All of
these events are associated with methods that have a particular signature.
For instance, most of them are of type TDataSetNotifyEvent. A TDataSetNotifyEvent is declared like this: typedef void _C!astcall (__closure
*TDataSetNotifyEvent)(TDataSet *DataSet); Or, to
make the matter more comprehensible, here is the code BCB generates for an AfterClose event: void __fastcall
TForm1::tblCountryAfterClose(TDataSet *DataSet) { } To work
with these properties, you should see the EventOrd program found on the
CD-ROM that accompanies this book, and shown below in Figure 9.11. This
program is better experienced in action, rather than explained in words here
on the page. The main point of the program is to notify you when any major
event associated with a table occurs. If you play with the program for some
time, you will begin to get a good feeling for the events associated with a
table, and for the order in which they occur. NOTE: When I first wrote this program, I
learned a number of things about VCL databases that had not been clear to me
from prior experience. There is nothing like seeing the events being fired to
start to get a sense of how the VCL really works! So I would strongly
recommend spending some time with this program.
void __fastcall
TDataMod::tblCountryNewRecord(TDataSet *DataSet) {
HandleDataEvent("New Record"); } The HandleDataEvent pops the string into the list box
and makes sure that the most recently added items in the list box are always
visible: void __fastcall TDataMod::HandleDataEvent(char * S) {
Form1->ListBox1->Items->Add(S);
Form1->ListBox1->ItemIndex =
Form1->ListBox1->Items->Count - 1; } The main
form for the application has a simple method that will write the contents of
the list box to disk: void __fastcall
TForm1::WriteListBoxDataToDisk1Click(TObject *Sender) { if
(OpenDialog1->Execute())
ListBox1->Items->SaveToFile(OpenDialog1->FileName); } Here,
for instance, is a short run from the program: Before Open Filter Record dsCountry: State Change dsCountry: Data Change After Open Filter Record dsCountry: Data Change Navigator click: Next Filter Record dsCountry: Data Change Navigator click: Next Before Insert dsCountry: State Change New Record dsCountry: Data Change After Insert Navigator click: Insert Before Cancel dsCountry: State Change Filter Record dsCountry: Data Change After Cancel Navigator click: Cancel The
preceding list records what happens when
Unfortunately,
the TDBNavigator OnClick method occurs after the changes are recorded in the dataset. For
instance, the last six events in the record look like this: Before Cancel dsCountry: State Change Filter Record dsCountry: Data Change After Cancel Navigator click: Cancel They
actually occurred in the following order: Navigator click: Cancel Before Cancel dsCountry: State Change Filter Record dsCountry: Data Change After Cancel The
point is that I clicked the navigator Cancel button, and then the other
events occurred, in the order shown. However, BCB gives priority to the
database events, and then reports the OnClick event. If you
do a lot of database programming, you really need to know the order in which
certain events occur. Outlining all that information in these pages is not
likely to be particularly helpful to most readers. To learn about this
information, it's best just to watch the events occur and to trace their
history. So fire up the OrdEvents program and see what you can learn from it.
Working with
TDatabase While
you are learning the basics of connecting to a table, you should also look at
the TDatabase object, which exists primarily to
give you a means of staying connected to a database even if you are
continually opening and closing a series of tables. If you use TDatabase, you can be connected to Oracle, InterBase,
or other servers without ever opening any tables. You can then begin opening
and closing tables over and over without having to incur the overhead of
connecting to the database each time you call Open. The TDatabase object also enables you to start
server-based applications without specifying a password, and it gives you
access to transactions. Besides the information in this chapter, more
information on TDataBase appears in the discussion of the
Transacts program, which is covered at the end of Chapter 15, "Working
with the Local InterBase Server." To use
the TDatabase object, first drop it on to a
form or data module. Set the AliasName property to DBDEMOS. Create your own DatabaseName, such as MyDBDemos. Set Connected to True. Drop down a TTable object and set its DatabaseName property to MyDBDemos. The issue here is that you created a "new database" when
you dropped the TDatabase object on the form and filled in
its fields as shown previously. Therefore you can select the name of this
database from the drop-down list for the TTable DatabaseName property. After
connecting the table to the TDatabase object, you can work with the TTable object exactly as you would under normal
circumstances. For instance, you could connect it to the BioLife table, as shown in the DatabaseObject program
found on the CD-ROM that accompanies this book. If you
double-click a TDatabase object, you bring up the
component editor for this object, shown in Figure 9.12. Inside the component
editor you can discover the default values for many of the database parameters
passed in the BDE alias for this database.
Connecting to
a Database without Visual Tools Everything
that you do in BCB with the visual tools can also be done in code. The visual
tools are just an easy way to write code; they do not do anything special
that you can't also do in code. In fact, the code came first, and then the
visual tools were added to make it easier for you to create programs. The key
to BCB is the way its language conforms to the metaphors of visual
programming without causing you any undo overhead. NOTE: It would not be true, of course,
to say that BCB has no overhead as a result of its concessions to the world
of visual programming. For instance, all objects that reside on the Component
Palette inherit at least some code that makes them able to be manipulated
visually. In other words, they descend not directly from TObject, but from TComponent. If you
want to write code that is completely independent of the visual tools, you
have to add some include directives to the header file for
your form. For instance, to use a TTable object,
you should add the following code to your header file: #include <vcl\DBTables.hpp> #include <vcl\DB.hpp> Here is
an example of how to connect to a TTable object
without using any visual tools: void __fastcall TForm1::Button1Click(TObject
*Sender) { TTable
*MyTable = new TTable(this);
MyTable->DatabaseName = "DBDEMOS";
MyTable->TableName = "COUNTRY";
MyTable->Open(); ShowMessage(MyTable->Fields[0]->AsString);
MyTable->Free(); } Of
course, this code would not work if I did not first drop down a TButton object and create an OnClick event for it with the visual tools. But I am
not using any visual tools when working with the database. In this
example, the first line of code creates an instance of a TTable object. This is the equivalent in code of
dropping a TTable object onto a form from the
Component Palette. Notice that you pass in this to the object's constructor in order to give the object an owner. In
particular, Form1 is being assigned as the owner of
the object, and is therefore responsible for freeing the object at shutdown
time. If you passed in NULL in this parameter, you would have
to call Free or Delete in the last line of code. I chose to do this anyway, in large part
because I am declaring TTable as a local variable that lives on
the stack created for this one function. It therefore makes sense to destroy
the object at the end of the function, though the VCL would in fact still
clean up the memory for me when the form is destroyed. However, if I declared
MyTable as a field of Form1, it would make sense just to let Form1 call Free on the object at program shutdown, which is what I do in the other
examples included in this section. The next
three lines of code do what you would normally do with the Object Inspector.
That is, you first fill in the DatabaseName and TableName properties, and then you call Open, which is the equivalent of setting Active to True. After
opening the table, you are free to access its code in any way you want. You
could, for instance, iterate through the records in a table, as explained
later in this chapter, or you could even connect the table to a data source,
and the data source to a TDBGrid or TDBEdit control. An
example of this type of program is available on disk as a program called TABLESWITHOUTVISUALTOOLS.MAK, found in the NOVISUALTOOLS directory. The code for the program is shown
in Listings 9.1 and 9.2. //--------------------------------------------------------------------------
#include <vcl\vcl.h> #pragma hdrstop #include "Main.h" //--------------------------------------------------------------------------
#pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) :
TForm(Owner) { } //--------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject
*Sender) { TTable
*MyTable = new TTable(this);
MyTable->DatabaseName = "DBDEMOS";
MyTable->TableName = "COUNTRY"; MyTable->Open();
ShowMessage(MyTable->Fields[0]->AsString);
MyTable->Free(); } Listing 9.2. The header file for the TablesWithoutVisualTools program. //--------------------------------------------------------------------------
#ifndef MainH #define MainH //--------------------------------------------------------------------------
#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\DBTables.hpp> #include <vcl\DB.hpp> //--------------------------------------------------------------------------
class TForm1 : public TForm { __published:
// IDE-managed Components TButton
*Button1; void
__fastcall Button1Click(TObject *Sender); private:
// User declarations public:
// User declarations virtual
__fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------
extern TForm1 *Form1; //--------------------------------------------------------------------------
#endif If you
also wanted to connect to TDatabase object without using any visual
tools, you can do so as follows: void __fastcall TForm1::bOpenTableClick(TObject
*Sender) { FMyDatabase
= new TDatabase(this);
FMyDatabase->AliasName = "DBDEMOS";
FMyDatabase->DatabaseName = "MyDBDemos";
FMyDatabase->Connected = True; FMyTable =
new TTable(this);
FMyTable->DatabaseName = FMyDatabase->DatabaseName; FMyTable->TableName
= "COUNTRY";
FMyTable->Open();
DataSource1->DataSet = FMyTable; } For this
code to work, you need to do several things. In particular, you would need to
add DB.HPP and DBTABLES.HPP to your header file for your main form, and
you would need to edit the declaration for your TForm1 class so that it looked like this: class TForm1 : public TForm { __published: TButton
*bOpenTable; TDBGrid
*DBGrid1; TDataSource
*DataSource1; void
__fastcall bOpenTableClick(TObject *Sender); private: // User
declarations TDatabase
*FMyDatabase; // Add field
for database TTable
*FMyTable; // Add
field for table public: // User declarations
virtual
__fastcall TForm1(TComponent* Owner); }; Notice
the addition of the TDatabase and TTable data items. These are added so that the objects are available for use
in the bOpenTableClick function. I have
also added TDBGrid and TDataSource to the project and hooked up my database to them. I do this in order
to show that there is absolutely no difference between creating a TTable and TDatabase object in code and doing it with the visual tools. As you can see,
you can hook up the visual tools to the code based objects with just a single
line of code: DataSource1->DataSet = FMyTable; In
particular, this line of code defines exactly what happens logically when you
connect a data source to a table using the visual tools. In this
next example, absolutely everything is done from scratch. No visual database
objects are used, and even the Alias is created on the fly. All the
visual database controls are created on the fly, assigned locations on the
form, attached to the database, and made visible: void __fastcall TForm1::bCreateClick(TObject
*Sender) { FMyDatabase
= new TDatabase(this);
FMyDatabase->DatabaseName = "AnyName";
FMyDatabase->DriverName = "STANDARD";
FMyDatabase->Params->Add("path=g:\\cs\\examples\\data");
FMyDatabase->Connected = True; FMyTable =
new TTable(this);
FMyTable->DatabaseName = FMyDatabase->DatabaseName;
FMyTable->TableName = "BioLife";
FMyTable->Open();
FMyDataSource = new TDataSource(this);
FMyDataSource->DataSet = FMyTable; FMyEdit =
new TDBEdit(this);
FMyEdit->DataSource = FMyDataSource;
FMyEdit->Parent = this;
FMyEdit->Visible = True;
FMyEdit->DataField = "Common_Name"; FMyImage =
new TDBImage(this);
FMyImage->Parent = this;
FMyImage->BoundsRect = Bounds(0, 100, 300, 150);
FMyImage->DataSource = FMyDataSource;
FMyImage->DataField = "Graphic";
FMyImage->Stretch = True;
FMyImage->Visible = True; FMyNavigator
= new TDBNavigator(this);
FMyNavigator->Parent = this;
FMyNavigator->BoundsRect = Bounds(0, 50, 100, 25);
FMyNavigator->VisibleButtons =
TButtonSet() << nbFirst << nbLast << nbNext <<
nbPrior;
FMyNavigator->DataSource = FMyDataSource;
FMyNavigator->Visible = True; } Needless
to say, this code would not work unless the database objects were declared in
the class declaration for TForm or in some other legal location: class TForm1 : public TForm { __published:
// IDE-managed Components TButton
*bCreate; void
__fastcall bCreateClick(TObject *Sender); private:
// User declarations TDatabase
*FMyDatabase; TTable
*FMyTable; TDataSource
*FMyDataSource; TDBEdit
*FMyEdit; TDBImage
*FMyImage; TDBNavigator
*FMyNavigator; public:
// User declarations virtual
__fastcall TForm1(TComponent* Owner); }; In this
case all variables declared in the private section are database objects used
in the bCreateClick method shown previously. The key
lines to focus on are the ones that define the Alias for the TDatabase object: FMyDatabase->DatabaseName = "AnyName"; FMyDatabase->DriverName = "STANDARD"; FMyDatabase->Params->Add("path=g:\\bcb\\examples\\data");
The
first thing to notice about this code is that it hard-codes the path to my
version of BCB into the application. You will, of course, probably have to
edit this path before running the application. You can
see that the database object is given a DatabaseName, but no AliasName is declared. Instead, a DriverName is
specified. This is an either/or proposition. You declare the AliasName, in which case the driver is specified
inside the Alias, or else you explicitly declare
the DriverName, which means that no externally
defined Alias will be used. Selecting
the STANDARD driver is another way of saying
you want to use a Paradox table. If you resort to the visual tools, you can
see that this is a drop-down list of items, so you can see what other drivers
are available on your system. As a rule, you will have available STANDARD drivers, dBASE drivers, and a list of SQL
Links drivers such as Oracle, Sybase, and so on. If no
externally defined Alias will be used, how does the TDatabase object perform its function? How does it
know which database you want to reference? How does it know the details about
the database such as server name, user name, and database name? The solution
to this quandary is supplied by the Params
property, which can be used to define all the data that normally appears in
an Alias. (This is the same property you
explored earlier in this chapter when you double-clicked a TDataBase object to bring up the TDatabase component editor.) In the
case of Paradox tables, the only information that you need to fill concerns
the path to the database. However, there are additional fields that will need
to be covered if you want to connect to a client/server database. The Params property is of type TStrings, which means you can treat it more or less as you would any TStringList object, or as you would the Items property for a TListBox. Notice
that when you create visual database controls, you need to specify both the
owner and the parent of the control: FMyEdit = new TDBEdit(this); FMyEdit->Parent = this; In this
case, both the owner and the parent are the main form. The Owner is responsible for deallocating the memory
associated with the object, and the Parent is a
field used by Windows so it will know where to draw the control. If you
are working in code rather than using the visual tools, you still have
complete control over the object. For instance, I can define how many buttons
will be visible on a TDBNavigator control: FMyNavigator->VisibleButtons =
TButtonSet() << nbFirst << nbLast << nbNext <<
nbPrior; In this
case, the VisibleButtons property is of type TButtonSet. In C++, sets are emulated through objects.
If you want to add a member to the set, you use the overloaded << operator. In this case I am turning on the
First, Last, Next, and Prior buttons, and leaving the other buttons
uninitialized. Before
closing this section, I should make one final point. In all these examples, I
am assuming that a TSession object has been created. This
will indeed happen automatically, so long as you have a TApplication object initialized in your application. The DatabaseAlias application and all standard BCB
applications initialize the TApplication object in WinMain: WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
Application->Initialize(); Application->CreateForm(__classid(TForm1),
&Form1);
Application->Run(); return 0; } If you
don't have this kind of code in your application (for instance, if you have
created a console application), you will have to explicitly create the TSession object yourself. Code for doing this is
shown later in the book in the chapter on ISAPI programming, "Extending
an Internet Server with ISAPI." In this
section you have learned something about creating database objects in code
rather than with the visual tools. One drawback to this section is that it
makes BCB database programming appear fairly difficult. The other side of the
coin, of course, is that you don't have to manipulate the database tools this
way. In fact, you will usually use the speedy visual tools to do in a few
seconds what is done previously in code. It is, however, important to know
that you can do all forms of database programming in code if you so desire. Summary In this
chapter, you have learned how to use the TDataSet, TField, TDBDataSet, TTable, TDatabase, and TDataSource classes. This material is very
much at the heart of the BCB database machinery, so you should be sure you
understand how it works. The key
points to remember are as follows:
In the
next chapter, you will learn about the TQuery object and SQL. SQL is especially useful when you want to access the
advanced capabilities associated with servers and server data.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++
Builder.
|