Flat-File, Real-World Databases
Overview This
chapter is the first of a "two-part series" on constructing
real-world databases. The goal is to move from the largely theoretical
information you got in the preceding chapter into a few examples of how to
make programs that someone could actually use for a practical purpose in the
real world. In
several sections of this chapter, I go into considerable depth about
design-related issues. One of the burdens of this chapter is not merely to
show how database code works, but to talk about how to create programs that
have some viable use in the real world. These design-related issues are among
the most important that any programmer will ever face. In this
chapter, you will get a look at a simple, nearly pure, flat-file address book
program called Address2. This program is designed to represent the simplest
possible database program that is still usable in a real-world situation. In
the next chapter, I will create a second address program, designed to be a
"killer" relational database with much more power than the one you
see in this chapter. One of
my primary goals in these two chapters is to lay out in the starkest possible
terms the key differences between flat-file and relational databases. The
point is for you to examine two database tools that perform the same task and
see exactly what the relational tool brings to the table. I found, however,
that it was simply impossible for me to create a completely flat-file
database design. As a result, I had to content myself with a design that was
nearly a pure example of a flat-file database. It does, however, contain one
smaller helper table that is linked in using relational database design
principles. My simple inability to omit this table does more than anything
else I can say to stress the weaknesses of the flat-file model, and to show
why relational databases are essential. The
second database program you see will contain a very powerful set of
relational features that could, with the aid of a polished interface, stand
up under the strain of heavy and complex demands. You could give this second
program to a corporate secretary or executive, and that person could make
some real use of it. The database shown in this chapter, on the other hand,
is meant to be a quick solution to a simple problem. One of the points you
shouldn't miss, however, is that the database from this chapter more than
suits the needs of most people. One of
the classic and most commonly made mistakes is to give people too many
features, or to concentrate on the wrong set of features. Those of us who
work in the industry forget how little experience most users have with
computers. Even the simple database program outlined in this chapter might be
too much for some people. Any attempt to sell them on the merits of the
database from the next chapter would simply be an exercise in futility. They
would never be willing to take the time to figure out what to do with it. As
a result, I suggest that you not turn your nose up at the database shown in
this chapter just because it is not as powerful as the one in the next
chapter. Just because your typical Volkswagen is not as powerful as an Alpha
Romeo does not mean that the Volkswagen people are in a small business niche,
or even that there is less money in VWs than in Alpha Romeos. Here is
a quick look at the terrain covered in this chapter:
After
finishing this chapter, you will have learned something about the kinds of
problems experienced when writing even a very basic database program that
serves a real-world purpose. The final product, though not quite up to
professional standards, provides solutions to many of the major problems
faced by programmers who want to create tools that can be used by the typical
user. In particular, the program explores how to use BCB to create a
reasonably usable interface. You will
find that the final program is relatively long when compared to most of the
programs you have seen so far in this book. The length of the program is a
result of my aspiration to make it useful in a real-world setting, while
simultaneously providing at least a minimum degree of robustness. The act of
adding a few niceties to the interface for a program gives you a chance to
see how RAD programming can help solve some fairly difficult problems. Before
closing this overview, I should perhaps explicitly mention that this chapter
does not cover printing, which is certainly one of the most essential
real-world needs for a database program. I will, however, add printing
capabilities to this program in Chapter 17, "Printing: QuickReport and
Related Technologies." In fact, that chapter will show how to add
printing to all the useful database programs that will be created in the next
few chapters of this book. My plan is to isolate the important, even crucial,
subject of printing in its own chapter where it can be properly addressed. You
should also be sure you have read the readme files on the CD that accompanies
this book for information about the alias used in the Address2 program and in
other programs in this book. If you have trouble getting any of these
programs running, be sure to check my Web site (users.aol.com/charliecal) for possible updates. Defining the
Data When
you're considering an address program, you can easily come up with a
preliminary list of needed fields: First Name Last Name Address City State Zip Phone After
making this list and contemplating it for a moment, you might ask the
following questions:
This
list of questions emerges only after a period of gestation. In a real-world
situation, you might come up with a list of questions like this only after
you talk with potential users of your program, after viewing similar programs
that are on the market, and after experimenting with a prototype of the
proposed program. Further information might be culled from your own
experience using or writing similar programs. Whatever way you come up with
the proper questions, the key point is that you spend the time to really
think about the kind of data you need. NOTE: Many books tell you to complete
your plan before you begin programming. The only thing wrong with this theory
is that I have never seen it work out as expected in practice. After
considering the preceding questions, you might come up with a revised list of
fields for your program: First Name Last Name Company Address1 Address2 City State Zip Home Phone Work Phone Fax EMail1 EMail2 Comment This
list might actually stand up to the needs of a real-world user. Certainly, it
doesn't cover all possible situations, but it does represent a reasonable
compromise between the desire to make the program easy to use and the desire
to handle a variety of potential user demands. At this
stage, you might start thinking about some of the basic functionality you
want to associate with the program. For example, you might decide that a user
of the program should be able to search, sort, filter, and print the data.
After stating these needs, you'll find that the user will need to break up
the data into various categories so that it can be filtered. The question, of
course, is how these categories can be defined. After
considering the matter for some time, you might decide that two more fields
should be added to the list. The first field can be called Category; it holds a name that describes the type of
record currently being viewed. For example, some entries in an address book
might consist of family members, whereas other entries might reference
friends, associates from work, companies where you shop, or other types of
data. A second field can be called Marked; it
designates whether a particular field is marked for some special processing. Here is
the revised list, with one additional field called Category, that is used to help the user filter the data he or she might be
viewing: First Name Last Name Company Address1 Address2 City State Zip Home Phone Work Phone Fax EMail1 EMail2 Comment Category Marked After
you carefully consider the fields that might be used in the Address2 program,
the next step is to decide how large and what type the fields should be.
Table 13.1 shows proposed types and sizes.
Now that you have decided on the basic structure of the table, the next task is to work out some of the major design issues. In particular, the following considerations are important:
A brief
consideration of the design decisions makes it clear that the table should
have a primary index on the first three fields and secondary indexes on the FName, LName, Company, and Category fields. The primary index can be used in place of a secondary index
on the FName field, but the intent of the
program's code will be clearer if a secondary index is used for this purpose.
In other words, the code will be easier to read if it explicitly sets the IndexName to something called FNameIndex instead of simply defaulting to the primary index. Table 13.2 shows
the final structure of the table. The three asterisks in the fourth column of
the table show the fields that are part of the primary index. NOTE: This table does not have a code
field--that is, it does not have a simple numerical number in the first field
of the primary index. Most tables will have such a value, but it is not necessary
here, because this database is, at least in theory, a flat-file database. I
say, "at least in theory," because I am going to make one small
cheat in the structure of this database. In short, there will be a second
table involved, simply because I could see no reasonable way to omit it from
the design of this program. Table
13.2. The fields used by the Address2 program.
Here is
another way of looking at the indexes for this table: Primary Index LName FName Company Category Index Category Company Index Company FName LName LName Index LName FName Company In this
particular case, I will actually end up using these fields and indexes as
designed. However, in a real-world situation, you should expect to come up
with a carefully thought-out draft like this, and then know in your heart
that after you get the program up and running, some things will have to
change. Don't tell someone: "Oh, I can complete this program in two
weeks; this is going to be easy!" Instead, say: "In two weeks, I
can get you a prototype, and then we can sit down and decide what changes
need to be made." You
should, however, have some clearly defined boundaries. For example, this
program is designed to be a flat-file database. If someone (yourself most
especially included!) tries to talk you into believing that this program
should really be a relational database of the kind planned for the next
chapter, then you have to slam your foot down and say: "No way!"
After you've started on a project, you should expect revisions, but you must
not allow the goal of the project to be redefined. That way leads to madness!
Defining the
Programs Appearance Before
beginning the real programming chores, you need to create a main form and at
least one of the several utility forms that will be used by the program. You
can let the Database Expert perform at least part of this task for you, but I
prefer to do the chore myself to give my program some individuality. The main
form of the Address2 program, shown in Figure 13.2, contains two panels. On
the top panel are all the labels and data-aware controls necessary to handle
basic input and output chores. All the main fields in the program can be
encapsulated in TDBEdit controls, except for the Comment field, which needs a TDBMemo, and the Category field, which needs a TDBLookupComboBox. The names of the data-aware
controls should match the field with which they are associated, so the first TDBEdit control is called FNameEdit; the second, LNameEdit; and so on. The TDBLookupComboBox is therefore called CategoryCombo--and the memo field, CommentMemo.
NOTE: If you find yourself chafing under
the restraints of my naming conventions, you shouldn't hesitate to adopt the
method you think best. For example, if you really prefer eFName or plain FName rather than FNameEdit as the name of a TDBEdit control, then you should go with your gut
instinct. The
bottom panel should contain four buttons for navigating through the table's
records, as well as Edit, Insert, and Cancel buttons. A status bar at the
bottom of the main form provides room for optionally reporting on the current
status of the program. The top
of the program contains a menu with the following format: Caption = `File' Caption =
`Print' Caption =
`-' Caption =
`Exit' Caption = `Edit' Caption =
`Copy' Caption =
`Cut' Caption =
`Paste' Caption = `Options' Caption =
`Filter' Caption =
`Set Category' Caption = `Search' Caption = `First
Name' Caption =
`Last Name' Caption =
`Company' Caption = `Sorts' Caption =
`First Name' Caption =
`Last Name' Caption =
`Company' Caption = `Colors' Caption =
`Form' Caption =
`Edits' Caption =
`Edit Text' Caption =
`Labels' Caption =
`------' Caption =
`Panels' Caption =
`System' Caption =
`Default' Caption =
`------' Caption =
`The Blues' Caption =
`Save Colors' Caption =
`Read Colors' Caption = `Marks' Caption = `Mark
All' Caption =
`Clear All Marks' Caption =
`Print Marked to File' Caption =
`Show Only Marked' Caption = `Help' Caption =
`About' Each
line represents the caption for one entry in the program's main menu. The
indented portions are the contents of the drop-down menus that appear when
you select one of the menu items visible in Figure 13.2. After
you create the program's interface, drop down a TTable and TDataSource on a data module, wire them up to
ADDRESS.DB, and hook up the fields to the
appropriate data-aware control. Name the TTable object AddressTable and name the TDataSource object AddressSource. To make this work correctly, you should
create an alias, called Address, that points to the location of ADDRESS.DB. Alternatively, you can create a single
alias called CUnleashed that points to the tables that
ship on the CD that accompanies this book. Take a look at the readme files on
the CD that accompanies this book for further information on aliases. Now
switch back to the main form, use the File | Include Unit Header option to
connect the main form and the TDataModule, and hook up the data-aware
controls shown in Figure 13.2 to the fields in the address table. The only
tricky part of this process involves the Category field, which is connected to the TDBLookupComboBox. I will explain how to use this field over
the course of the next few paragraphs. If you
run the program you have created so far, you will find that the TDBLookupComboBox for the Category field does not contain any entries; that is, you can't drop down its
list. The purpose of this control is to enable the user to select categories
from a prepared list rather than force the user to make up categories on the
fly. The list is needed to prevent users from accidentally creating a whole
series of different names for the same general purpose. Consider
a case in which you want to set a filter for the program that shows only a
list of your friends. To get started, you should create a category called Friend 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: Friend Friends Frends Acquaintances Buddies Buds Homies HomeBoys Amigos Chums Cronies Companions 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 Friend. The
simplest way to make this happen is to use not a TDBLookupComboBox, but a TDBLookupCombo. To use this control, simply pop open the
Property Editor for the Items property and type in a list of
categories such as the following: Home Work Family Local Business Friend Now when
you run the program and drop down the Category combo box, you will find that
it contains the preceding list. The only
problem with typing names directly into the Items property for the TDBLookupCombo 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 CATS.DB. 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: Home Work Family Local Business Friend Now that
you have two tables, it's best to switch away from the TDBLookupCombo and go instead with the TDBLookupComboBox. You make the basic connection to
the TDBLookupComboBox by setting its DataSource field to AddressSource and its DataField to Category. Then set the ListSource for the control to CatSource, and set ListField and KeyField to Category. NOTE: The addition of the TDBLookupComboBox into the program begs the
question of whether Address2 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 CATS and Address tables are not bound by a primary
and a foreign key. To allow
the user to change the contents of the CATS 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 CategoryDlg module's header to the list of
files included in the main form. You can do so simply by choosing File |
Include Unit Header. 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: void __fastcall TForm1::Category1Click(TObject
*Sender) { CategoryDlg
= new TCategoryDlg(this);
CategoryDlg->ShowModal();
CategoryDlg->Free(); } 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. Setting Up the
Command Structure for the Program 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: enum TCommandType {ctClose, ctInsert, ctPrior,
ctEdit, ctNext, ctCancel,
ctPrint, ctFirst, ctLast,
ctPrintPhone, ctPrintAddress,
ctPrintAll, ctDelete}; This
type enables you to associate each of the program's commands with the Tag 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: 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; } } This
code performs a simple typecast to allow you to access the Tag field of the component that generated the
command. This kind of typecast was explained in depth in Chapter 4,
"Events." 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 Tag property
of the appropriate control gets the correct value and that all the controls
listed here have the OnClick method manually set to the CommandClick method. I took all these steps while in
design mode, being careful to associate the proper value with the Tag property of each control. Table
13.3 gives a brief summary of the commands passed to the CommandClick method.
All the
code in this program will compile at this stage except for the references in CommandClick to HandleEditMode
and PrintData. For now, you can simply create
dummy HandleEditMode and PrintData private methods and leave their contents blank. 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. Examining the
"Rough Draft" of an Application 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. 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. "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?" 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. 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. 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 TDBGrid 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.) 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 FirstLastCompany that looks like this: 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"; } 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 "Blank
Record" should
appear in the calculated field. 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. Creating a
Finished Program 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. 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. /////////////////////////////////////// // File: Main.h // Project: Address2 // Copyright (c) 1997 by Charlie Calvert //--------------------------------------------------------------------------
#ifndef MainH #define MainH //--------------------------------------------------------------------------
#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\ExtCtrls.hpp> #include <vcl\Buttons.hpp> #include <vcl\DBCtrls.hpp> #include <vcl\Mask.hpp> #include <vcl\DBTables.hpp> #include <vcl\DB.hpp> #include <vcl\Menus.hpp> #include <vcl\Dialogs.hpp> #include <vcl\Report.hpp> #include <vcl\ComCtrls.hpp> #include <vcl\DBGrids.hpp> #include <vcl\Grids.hpp> #define READ_ONLY_STRING " [Read Only
Mode]" #define EDIT_MODE_STRING " [Edit Mode]" enum TSearchSortType {stFirst, stLast, stCompany}; enum TColorType {ccForm, ccEdit, ccEditText,
ccLabel, ccPanel}; enum TChangeType {tcColor, tcFontColor}; enum TCommandType {ctClose, ctInsert, ctPrior,
ctEdit, ctNext, ctCancel,
ctPrint, ctFirst, ctLast, ctPrintPhone, ctPrintAddress,
ctPrintAll, ctDelete}; class TForm1 : public TForm { __published:
// IDE-managed Components TPanel
*Panel2; TButton
*InsertBtn; TButton
*EditBtn; TButton
*CancelBtn; TMainMenu *MainMenu1; TMenuItem
*File1; TMenuItem
*PrintAddresses1; TMenuItem
*PrintPhoneOnly1; TMenuItem
*PrintEverything1; TMenuItem
*Print1; TMenuItem
*N1; TMenuItem *Exit1; TMenuItem *Edit1; TMenuItem
*Copy1; TMenuItem
*Cut1; TMenuItem
*Paste1; TMenuItem
*Options1; TMenuItem
*Search1; TMenuItem
*Filter1; TMenuItem
*Category1; TMenuItem
*Sorts1; TMenuItem
*FirstName1; TMenuItem
*LastName1; TMenuItem
*Company1; TMenuItem *Colors1;
TMenuItem
*FormColor1; TMenuItem
*EditColor1; TMenuItem
*EditText1; TMenuItem
*Labels1; TMenuItem
*Panels1; TMenuItem
*Marks1; TMenuItem
*MarkAll1; TMenuItem
*ClearAllMarks1; TMenuItem
*PrintMarkedtoFile1; TMenuItem
*Help1; TMenuItem
*About1; TColorDialog
*ColorDialog1; TDBNavigator
*DBNavigator1; TStatusBar
*StatusBar1; TBevel
*Bevel1; TPanel
*Panel1; TLabel
*Label2; TLabel
*Label3; TLabel
*Address1; TLabel
*Address2; TLabel
*City; TLabel
*State; TLabel *Zip;
TLabel
*Company; TLabel
*HPhone; TLabel
*WPhone; TLabel *Fax;
TLabel
*Comment; TLabel
*EMail1; TLabel
*Category; TLabel
*EMail2; TSpeedButton
*SpeedButton1; TDBEdit
*LNameEdit; TDBEdit
*FNameEdit; TDBEdit
*Address1Edit; TDBEdit
*Address2Edit; TDBEdit
*CityEdit; TDBEdit
*StateEdit; TDBEdit
*ZipEdit; TDBEdit
*CompanyEdit; TDBEdit
*HomePhoneEdit; TDBEdit
*WorkPhoneEdit; TDBEdit
*FaxEdit; TDBEdit
*EMail1Edit; TDBEdit
*EMail2Edit; TDBMemo
*CommentMemo;
TDBLookupComboBox *CategoryCombo; TButton
*DeleteBtn; TDBGrid
*DBGrid1; TMenuItem
*FNameSearch; TMenuItem
*LNameSearch; TMenuItem *CompanySearch;
TMenuItem
*N3; TMenuItem
*System1; TMenuItem
*Defaults1; TMenuItem
*Blues1; TMenuItem
*N4; TMenuItem
*SaveCustom1; TMenuItem
*ReadCustom1; TMenuItem
*N2; TMenuItem
*ShowOnlyMarked1; void __fastcall
Copy1Click(TObject *Sender); void
__fastcall CommandClick(TObject *Sender); void
__fastcall AddressSourceStateChange(TObject *Sender); void
__fastcall FormShow(TObject *Sender); void
__fastcall About1Click(TObject *Sender); void
__fastcall CommandSortClick(TObject *Sender); void
__fastcall CommandSearchClick(TObject *Sender); void
__fastcall CommandColorClick(TObject *Sender); void
__fastcall System1Click(TObject *Sender); void
__fastcall Defaults1Click(TObject *Sender); void
__fastcall Blues1Click(TObject *Sender); void
__fastcall SaveCustom1Click(TObject *Sender); void
__fastcall ReadCustom1Click(TObject *Sender); void
__fastcall Filter1Click(TObject *Sender); void __fastcall
AddressSourceDataChange(TObject *Sender, TField *Field); void
__fastcall Category1Click(TObject *Sender); void
__fastcall MarkAll1Click(TObject *Sender); void
__fastcall ClearAllMarks1Click(TObject *Sender); void
__fastcall SpeedButton1Click(TObject *Sender); void
__fastcall FormDestroy(TObject *Sender); void
__fastcall ShowOnlyMarked1Click(TObject *Sender); private:
// User declarations AnsiString
FCaptionString; void
DoSort(TObject *Sender); void HandleEditMode();
void
SetReadOnly(BOOL NewState); void
PrintData(TCommandType Command); void
SetEdits(TColor Color); void
SetEditText(TColor Color); void
SetLabels(TColor Color); void
SetPanels(TColor Color); TColor GetColor(TObject
*Sender); public:
// User declarations virtual
__fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------
extern TForm1 *Form1; //--------------------------------------------------------------------------
#endif Listing 13.2. The main form for the Address2 program. /////////////////////////////////////// // File: Main.cpp // Project: Address2 // Copyright (c) 1997 by Charlie Calvert #include <vcl\vcl.h> #include <vcl\clipbrd.hpp> #include <vcl\registry.hpp> #pragma hdrstop #include "Main.h" #include "DMod1.h" #include "AboutBox1.h" #include "FileDlg1.h" #include "Category1.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) :
TForm(Owner) {
FCaptionString = Caption;
ReadCustom1Click(NULL); } void __fastcall TForm1::FormDestroy(TObject *Sender)
{
SaveCustom1Click(NULL); } void TForm1::DoSort(TObject *Sender) { switch
(dynamic_cast<TComponent *>(Sender)->Tag) { case
stFirst:
DMod->AddressTable->IndexName = "FNameIndex"; break; case
stLast:
DMod->AddressTable->IndexName = "LNameIndex"; break; case
stCompany:
DMod->AddressTable->IndexName = "CompanyIndex"; break; } } void __fastcall TForm1::Copy1Click(TObject *Sender) { if
(dynamic_cast<TDBEdit*>(ActiveControl))
(dynamic_cast<TDBEdit*>(ActiveControl))->CopyToClipboard(); if
(dynamic_cast<TDBMemo*>(ActiveControl))
dynamic_cast<TDBMemo*>(ActiveControl)->CopyToClipboard(); if
(dynamic_cast<TDBComboBox*>(ActiveControl))
Clipboard()->AsText = dynamic_cast<TDBComboBox*>(ActiveControl)->Text;
} 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; } } void TForm1::HandleEditMode() {
InsertBtn->Enabled = !DMod->AddressSource->AutoEdit;
CancelBtn->Enabled = !DMod->AddressSource->AutoEdit;
DeleteBtn->Enabled = !DMod->AddressSource->AutoEdit; if
(!DMod->AddressSource->AutoEdit) { SetReadOnly(True);
EditBtn->Caption = "Stop Edit"; Caption =
FCaptionString + EDIT_MODE_STRING; } else { if
(DMod->AddressTable->State != dsBrowse)
DMod->AddressTable->Post();
SetReadOnly(False);
EditBtn->Caption = "Goto Edit"; Caption =
FCaptionString + READ_ONLY_STRING; } } void TForm1::PrintData(TCommandType Command) { } void TForm1::SetReadOnly(BOOL NewState) {
DMod->AddressSource->AutoEdit = NewState; } void __fastcall TForm1::AddressSourceStateChange(TObject
*Sender) { AnsiString
S; switch
(DMod->AddressTable->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->SimpleText = "State: " + S; } void __fastcall TForm1::AddressSourceDataChange(TObject
*Sender, TField
*Field) { HBITMAP
BulbOn, BulbOff; Caption =
DMod->AddressTable->FieldByName("Marked")->AsString; if
(DMod->AddressTable->FieldByName("Marked")->AsBoolean) { BulbOn =
LoadBitmap((HINSTANCE)HInstance, "BulbOn");
SpeedButton1->Glyph->Handle = BulbOn; } else { VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++
Builder.
|