Thread: Menu (any default style ? )

  1. #16
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    You should clear the error flags on std::cin after a failed read (e.g., the user entered letters instead of an integer) and ignore what remains on the buffer otherwise you risk an infinite loop.

    Do you prefer using enum or not ?
    I think they are fine for what you want to do.

    As an alternative, you could try implementing the menu via callback functions. You could create a menu item class that stores the description of the menu item and a function pointer to the function that you want to call if the user selects that option. A menu class could then store a list of menu items and perhaps a heading for the menu. The menu class could have a member function that displays the menu (in a loop, automatically numbering the menu items), handles user input, and then runs the callback function of the selected menu item.

    Once you have written these two classes, creating a menu would just involve creating a menu object and adding menu item objects to it. The tradeoff is that this is more initial work in implementing these two classes and you have to create a callback function for each menu option (but for a reasonably complex option, you would write a function for that anyway). On the other hand, you can just create menu objects in those functions so as to have sub-menus.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  2. #17
    Registered User
    Join Date
    Aug 2007
    Posts
    66
    You should clear the error flags on std::cin after a failed read (e.g., the user entered letters instead of an integer) and ignore what remains on the buffer otherwise you risk an infinite loop.
    How can I do this ?


    As an alternative, you could try implementing the menu via callback functions. You could create a menu item class that stores the description of the menu item and a function pointer to the function that you want to call if the user selects that option. A menu class could then store a list of menu items and perhaps a heading for the menu. The menu class could have a member function that displays the menu (in a loop, automatically numbering the menu items), handles user input, and then runs the callback function of the selected menu item.

    Once you have written these two classes, creating a menu would just involve creating a menu object and adding menu item objects to it. The tradeoff is that this is more initial work in implementing these two classes and you have to create a callback function for each menu option (but for a reasonably complex option, you would write a function for that anyway). On the other hand, you can just create menu objects in those functions so as to have sub-menus.
    This sounds very interesting. Although I am not used of pointers (it's the next chapter on my C++ book) I would like to see a source code that does this implementation. Could you or anyone else post it here ?

  3. #18
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Clearing input when failed:
    http://faq.cprogramming.com/cgi-bin/...&id=1043284392

    I've never written C++ classes to support menus, but it shouldn't be hard to do. Perhaps something like this:
    Code:
    #include <iostream>
    #include <string>
    
    
    class menucontext {
    public:
      bool fquit;
      menucontext() : fquit(false) { };
      ~menucontext() {};
    };
    
    typedef void (*menufunc)(menucontext &ctxt);
    
    
    class menuitem {
    private:
      std::string   descr;
      char          choice;
      menufunc      func;
    public:
      menuitem(const std::string &s, char c, menufunc f): 
        descr(s), choice(c), func(f) 
      {
      };
      ~menuitem() {
      };
      void print() { 
        std::cout << choice << ": " << descr << std::endl;
      };
      void domenu(menucontext &ctxt) {
        func(ctxt);
      };
      bool isChoice(char c) {
        return (c == choice);
      }
    };
    
       
    
    class menu {
    private:
      struct menunode {
        menunode *next;
        menuitem *item;
      };
      menunode *head;
      menunode *tail;
    public:
      menu() : head(0), tail(0) {
      }
      ~menu() {
        menunode *p, *q;
        for(p = head; p; p = q) {
          q = p->next;
          delete p->item;
          delete p;
        }
      };
      void addmenu(std::string s, char c, menufunc f) {
        menunode *node = new menunode;
        node->item = new menuitem(s, c, f);
        node->next = 0;
        if (!head)
          head = tail = node;
        else {
          tail->next = node;
          tail = node;
        }
      };
      void choose(menucontext &ctxt) {
        char c;
        menunode *p;
        bool choosen = false;
        for(p = head; p; p = p->next) {
          p->item->print();
        }
        do {
          std::cin >> c;
          for(p = head; p; p = p->next) {
    	if (p->item->isChoice(c)) {
    	  p->item->domenu(ctxt);
    	  choosen = true;
    	}
          }
          if (!choosen)
    	std::cout << "That wasn't a valid choice" << std::endl;
        } while(!choosen);
      };
    };
    
    
    void first(menucontext &ctxt)
    {
      std::cout << "In first" << std::endl;
    }
    
    void quit(menucontext &ctxt)
    {
      std::cout << "Quitting..." << std::endl;
      ctxt.fquit = true;
    }
    
    
    int main() {
      menu m;
    
      menucontext ctxt;
      m.addmenu("First menu", '1', first);
      m.addmenu("QUit",       'q', quit);
    
      while(!ctxt.fquit)
        m.choose(ctxt);
      return 0;
    }
    [That got a lot longer than I expected.]

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  4. #19
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    By the way, I guess it's a good idea to use some STL to make the list of menuitems, e.g. a list or vector.

    Code:
    #include <list>
    ...
    class menuitem {
    private:
      std::string   descr;
      char          choice;
      menufunc      func;
    public:
      menuitem(const std::string &s, char c, menufunc f): 
        descr(s), choice(c), func(f) 
      {
      };
      ~menuitem() {
      };
      void print() const { 
        std::cout << choice << ": " << descr << std::endl;
      };
      void domenu(menucontext &ctxt) const {
        func(ctxt);
      };
      bool isChoice(char c) const {
        return (c == choice);
      }
    };
    
       
    
    class menu {
    private:
      std::list<menuitem> menuitems;
    public:
      menu() {
      }
      ~menu() {
         menuitems.clear();
      };
      void addmenu(std::string s, char c, menufunc f) {
        menuitems.push_back(menuitem(s, c, f));
      };
      void choose(menucontext &ctxt) {
        char c;
        std::list<menuitem>::const_iterator i;
        bool choosen = false;
        for(i = menuitems.begin(); i != menuitems.end(); i++) {
          i->print();
        }
        do {
          std::cin >> c;
          for(i = menuitems.begin(); i != menuitems.end(); i++) {
    	if (i->isChoice(c)) {
    	  i->domenu(ctxt);
    	  choosen = true;
    	}
          }
          if (!choosen)
    	std::cout << "That wasn't a valid choice" << std::endl;
        } while(!choosen);
      };
    };
    The above is the changed code, marked in red.

    --
    Mats
    Last edited by matsp; 09-05-2007 at 04:23 AM.
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  5. #20
    Registered User
    Join Date
    Aug 2007
    Posts
    66
    Pretty cool! I think that I have not the skills to write something like this but I will (someday:P).

    in the link you gave me there is :

    Code:
    #include <iostream> 
    #include <cstdio> 
    
    using std::cin;
    using std::cout;
    using std::endl;
    
    int main(void)
    {
      int ch;
      char buf[BUFSIZ];
        
      cout <<"Flushing input" <<endl;
      
      while ((ch = cin.get()) != '\n' && ch != EOF);
      
      cout <<"Enter some text: ";
      cout.flush();
      
      if (cin.getline(buf, sizeof(buf)))
      {
        cout <<"You entered: " <<buf <<endl;
      }
    
      return 0;
    }
    
    /*
     * Program output:
     *
     Flushing input
     blah blah blah blah
     Enter some text: hello there
     You entered: hello there
     *
     */
    I didn't understand very well. Should I use
    cout.flush();
    under every cout command ?

    Am I forced to use the strange if conditions including EOF ? WTF ?

  6. #21
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    This line is the one that does the actual "eating of any garbage in the inptut".
    Code:
    while ((ch = cin.get()) != '\n' && ch != EOF);
    The purpose is mainly to avoid a whole stream of "invalid input" when you've entered "jkdfhukadshfjghawerihfj" because the cat decided to walk on the keyboard (or my 8m old daughter decided "to help daddy do typing"). When using numeric input (as you do when you read an enum), the input could get into a "stuck" state, in which case you also want to use cout.clear() to clear the error flag that has been set.

    And yes, it's a good idea to check for EOF - if you don't, things will sooner or later go into an infinite loop when reading something.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  7. #22
    Registered User
    Join Date
    Aug 2007
    Posts
    66
    Could you edit my code with this additional stuff ? I am comfused with this "buf"... Sorry if bothering, man.

  8. #23
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    When you see that someone made the wrong choice, just insert that line I posted two posts ago - that will read anything "still waiting to be processed" up to the "enter".

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  9. #24
    Registered User
    Join Date
    Aug 2007
    Posts
    66
    Code:
    #include <iostream>
    
    using namespace std;
    
    enum CHOICE  {
         Wake = 1,
         Bath,
         Toilet,
         Hack,
         Quit   };
    
    // Function Definition and Prototype
    
    int DoMenu()
    {
        int choice;
        
        cout << endl << endl;
        cout << " ***** Menu ***** " <<endl;
        cout << "(1) Wake me up when project ends" << endl;
        cout << "(2) GOTO Bathroom" << endl;
        cout << "(3) fflussh(toilet)" << endl;
        cout << "(4) Hack the kernel" << endl;
        cout << "(5) Quit" << endl;
        
        cout << "Please select :";
        cin >> choice;
        cin.ignore(choice);
     while (choice > Quit || choice < Wake) 
    {
        while ((choice = cin.get()) != '\n' && ch != EOF);
          cout << "You wrong! ";
          cout << "Retype: ";
          cin >> choice;
    }
        return choice;
    }
    
    int main()
    {
        int choice;
        bool fQuit = false;
        
        while (!fQuit)
        {
              char  reSelect;
              choice = DoMenu();
          
              switch(choice)
              {
                            case Wake:
                                 cout << "Drinsane!!! " << endl;
                                 break;
                            case Bath:
                                 cout << "Please wait while bathing ... " << endl;
                                 cout << "You smell sehr gut" << endl;
                                 break;
                            case Toilet:
                                 break;
                            case Hack:
                                 break;
                            case Quit:
                                 fQuit = true;
                                 cout << "\nExiting .... " << endl << endl;
                                 break;
                            default:
                                     fQuit = true;
                                 cout << "\nExiting .... " << endl << endl;
                                 break;
              }
            
       
              
    }        
              
        system("PAUSE");
        return 0;
    }
    errors....

  10. #25
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    You haven't declared the variable ch.
    You probably don't want to pass choice to cin.ignore();

    The "reselect" variable in main is never used.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  11. #26
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    This block of code:
    Code:
    cout << "Please select :";
    cin >> choice;
    cin.ignore(choice);
    while (choice > Quit || choice < Wake) 
    {
        while ((choice = cin.get()) != '\n' && ch != EOF);
        cout << "You wrong! ";
        cout << "Retype: ";
        cin >> choice;
    }
    Should be:
    Code:
    cout << "Please select :";
    while (!(cin >> choice) || choice > Quit || choice < Wake)
    {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        cout << "You wrong! Retype: ";
    }
    While the read fails, or if the input is out of range, you inform the user of an error and request for more input. The cin.clear() clears the error flags after a failed read. The cin.ignore() removes remaining characters on the buffer up to and including the new line character. As far as I know, you do not need to worry about EOF here since the user has to press enter to enter input, thus placing a new line on the input buffer. The numeric_limits<streamsize>::max() returns the maximum size of a stream, but you need to #include <limits> to use it.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Checking array for string
    By Ayreon in forum C Programming
    Replies: 87
    Last Post: 03-09-2009, 03:25 PM
  2. WM_CAPTION causing CreateWindowEx() to fail.
    By Necrofear in forum Windows Programming
    Replies: 8
    Last Post: 04-06-2007, 08:23 AM
  3. Problem with Mouse Over Menu!!! HELP!!!
    By SweeLeen in forum C++ Programming
    Replies: 3
    Last Post: 02-09-2006, 02:10 AM
  4. Menu stuff
    By Shadow in forum C Programming
    Replies: 10
    Last Post: 04-28-2002, 09:05 AM