Christopher Bui
Christopher Bui

Reputation: 13

Console Application Menu with Classes C++

I am currently working on a personal project in C++, and I'm having a lot of fun learning. I just learned some ideas on using inheritance and virtual functions. I decided to create a Budget Tracking console application! Of course, I could use control statements, and switches; however, I wanted to try and solve this using classes. Here is my code so far:

Main.cpp

#include <iostream>
#include "MainMenu.h"
#include "Menu.h"

int main() {

std::cout << "Welcome to BudgetTracker!\n" <<
            "Let's get started!\n\n";

Menu *menu = new MainMenu;
int mainInput;

menu->displayMenu();
std::cin >> mainInput;
menu->input(mainInput);

//Somehow switch my menu to a sub menu
//Add a loop without terminating entirely

}

MainMenu.cpp

#include "MainMenu.h"

MainMenu::MainMenu(){}
MainMenu::~MainMenu(){}


void MainMenu::displayMenu() {
std::cout   <<  "Welcome to the MainMenu! \n" <<
                "Select an option from the following: \n" <<
                "1.....Overall View\n" <<
                "2.....Accounts\n" <<
                "3.....Spending\n" <<
                "4.....Statistics\n" <<
                "5.....Budgeting\n" <<
                "0.....Close Program\n\n";
}

void MainMenu::input(int userInput) {
    m_input = userInput;
    std::cout << "You have entered " << m_input << ".\n\n";
    //some code to enter a different submenu
}

In the end, I imagine a user entering, something along the lines of while in mainMenu -> input 2. Now in accountMenu -> input 3 to manage accounts. Input 2-> add account.

Can you guys help me find a way to implement this idea? I saw something in regards to a composite pattern design; however, I felt as though it doesn't match this idea. Am I wrong? Is this a wrong approach?

Thanks in advance!

Upvotes: 1

Views: 6653

Answers (2)

Thomas Matthews
Thomas Matthews

Reputation: 57743

For menus, I recommend using state tables (data driven software):

// Define short hand for function pointer
typedef void (*Menu_Processing_Function_Ptr)();

struct Menu_Item
{
    unsigned int  number;
    const char *  text;
    Menu_Processing_Function_Ptr p_processing_function;
};

Given the above menu item structure, you can create a generic engine that processes {any} menu. A menu would be an array of Menu_Item.

void Menu_Engine(Menu_Item * p_menu, unsigned int item_quantity)
{
  // Display the menu
  for (unsigned int i = 0; i < item_quantity; ++i)
  {
    std::cout << p_menu[i].number
              << ". "
              << p_menu[i].text
              << "\n";
  }
  std::cout << "Enter selection: ";
  unsigned int selection;
  std::cin >> selection;
  for (unsigned int i = 0; i < item_quantity; ++i)
  {
     if (selection == p_menu[i].number)
     {
        // Execute the processing function for the selection.
        (p_menu[i].p_processing_function)();
        break;
     }
  }
  if (i >= item_quantity)
  {
     std::cout << "invalid selection\n";
  }
}

You can then define a menu as:

//  Forward declarations
void Process_Selection_1();
void Process_Selection_2();
void Process_Selection_3();

Menu_Item first_menu[] = 
{
    {1, "First Selection", Process_Selection_1},
    {2, "Second Selection", Process_Selection_2},
    {3, "Third Selection", Process_Selection_3},
};
static const unsigned int menu_size =
    sizeof(first_menu) / sizeof(first_menu[0]);

An example usage:

int main()
{
   // Process the menu
   Menu_Engine(&first_menu[0]);

   // Pause.
   std::cout << "Paused. Press ENTER to continue.";
   std::cin.ignore(1000000, '\n');

   return 0;
}

This pattern allows you to change the menu without having to retest or change the code (the engine). Adding a selection or modifying a selection does not affect the engine.

This allows for the menu to be placed into read-only memory.

You will only need one engine to process multiple menus.

Edit 1: std::map
Another possibility is to use an associative array, mapping the selection number to the selection attributes:

struct Menu_Item_Attributes
{
  const char * text;
  Menu_Processing_Function_Ptr p_processing_function;
};
typedef std::map<unsigned int, Menu_Item_Attributes> Menu_Item_Container;

Your input processing would be:

unsigned int selection;
std::cout << "Enter selection: ";
std::cin >> selection;
try
{
   Menu_Item_Attributes attributes = menu.at(selection);
   (attributes.p_processing_function)();
}
catch (std::out_of_range& e)
{
  std::cout << "Invalid selection";
}

Reminder that the std::map cannot be stored in read-only memory and would need to be initialized during run-time.

Upvotes: 2

Nicolas
Nicolas

Reputation: 177

It seems to me that what you need is State pattern : https://en.wikipedia.org/wiki/State_pattern

Upvotes: 1

Related Questions