Flat-File, Real-World Databases - (Part 01)

 

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:

  • Sorting data.

 

  • Filtering data.

 

  • Searching for data.

 

  • Dynamically moving a table in and out of a read-only state.

 

  • Forcing the user to select a field's value from a list of valid responses.

 

  • Allowing the user to choose the colors of a form at runtime.

 

  • Saving information to the Registry. In particular, you see how to use the Registry to replace an INI file, and how to save and restore information from and to the Registry at program startup.

 

  • Using events that occur in a TDataModule inside the main form of your program. That is, the chapter shows how to respond to events specific to one form from inside a second form. Or, more generally, it shows how to handle events manually rather than let BCB set up the event handler for you.

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:

  • What about complex addresses that can't be written on one line?

 

  • Is one phone number enough? What about times when I need a home phone and a work phone?

 

  • Speaking of work, what about specifying the name of the company that employs someone on the list?

 

  • What about faxes?

 

  • This is the 1990s, so what about an e-mail address?

 

  • What about generic information that doesn't fit into any of these categories?

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.

Nearly all the real-world programs that I have seen, both my own and others, whether produced by individuals or huge companies, always seem to go through initial phases that are later abandoned in favor of more sophisticated designs. This is part of what Delphi and RAD programs in general are all about. They make it possible for you to create a draft of your program and then rewrite it.

Think hard about what you want to do. Then get up a prototype in fairly short order, critique it, and then rethink your design. Totally abandoning the first draft is rarely necessary, but you are almost certainly going to have to rewrite. For this reason, concentrating on details at first is not a good idea. Get things up and running; then if they look okay, go back and optimize.

The point is that the process is iterative. You keep rewriting, over and over, the same way authors keep rewriting the chapters in their books. RAD programming tools help make this kind of cycle possible. The interesting thing about Delphi is that the same tool that lets you prototype quickly is also the tool that lets you optimize down to the last clock cycle.

I don't, however, think most contemporary application programming is really about optimization any more than it's about attempting to design a program correctly on paper before writing it. My experience leads me to believe that the practical plan that really works is iterative programming. Think for a little bit and then write some code. Review it, then rewrite it, then review it, and then rewrite it, and so on. Another, somewhat more old-fashioned name for this process is simply: One heck of a lot of hard work!


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.

Table 13.1. The lengths and types of fields used by the Address2 program.

Name

Type

Size

FName

Character

40

LName

Character

40

Company

Character

40

Address1

Character

40

Address2

Character

40

City

Character

40

State

Character

5

Zip

Character

15

HPhone

Character

15

WPhone

Character

15

Fax

Character

15

EMail1

Character

45

EMail2

Character

45

Comment

Memo

20

Category

Character

15

Marked

Logical

 



As you can see, I prefer to give myself plenty of room in all the fields I declare. In particular, notice that I have opted for wide
EMail fields to hold long Internet addresses, and I have decided to make the Comment field into a memo field so that it can contain long entries, if necessary. The names of some of the fields have also been altered so that they don't contain any spaces. This feature might prove useful if the data is ever ported to another database.

 

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:

  • The program should run off local tables, because this is the kind of tool likely to be used on individual PCs rather than on a network. The choice of whether to use Paradox or dBASE tables is a toss-up, but I'll opt to use Paradox tables because they provide more features.

 

  • The user should be able to sort the table on the FName, LName, and Company fields.

 

  • Searching on the FName, LName, and Company fields should be possible.

 

  • The user should be able to set up filters based on the Category field.

 

  • The times when the table is editable should be absolutely clear, and the user should be able to easily move in and out of read-only mode.

 

  • Printing the contents of the table based on the filters set up by the Category field should be possible.

 

  • Choosing a set of colors that will satisfy all tastes is very difficult, so the user should be able to set the colors of the main features in the program.

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.

Name

Type

Size

PIdx

Index

FName

Character

40

*

FNameIndex

LName

Character

40

*

LNameIndex

Company

Character

40

*

CompanyIndex

Address1

Character

40

 

 

Address2

Character

40

 

 

City

Character

40

 

 

State

Character

5

 

 

Zip

Character

15

 

 

HPhone

Character

15

 

 

WPhone

Character

15

 

 

Fax

Character

15

 

 

EMail1

Character

45

 

 

EMail2

Character

45

 

 

Comment

Memo

20

 

 

Category

Character

15

 

CategoryIndex

Marked

Logical

 

 

 



Now that you have a clear picture of the type of table that you need to create, you can open the Database Desktop and create the table, its primary index, and its four secondary indexes. When you're done, the structure of the table should look like that in Figure 13.1. You can save the table under the name
ADDRESS.DB.

FIGURE 13.1. Designing the main table for the Address2 program. Portions of the table are not visible in this picture.

 

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.

FIGURE 13.2. The main form for the Address2 program.

 

 


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.

My history in this regard is simple. I started out deploring Hungarian notation and then slowly inched over to the point at which I was reluctantly starting to use it in my programs.

Then there came a day when I was squinting at some egregious variable name dreamed up by an undoubtedly besotted Microsoft employee, and I just knew that I had had enough of abbreviations, and especially of prefixing them to a variable name.

One of my original goals was to keep variable names short. I went to great lengths to achieve this end. Then I watched C++ linkers mangle my short variable names into behemoths that consumed memory like sharks possessed by a feeding frenzy. After contemplating this situation for a while, I decided that the one thing I could bring to the table that I really cared about was clarity. As a result, I dropped Hungarian notation from all my new code and began using whole words whenever possible.


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.

It is, however, a cheat in the design of the program, since my goal was to create a pure flat-file database. The facts here are simple: I want the database to be as simple as possible, but I also want it to be useful. Without this one feature, I saw the program as hopelessly crippled. As stated earlier, this shows the importance of relational database concepts in even the simplest programs. In short, I don't think I can get any work done without using relational techniques. Relational database design is not a nicety; it's a necessity.


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.

FIGURE 13.3. The Category form enables the user to alter the contents of CATS.DB.

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.

Table 13.3. Commands passed to CommandClick.

Command

Type

Name

Tag

Exit

TMenuItem

btClose

0

Insert

TButton

btInsert

1

Prior

TButton

btPrior

2

Edit

TButton

btEdit

3

Next

TButton

btNext

4

Cancel

TButton

btCancel

5

Print

TMenuItem

btPrint

6

First

TButton

btFirst

7

Last

TButton

btLast

8



The task of filling in the
Tag properties and setting the OnClick events for all these controls is a bit tedious, but I like the easy-to-read code produced by following this technique. In particular, I like having all the major commands send to one method, thereby giving me a single point from which to moderate the flow of the program. This is particularly useful when you can handle most of the commands with a single line of code. Look, for example, at the ctNext and ctCancel portions of the case statement in 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.

Listing 13.1. The source code for the header of the main form of the Address2 program.

///////////////////////////////////////

 

// 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

 

  {

 

   

Next (Part 02) >>

 

VMS Desenvolvimentos

Diversas Dicas, Apostilas, Arquivos Fontes, Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.