5

How can one create menus in the command line program? I've tried stuff like:

cin >> input;
switch (input) {
  case (1):
    // do stuff
  case (2):
    // ...
}

but then I've had the problem of sub-menus, and going back to the same menu, etc. The first program I wrote (apart from exercises) that tried to use the switch idea for the menus had goto statements because the alternative was heaps of (at the time) complicated loops.

Miles Rout
  • 1,204
  • 1
  • 13
  • 26

3 Answers3

9

If I tried to count the ways in which one might create a 1,2,3 menu, we'd both be dead before I'd iterated 1/2 of them. But here's one method you could try to get you started (untested, you may need to clean up a couple things):

struct menu_item
{
  virtual ~menu_item() {}
  virtual std::string item_text() const = 0;
  virtual void go() = 0;
};

struct print_hello_item
{
  std::string item_text() const { return "display greeting"; }
  void go() { std::cout << "Hello there, Mr. User."; }
};

struct kill_everyone_item
{
  std::string item_text() const { return "Go on murderous rampage"; }
  void go() { for(;;) kill_the_world(); }
};

struct menu_menu_item
{
  menu_menu_item(std::string const& text) : text_(text), items() {}
  void add_item(std::unique_ptr<menu_item> item) { items.push_back(std::move(item)); }
  void go()
  {
    std::cout << "Choose: \n";
    std::for_each(items.begin(), items.end(), [](std::unique_ptr<menu_item> const& item)
    {
      std::cout << "\t" << item->item_text() << "\n";
    });
    std::cout << "\n\n\tYour choice: ";
    int choice = get_number_from_console();
    if (items.size() > choice) items[choice]->go();
  }
  std::string item_text() const { return text_; }

private:
  std::string text_;
  std::vector<std::unique_ptr<menu_item> > items;
};

int main()
{
  menu_menu_item top_item;
  top_item.add(std::unique_ptr<menu_item>(new print_hello_item));
  top_item.add(std::unique_ptr<menu_item>(new kill_everyone_item));

  top_item.go();
}

As an exercize, how might I define menu items like so:

top_level.add()
  ( "Drive off a cliff", &die_function )
  ( "Destroy the world", &global_thermal_nuclear_war )
  ( "Deeper", submenu()
                ( "Hey, check this shit out!", &gawk ))
;

It can be done with the above framework as a starting point.

This is the difference between OO design and what might be called "procedural". I created an abstraction behind what it means to be a menu choice (which can be another menu) that can be extended in various directions. I create the extensions I need, put them together, and tell the thing to go. Good OO design is just like that...the main part of your program is comprised of putting stuff together and telling it to go.

The key thing to take from this is not necessarily to do it the way I just did, but to think about it in a different way. If you can get the gist of the above code then you'll see that you can add new items, with new menus, to arbitrary depths, without having to deal with the kind of overly complicated code that the switch style causes.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • It is certainly preferable to any switchy way. Yes, you could certainly extend it with some sort of file format reader so that the menu was built from external input too. – Edward Strange Jan 14 '11 at 05:12
  • std::struct in kill_everyone_item should be replaced with std::string – Septagram Jan 14 '11 at 05:27
  • 1
    @Miles: Actually, `for(;;)` is an infinite loop. Also, there are some C++0x things in here that may not be in your compiler yet. – Fred Larson Jan 14 '11 at 05:34
  • I would have thought that while(1) was more readable, but meh. What C++0x stuff is in there? – Miles Rout Jan 14 '11 at 05:38
  • There are arguments elsewhere on SO about `for(;;)` vs. `while(true)`. Lambda functions, std::move(), and unique_ptr are all C++0x. – Fred Larson Jan 14 '11 at 05:45
  • Yeah, I wasn't going to write out a sample framework without using some C++0x constructs...too much work. Most up-to-date compilers support everything I used, but none of it is strictly necessary...was just quicker to make that way (which is of course why they're being added to begin with). – Edward Strange Jan 14 '11 at 05:48
2

You can integrate submenus in your menu with your method:

cin >> input;
switch (input) {
  case (1): 
    cin >> input;
    switch (input) {
       case (1): //do stuff
       case (2): //do stuff
    }
    break;
  case (2):
    break;
}

Is this what you're looking for? Otherwise: What do you want to solve exactly?

Edit: So what you need is an additional loop in your sub-menus with an break condition?

do{
    cin >> input;
    switch (input) {
      case (1): 
        do{
          cin >> input;
          switch (input) {
            case (1): //do stuff
            case (2): //do stuff
          }
        }while(input != 3);
        break;
      case (2):
        break;
    }
  }while(true);
Constantin
  • 8,721
  • 13
  • 75
  • 126
  • In the context of the menu you give, I was hoping that once the user completes an action (such as the first do stuff, it would go back to the submenu, and that there would be an option to go back to the first menu. Does that make sense? – Miles Rout Jan 14 '11 at 04:56
  • Obvious input isn't sanitised, however this question isn't about sanitisation, it's about the switch statement I've used and alternatives or improvements. – Miles Rout Jan 14 '11 at 05:37
  • Yes, but I've seen this same thing done in programs at work. It's no fun if you fat finger something and then get your menu printing out in an infinite loop. – Fred Larson Jan 14 '11 at 05:39
  • No sanitisation of input in a production tool? That's good design. Looool. – Miles Rout Jan 14 '11 at 05:40
  • Not production, just in-house utilities. Still not cool, though. – Fred Larson Jan 14 '11 at 05:46
  • Hmm? If you enter non-numeric characters, you are simply prompt to enter a new value - the same like if you enter an numeric value, that isn't included in the switch-case statement. Wheres your problem? – Constantin Jan 14 '11 at 05:51
  • Oh well that's quite awesome then. – Miles Rout Jan 14 '11 at 05:54
  • @Constantin: Did you try it? I did. The non-numeric input is not consumed by the `cin >> input`, so it gets used again the next time around -- forever. I've seen this happen lots of times. – Fred Larson Jan 14 '11 at 05:58
  • @Fred: If you get an infinite loop, that depends on your own declaration of the 'input' variable. Please be aware, that cin isn't type-safe for integer! – Constantin Jan 14 '11 at 06:06
  • Could you use `cin << (int) input` – Miles Rout Jan 14 '11 at 07:28
  • @Constantin: What type would you use? `std::string` would come closer, but you can't `switch` on it. And that doesn't necessarily consume the buffer either. I think you need to use `getline` and then convert to an integer. – Fred Larson Jan 14 '11 at 16:04
  • My main issue is trying to use OOP to eliminate the `if-else-if` and `switch` structures as well as nesting them for submenus. Now the control logic is moved from main() to the menu()'s "read input" function. – Krista K Sep 06 '13 at 05:18
1

I just asked a similar question at Libraries for displaying a text-mode menu? -- it looks like ncurses will dramatically simplify the menu-displaying parts.

Community
  • 1
  • 1
ExOttoyuhr
  • 667
  • 1
  • 8
  • 21
  • 1
    imho, ncurses isn't a great answer, as its menus are not OOP and instead implement `*` and `**` pointer based linked lists. I'm on a similar search for a menu, nav, control class/library and am trying to not do YAIEI or YAPBLL. Yet another (if-else-if | pointer based linked list) – Krista K Sep 06 '13 at 05:20
  • I agree. I tried to use the Curses menu functionality, but gave up and wrote my own (C++-style, STL-using) version instead. I'll add that to the question I asked. – ExOttoyuhr Sep 13 '13 at 20:01
  • I gave up on OOP. :( I kinda feel like a fail, but I've got to get back to actually working and this week-long venture trying OOP was enough. I just can't pull the pieces together in my brain. I took this struct templating example from 1995 and it does what I want. http://dec.bournemouth.ac.uk/staff/awatson/micro/articles/9509_066.pdf Sample is in my Github. – Krista K Sep 15 '13 at 00:41