The Final Version of the Home Inventory Program

Now let's get to the detailed analysis of the final version of the home inventory program. We'll start with the main() function of this program, which is shown in Figure 13.1.

Figure 13.1. The main() function of the final version of the home inventory main program (from codehmtst8.cpp)
int main()
{
  ifstream HomeInfo("home.inv");
  HomeInventory MyInventory;
  char option;
  string answer;

  MyInventory.LoadInventory(HomeInfo);

  for (;;)
    {
    option = GetMenuChoice(MyInventory);

    if (option == Exit)
      break;
    else
      ExecuteMenuChoice(option,MyInventory);
    }

    HomeInfo.close();

    for (;;)
      {
      cout << "Save the changes you have made? (Y/N) ";
      cin >> answer;

      if ((answer == "Y") || (answer == "y"))
        {
        ofstream NewHomeInfo("home.inv");
        MyInventory.StoreInventory(NewHomeInfo);
        cout << "Inventory updated" << endl;
        break;
        }
      else if ((answer == "N") || (answer == "n"))
        {
        cout << "Changes cancelled" << endl;
        break;
        }
      else
        windos::clrscr();
      }

    return 0;
}

This starts off much like the previous versions, by creating a HomeInventory object called MyInventory and loading it with data from an input file called home.inv. Then it enters an “endless” for loop that calls the GetMenuChoice function to find out what operation the user wants to perform, then calls the ExecuteMenuChoice function to perform that operation.

We haven't yet used an “endless” loop, which is written as “for (;;)”.[1] Since we haven't specified any initialization, modification, or continuation expression, such a loop will run until it is interrupted by a break or return statement. In this case, the break statement is executed when the user indicates that he or she is finished with the program by entering the code for “exit” in the GetMenuChoice function.

[1] One possible pronunciation of this construct is “forever”.

Once the user is done entering, modifying, and examining data, the second “endless” for loop asks the user whether any changes should be made permanent by being written out to the home.inv file. If the user answers “y” or “Y”, the changes are written out; if the answer is “n” or “N”, the changes aren't written out. In either case, the “endless” loop is terminated, as is the program immediately afterward. If the user doesn't type a valid character, the program keeps asking the question until it gets an answer it likes.

So much for the bird's-eye view of the program. Now let's take a more detailed look at how it works. The first topic we'll examine is where that Exit value came from. This question is answered by the enum defined in Figure 13.2.

Figure 13.2. The MenuItem enum (from codehmtst8.cpp)
enum MenuItem {AddItem=1, SelectItemFromNameList,
 EditByPartialName, EditByDescription, EditByCategory,
 DeleteItemFromNameList, PrintNames, PrintAll, Exit
};

As you can see, this enum lists all of the possible menu choices from which the user can select. I've put it at the top of the hmtst8.cpp source file because the values it defines are needed in more than one function in the main program, and that's the easiest way to make these values available to several functions.

Next, let's look at the GetMenuChoice function, shown in Figure 13.3.

Figure 13.3. The GetMenuChoice function (from codehmtst8.cpp)
char GetMenuChoice(HomeInventory& Inventory)
{
 short MenuRow;
 short option;

 for (;;)
   {
   windos::clrscr();

   cout << Inventory.GetCount() << " items in database." << endl;

   cout << endl;

   cout << AddItem << ". Add item" << endl;

   cout << SelectItemFromNameList <<
    ". Select item name from list" << endl;

   cout << EditByPartialName <<
    ". Edit item by partial name" << endl;

   cout << EditByDescription <<
    ". Edit item by description" << endl;

   cout << EditByCategory <<
    ". Edit item by category" << endl;

   cout << DeleteItemFromNameList <<
    ". Delete item" << endl;

   cout << PrintNames <<
    ". Print item names" << endl;

   cout << PrintAll << ". Print data base" << endl;

   cout << Exit << ". Exit" << endl;

   cout << endl;

   cout << "Please enter a number from " <<
    AddItem << " to " << Exit << ": ";

   cin >> option;

   if ((option >= AddItem) && (option <= Exit))
     break;
   else
    HomeUtility::HandleError("Sorry, that's an invalid option.");
   }

  windos::clrscr();

  return option;
}

Most of this code is pretty simple, but there are a few new twists. To start with, we're using a screen control function that we haven't seen before: windos::clrscr. This function clears the screen so that we can start writing text on it without worrying about what might already be there.[2]

[2] The clrscr function is one of a few functions that I've had to write to allow our programs to use underlying operating system I/O functions, because the facilities they provide don't exist in standard C++. You'll have to replace them if you want to run with another compiler or operating system. A list of these functions is in the “readme.txt” file on the CD.

Once the screen has been cleared, we display each of the menu choices on the screen and ask the user to type in the number of the operation to be performed. Next, we check the entered number to make sure that it is one of the legal values. If it is, we break out of the “endless” loop, clear the screen again so that the function we're going to perform has a fresh canvas to paint on, and return the number of the operation the user selected. On the other hand, if the user has typed in an invalid value, we call a utility function called HomeUtility::HandleError that notifies the user of the error; then we continue in the “endless” loop until we get a valid answer to our question.[3]

[3] We'll get to the purpose and implementation of the HomeUtility namespace functions in the section “Using a namespace to Group Utility Functions” on page 921.

Susan had some questions about this function, so we discussed it.

Susan: Is this GetMenuChoice function listing the choices on the screen?

Steve: Yes.

Susan: How does it know how to put the number of the selection in front of each one?

Steve: That's how enum values are displayed by operator <<. That's why each line begins with one of the values from the MenuItem enum.

Susan: How did you know to use the clrscr function to clear the screen?

Steve: I read the documentation for the compiler.

Susan: But how did you know there even was such a function?

Steve: Because I've used it before.

Susan: Well, what if it was the first time you ever needed it?

Steve: Then I would either have to read a book (like this one) or ask somebody. It's just like learning about anything else.

Once we know which operation the user wants to perform, main calls ExecuteMenuChoice (Figure 13.4).

Figure 13.4. ExecuteMenuChoice (from codehmtst8.cpp)
void ExecuteMenuChoice(char option, HomeInventory& Inventory)
{
 short itemno;
 ofstream Printer("lpt1");
 string Name;
 string Description;
 string Category;

 switch (option)
  {
  case AddItem:
   {
   cout << "Adding item" << endl << endl;
   Inventory.AddItem();
   ofstream SaveHomeInfo("home.$$$");
   Inventory.StoreInventory(SaveHomeInfo);
   }
  break;

  case SelectItemFromNameList:
   cout << "Selecting item from whole inventory";
   cout << endl << endl;
   HomeUtility::IgnoreTillCR();
   itemno = Inventory.SelectItemFromNameList();
   if (itemno != -1)
     {
     Inventory.EditItem(itemno);
     ofstream SaveHomeInfo("home.$$$");
     Inventory.StoreInventory(SaveHomeInfo);
     }
  break;

  case EditByPartialName:
   cout << "Selecting item by partial name";
   cout << endl << endl;
   cout << "Please enter part of the name of the item
";
   cout << "(or ENTER for all items): ";
   HomeUtility::IgnoreTillCR();
   getline(cin,Name);
   cout << endl;
   itemno = Inventory.SelectItemByPartialName(Name);
   if (itemno != -1)
     {
     Inventory.EditItem(itemno);
     ofstream SaveHomeInfo("home.$$$");
     Inventory.StoreInventory(SaveHomeInfo);
     }
  break;

  case EditByDescription:
   cout << "Selecting item by partial description";
   cout << endl << endl;
   cout << "Please enter part of the description of the item
";
   cout << "(or ENTER for all items): ";
   HomeUtility::IgnoreTillCR();
   getline(cin,Description);
   cout << endl;

   itemno = 
     Inventory.SelectItemFromDescriptionList(Description);

   if (itemno != -1)
     {
     Inventory.EditItem(itemno);
     ofstream SaveHomeInfo("home.$$$");
     Inventory.StoreInventory(SaveHomeInfo);
     }
  break;

  case EditByCategory:
   cout << "Selecting item by partial category";
   cout << endl << endl;
   cout << "Please enter part or all of the category name
";
   cout << "(or ENTER for all categories): ";
   HomeUtility::IgnoreTillCR();
   getline(cin,Category);
   cout << endl;

   itemno = 
     Inventory.SelectItemFromCategoryList(Category);

   if (itemno != -1)
     {
     Inventory.EditItem(itemno);
     ofstream SaveHomeInfo("home.$$$");
     Inventory.StoreInventory(SaveHomeInfo);
     }
  break;

  case DeleteItemFromNameList:
   cout << "Deleting item" << endl << endl;
   itemno = Inventory.SelectItemFromNameList();
   if (itemno != -1)
     {
     string Query;
     cout << "Are you sure you want to delete item ";
     cout << itemno + 1 << " (Y/N)? ";
     cin >> Query;
     if (toupper(Query[0]) == 'Y')
       {
       Inventory.DeleteItem(itemno);
       ofstream SaveHomeInfo("home.$$$");
       Inventory.StoreInventory(SaveHomeInfo);
       }
     }
  break;

  case PrintNames:
   Inventory.PrintNames(Printer);
  break;

  case PrintAll:
   Inventory.PrintAll(Printer);
  break;
  }
}

While this function is fairly long, there's nothing terribly complex about it. After declaring some variables and initializing an ofstream called Printer to write to the printer whose name is “lpt1”, the rest of the function consists of a switch statement. Each of the cases of the switch executes whatever operation the user selected during the GetMenuChoice function. Most of these are very similar, as you'll see.[4]

[4] Please note that the C++ standard does not specify the name of the stream to which the system printer is connected. However, lpt1 is a typical name for the stream that is connected to the system printer in a Windows environment.

As soon as we take care of an initial question from Susan, we'll take a look at each of these cases in order.

Susan: What's a case?

Steve: It's the part of a switch statement that executes the code for one of the possibilities. Basically, the switch and case statements are like a bunch of if/else statements but easier to read and modify.

  1. The AddItem case displays a message telling the user what operation is being performed and calls the AddItem member function of the Inventory object to add the item. Then the (modified) inventory is saved to a “backup” file called home.$$$. The purpose of this file is to prevent disaster should the power fail or the system crash during a lengthy editing session. Because the inventory is written out to the home.$$$ file whenever any change is made, the user can recover the work that might otherwise be lost in case of any kind of system failure. After this is done, the processing for this step is complete, so the break statement exits from the switch statement and the ExecuteMenuChoice function returns to the main program.

  2. The SelectItemFromNameList case displays a message telling the user what operation is being performed and calls the SelectItemFromNameList member function of the Inventory object to determine which inventory item the user wants to edit. If the user doesn't select an item to be edited, the result of this call will be the value –1, in which case the editing step is omitted and the ExecuteMenuChoice function returns to the main program. However, if the user does select an item to be edited, the EditItem member function of the Inventory object is called with the index of that item. When that function has finished execution, the inventory is saved as in AddItem.

Susan had some more questions at this point.

Susan: Why is the result –1?

Steve: Because 0 is a valid index; remember, we're programming in C++, and 0 is the index of the first item in the inventory list.

Susan: How about using 99?<g>

Steve: What an original idea! That would work fine until we had 99 items; unfortunately, then we would have an Item100 problem.

  1. The EditByPartialName case displays a message telling the user what operation is being performed, asks the user to type in part of the name of the item to be edited, and calls the SelectItemByPartialName member function of the Inventory object to determine which inventory item the user wants to edit. The rest of this function is the same as the previous case, which makes sense because the only difference in the purpose of these two sections of code is how the user selects the item.

Susan, with her keen eye for detail, spotted a small discrepancy in this section of code, which led to the following discussion.

Susan: Why is the name of the case different from the name of the function?

Steve: That's a good question. It really should be the same, but I changed the program several times and forgot to resynchronize the two names. Because it doesn't affect the functioning of the program, I'm going to leave it as is.

  1. The EditByDescription case is exactly the same as the previous case, except that it asks the user for part of the description rather than the name and calls SelectItemFromDescriptionList, rather than SelectItemByPartialName, to select the item to be edited.

  2. The EditByCategory case is exactly the same as the previous two, except that it asks the user for the category name, rather than the item name or description, and calls SelectItemFromCategoryList to select the item to be edited.

  3. The DeleteItemFromNameList case is a bit different from the previous cases. It starts by allowing the user to select the item to be deleted from the entire inventory. Then it asks the user to confirm the deletion of that item, just to be sure that nothing gets deleted accidentally. Then it calls the DeleteItem member function of the Inventory object to do the actual deletion. Finally, it writes the changed inventory to the backup file.

Susan had a couple of comments about this case and its corresponding function.

Susan: You know, these long names look like German words: a whole bunch of words all strung together.

Steve: I do believe you're right!

Susan: Anyway, I like the confirmation. It's better to be safe rather than to accidentally delete an item when you didn't mean to.

Steve: I agree. Of course, we don't have a “mass delete” option, so an accident can't do unlimited damage, but I'd rather be safe than sorry.

  1. The PrintNames and PrintAll cases are much simpler than the previous ones because they don't allow the user to select which items will be included in the printed list. The user can print either the names of all the items in the inventory or all the data for all the items. Of course, even though the program is useful without a fancier printing capability, there might be occasions where printing data for part of the inventory would be very handy. Therefore, I've added an exercise to improve these facilities.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset