The purpose of this course is to apply the techniques learned in 266371 to the construction of large programming projects. We will follow a set of guidelines that will lead us through the process of creating such projects. Each step in this process requires new tools. These tools range from simple new techniques in c++, problem solving patterns, interface design techniques, to more complex Windows techniques.
Once you have a general idea of the scope of a project, the first step is to create an interface for the program. This allows the user to decide if you have achieved all that they require in the project and also make critical comments about the applicability of the interface (does this interface meet standard requirements of the user such as standard forms used in professions).
The user interface for a program frequently starts its life as a series of drawings, cut outs and other very childish looking objects. This is because the group that designs the interface is often composed of designers and programmers who want to be free to quickly change the look of the interface after consulting with the client. Experts on interface design often declare that programmers should not create interface designs since they are not design experts. The problem with this that except in large projects (programs created for large scale distribution), the budget for projects seldom includes an interface expert.
With this in mind programmers should learn as much as possible about standard interface designs and learn to prototype interfaces to get user feedback. In addition, you should learn how to package frequently used interface items so they can be easily reused. Dialogs such as those use to retrieving standard the name and address of a person should be the same every time they are used in a suit of programs. Before designing a interface we will consided the class structure of information.
Suppose you are asked to maintain a list of faculty members that are Instructors, Assistant, Associate and Full Professors. This list will have the name of the faculty along with the office number and phone. You should be able to add faculty, delete faculty and edit faculty data. The information should be saved in a database. You can use the project wizard to create a shell with the file menu (for save, open, and exit operations).
We will consider design of classes and interface in a simplified course listing program. Each course will have a number and a professor (no students to begin with). Our program will have a list box with all courses listed. We will start by designing the professor class and the dialog boxes needed to maintain members of this class.
The only operations your program will allow the user to do are insert, delete and edit. This means you will add the menu below.
With the experience you have had, it should be clear you will need a dialog box that will display the information needed to create a new professor and edit an existing professor.
|
You have determined that you will need a first name, last name, middle initial, rank, office and phone. This dialog should provide a method for gathering that data. There are a few new items in this box. First notice that the ok button appears to be greyed out. This indicates that it has been disabled. We want all of the fields to be filled in before the user can click ok, so we have added a set of functions that will disable ok until the fields have been filled. In addition, there are only three choices for the rank of a professor. We could have used a set of radio buttons for these choices, but chose instead to use a TComboBox (pull down listbox with a choice appearing at the top) to display the choices. We will use this again to display the list of professors when we create a new course listing. |
|
When the combo box arrow is selected, the entire list of choices appears, and whichever is selected will appear as the sole entry in the box after selection. |
Also notice that only part of the phone number is collected, since all phones at the school have area code 920 and exchange 465. This means the data returned from the dialog must me manipulated to add the constant parts, or whenever the data is printed these parts must be added. I have just stored the complete number, but this may not always be the best solution.
At this point you should test your interface on the user. The next step will be designing objects and a database to go with the objects. This is a major step which while not set in concrete, requires a considerable amount of work in a more complicated program than this.
Until now you have stored your data in stream files. These files were flexible enought to store objects of different types as long as they were members of classes that were descendents of the same parent class. The main problem with this type of file is that you needed to store all of the objects in an array. You can easily manipulate this array, but if your file is too large you cannot create an array large enough to hold all of the objects in the file. The solution to this problem is to store your objects in a database.
Databases are files created by database management systems. The database management system has many functions for retrieving items based on various key fields. This will allow you to retrieve only the objects you need for a given process. These programs can also find all records that share a given property or properties. Most comercially available databases today are relational databases. Data in relational databases is stored in tables. It is possible to use key fields to define relationships between data items in more than one table. You will use the relational database Access for this course. For many simple sets of objects relational databases work very well. One problem with them is that you cannot easily store a file of mixed types of objects. You can simulate this in c++, but the database itself does not support mixed object types.
This program has only one type of object Professor, so your database will have only one table. It will correspond directly to an array of Professor. In larger projects, like your final project, all of the data in your project will not fit into an array. You will only retrieve items when you need them and only store sets that may result from queries in arrays. Once you have completely specified the data members of a class you can easily create a table corresponding to objects in that class.
Once you have created your database (whether it has one or more tables), you have to use the DBE Administrator to create an alias for your database.
You may connect your database to the program by adding a TTable (and to check a TDataSource and TDBGrid).
Your first database example will be only a slight variation on what you did last semester with flat files (streams). You will have an array of professors that will be initialized from the database. The array will be responsible for displaying the information in the main form's listbox and retrieving the correct item when editing or deleting.
You will practice good object design by allowing the array to retrieve data from your database. You need only pass the table to a function to retrieve the data. This function in turn calls on a retrieve function in the class Professor to create professor objects. The ProfList retrieve function is almost a direct copy of the normal read function (you can see such a function in the source code for the completed project). The major differences are that you use a while not end of file look and the database functions First and Next. You may wonder why the parameter is a TDataSet and not a TTable. TDataSet is the parent of all of the standard database classes, so if you where to retrieve items from another descendent of TDataSet (such as TQuery), the same function can be used.
void ProfList::retrieve(TDataSet *source)
{
clearArray();
source->First(); //move to the beginning of the table
while (!source->Eof) //read until the end of the table
{
Professor p;
p.retrieve(source); // let the professor do its own read
source->Next(); // Go to the next item in the table
insertItem(p); // Insert the professor in the ProfList
}
}
The retrieve function for Professor has been made easy because you gave the fields of the database table the same names as the class data members. Recall that source is your table and since it is a descendent of TDataSet you can use the FieldByName function which retrieves the value in the named field as a TField. You must still convert to the type of your member data object. Some of the conversion functions are
void Professor::retrieve(TDataSet *source)
{
_lastName = source->FieldByName("lastName")->AsString;
_firstName = source->FieldByName("firstName")->AsString;
_initial = source->FieldByName("initial")->AsString;
_office = source->FieldByName("office")->AsString;
_phone = source->FieldByName("phone")->AsString;
_rank = source->FieldByName("rank")->AsInteger;
}
The function used to add a new entry, is almost identical to those you have used in the past. The dialog box is initialized with blanks (zero for the ItemIndex of the combo box). The data retrieved is used to create a professor and this professor is added to the ProfList. The database can be updated imediately since it doesn't need to be created linearly. Once again you will let the details of sending the record to the class Professor. The two table commands you need to issue are Append (add at the end) and Post (carry out the command). You will see other commands in the future.
void __fastcall TMainForm::AddProfessor1Click(TObject *Sender)
{
ProfDialog->LastName->Text = "";
ProfDialog->FirstName->Text = "";
ProfDialog->MiddleInit->Text ="";
ProfDialog->Office->Text = "";
ProfDialog->Phone->Text ="(920)465- ";
ProfDialog->RankComboBox->ItemIndex = 0;
if (ProfDialog->ShowModal()== IDOK)
{
Professor p(ProfDialog->LastName->Text,
ProfDialog->FirstName->Text,
ProfDialog->MiddleInit->Text,
ProfDialog->Office->Text,
ProfDialog->Phone->Text,
ProfDialog->RankComboBox->ItemIndex);
ProfTable->Append();
p.place(ProfTable);
ProfTable->Post();
ProfTable->Active=false;
ProfTable->Active=true;//This refreshes the table variable from the database.
_professors.insertItem(p);
ProfListBox1->Items->Text = _professors.getDescription();
}
}
The actual transfering of data again uses the FieldByName function, but this time you set its Value to the value of your data object. Note that there is no need to typecast here if you defined the fields of the database properly.
void Professor::place(TDataSet *source)
{
source->FieldByName("lastName")->Value = _lastName;
source->FieldByName("firstName")->Value = _firstName;
source->FieldByName("initial")->Value = _initial ;
source->FieldByName("office")->Value = _office ;
source->FieldByName("phone")->Value = _phone ;
source->FieldByName("rank")->Value = _rank;
}
Once again editing an existing record is very similar to what you have done in the past except the database is updated instantly. Notice the parts in red below. First you set the table to location "where" the table number of the item. This is done by moving to the beginning of the table then moving to "where". Finally instead of using Append as you did when you added an item, you use Edit. The rest of the update is the same as the update when you added a new item.
void __fastcall TMainForm::EditProfessor1Click(TObject *Sender)
{
int where = ProfListBox1->ItemIndex;
if(where >=0)
{
ProfTable->First();
ProfTable->MoveBy(where);
ProfDialog->LastName->Text = _professors[where].last();
ProfDialog->FirstName->Text = _professors[where].first();
ProfDialog->MiddleInit->Text =_professors[where].initial();
ProfDialog->Office->Text = _professors[where].office();
ProfDialog->Phone->Text =_professors[where].phone();
ProfDialog->RankComboBox->ItemIndex = _professors[where].rank();
if (ProfDialog->ShowModal()== IDOK)
{
Professor p(ProfDialog->LastName->Text,
ProfDialog->FirstName->Text,
ProfDialog->MiddleInit->Text,
ProfDialog->Office->Text,
ProfDialog->Phone->Text,
ProfDialog->RankComboBox->ItemIndex);
ProfTable->Edit();
p.place(ProfTable);
ProfTable->Post();
ProfTable->Active=false;
ProfTable->Active=true;//This refreshes the table variable from the database.
_professors.deleteItemAt(where);
_professors.insertItemAt(p,where);
ProfListBox1->Items->Text = _professors.getDescription();
}
}
}
Once again the delete function is not much different from those you have seen in the past except that you delete the item directly from the database when you delete it from the list.
void __fastcall TMainForm::DeleteProfessor1Click(TObject *Sender)
{
int where = ProfListBox1->ItemIndex;
if(where >=0)
{
String prompt = "Are you sure you want to delete "+_professors[where].name();
if (Application->MessageBoxA(prompt.c_str(),"Delete This?",MB_YESNO)==IDYES)
{
ProfTable->First();
ProfTable->MoveBy(where);
ProfTable->Delete();
ProfTable->Active=false;
ProfTable->Active=true;//This refreshes the table variable from the database.
_professors.deleteItemAt(where);
ProfListBox1->Items->Text = _professors.getDescription();
}
}
}
The code for the Place function does not set the id field in the database. This is because the id field is a Key Field and the database has complete control over its value. If you try to change this field you will get and error message that includes a statement about causing conflicts in queries. When you add a record to a table, the database automatically fills in the key field. If you want to display the key field in a list box, you must retrieve the record. It is perfectly legal to retrieve the key field from a database. To do this successfully, after calling place and Post, you must first set the tables Active property to false, then set it to true. This will allow you to retrieve the record including the key field.
If you review what you have done, you will see that except for displaying the items in the listbox, the list of professors is not needed. In the edit and delete functions, you could have move to the location in the database before calling the dialog. Then you simply retrieve the information from the database using Professor::retrieve and display the information in the dialog box much as you did here. In large applications like your final project, you can dispense with the array.
Click here to get the code to this point. The rest of this problem is discussed in Week 2.