Thread: Elegant console based menus

  1. #1
    Registered User Casey's Avatar
    Join Date
    Jun 2003
    Posts
    47

    Elegant console based menus

    What I've come up with so far are two classes, one that makes and shows a console based menu and one that handles the actual options and actions mapped to those options. But I can't figure out a simple and elegant way to allow functions with different parameters and return types. Here's what I have so far
    Code:
    #include <iostream>
    #include <cstdlib>
    #include <string>
    #include <list>
    #include <map>
    
    using namespace std;
    
    // Create and display a console based menu
    class ConsoleMenu {
    public:
        ConsoleMenu(string omsg): msg(omsg){};
        void add(string opt) { options.push_back(opt); }
        ostream& display();
    private:
        string       msg;
        list<string> options;
    };
    
    ostream& ConsoleMenu::display()
    {
        list<string>::const_iterator it = options.begin();
    
        while (it != options.end())
            cout<< *it++ <<'\n';
    
        return cout<< msg <<flush;
    }
    
    // Map menu options to functions
    class MenuOptions {
    public:
        void add(int opt, void (*fn)()) { actions[opt] = fn; }
        bool getOpt();
        void run() { actions[option](); }
    private:
        int                  option;
        map<int, void (*)()> actions;
    };
    
    bool MenuOptions::getOpt()
    {
        if (cin>>option && actions.find(option) != actions.end())
            return true;
        
        if (!cin.good())
        {
            cin.clear();
            
            int ch;
            while ((ch = cin.get()) != '\n' && ch != EOF)
                ;
        }
    
        return false;
    }
    
    void a() { cout<<"\nOption 1\n"<<endl; }
    void b() { cout<<"\nOption 2\n"<<endl; }
    void c() { exit(0); }
    
    int main()
    {
        ConsoleMenu menu("Choose an option: ");
        MenuOptions mopt;
    
        menu.add("1) Option 1");
        menu.add("2) Option 2");
        menu.add("3) Quit");
    
        mopt.add(1, a);
        mopt.add(2, b);
        mopt.add(3, c);
    
        while (1)
        {
            menu.display();
    
            if (mopt.getOpt())
                mopt.run();
            else
                cerr<<"\nInvalid option\n"<<endl;
        }
    }
    What I can't figure out is how to have different types of functions for each call to run() without losing the really simple interface I have. Has anyone encountered a problem like this before? If so, what was your solution?

  2. #2
    End Of Line Hammer's Avatar
    Join Date
    Apr 2002
    Posts
    6,231
    Here's a couple of options.

    Using void pointers:
    Code:
    #include <iostream>
    using namespace std;
    
    struct MyStruct
    {
      char name[10];
    };
      
    int foo(void *vp)
    {
      int i = *(int*)vp;
      cout <<"in foo, i is " <<i <<endl;
      return 1;
    }
    
    int bar(void *vp)
    {
      MyStruct *data = (MyStruct*)vp;
      cout <<"in bar, m is " <<data->name <<endl;
      return 1;
    }
    
    int main()
    {
      int (*fp)(void *);
      MyStruct m = {"Hammer"};
      int i = 1;
      
      fp = foo;
      fp(&i);
      
      fp = bar;
      fp(&m);
      
      return 0;
    }
    
    /*
     * Output
     in foo, i is 1
     in bar, m is Hammer
     *
     */
    Using templates:
    Code:
    #include <iostream>
    using namespace std;
    
    template <typename T>
    int foo (T data)
    {
      cout <<"in foo, data is " <<data <<endl;
      return 1;
    }
    
    int main(void)
    {
      foo (11);
      foo ("Hammer");
    }
    
    /*
     * Output
     in foo, data is 11
     in foo, data is Hammer
     *
     */
    And here's one more for luck:
    Code:
    #include <iostream>
    #include <cstdarg>
    
    using namespace std;
    
    int LogMessage(char *fmt, ...)
    {
      va_list arglist;
      va_start(arglist, fmt);
      vfprintf(stderr, fmt, arglist);
      va_end(arglist);
      return 1;
    }
    
    int main(void)
    {
      LogMessage ("Message:%d %s ", 11, "Serious error" );
      LogMessage ("Hammer");
    }
    When all else fails, read the instructions.
    If you're posting code, use code tags: [code] /* insert code here */ [/code]

  3. #3
    Registered User Casey's Avatar
    Join Date
    Jun 2003
    Posts
    47
    That was pretty much all I could come up with as well. All of them are pretty awkward with how I was doing it, so I scrapped the idea of the object handling such things. Here's what I have now.
    Code:
    #include <iostream>
    #include <cstdlib>
    #include <string>
    #include <list>
    #include <algorithm>
    
    class ConsoleMenu {
    public:
        // Generic prompt for input
        ConsoleMenu(): msg("Select an option: "){}
        // Custom prompt, just in case
        ConsoleMenu(std::string omsg): msg(omsg){}
    
        // The order of 'values' doesn't matter, 'options' does.
        void add(int val, std::string opt)
        {
            values.push_back(val);
            options.push_back(opt);
        }
    
        // For switch statements and the like
        int  opt() { return option; }
        // Interactive input
        bool selection();
    
        // Return ostream& for linked output (e.g. obj.display()<<endl;)
        std::ostream& display();
    private:
        int                    option;  // Interactive input option
        std::string            msg;     // Prompt message for input
        std::list<std::string> options; // List of printable options
        std::list<int>         values;  // List of valid option values
    };
    
    std::ostream& ConsoleMenu::display()
    {
        std::list<std::string>::const_iterator it = options.begin();
    
        while (it != options.end())
            std::cout<< *it++ <<'\n';
    
        // Be sure to flush the stream
        return std::cout<< msg <<std::flush;
    }
    
    bool ConsoleMenu::selection()
    {
        if (std::cin>>option &&
            std::find(values.begin(), values.end(), option) != values.end())
        {
            return true; // Good
        }
        
        // cin's cleanup is ugly
        if (!std::cin.good())
        {
            std::cin.clear();
            
            int ch;
            while ((ch = std::cin.get()) != '\n' && ch != EOF)
                ;
        }
    
        return false; // Bad
    }
    
    using namespace std;
    
    void a() { cout<<"\nOption 1\n"<<endl; }
    void b() { cout<<"\nOption 2\n"<<endl; }
    void c() { exit(0); }
    
    int main()
    {
        ConsoleMenu menu;
        void (*action[])() = {a, b, c};
    
        menu.add(1, "1) Option 1");
        menu.add(2, "2) Option 2");
        menu.add(3, "3) Quit");
    
        while (1)
        {
            menu.display();
    
            if (menu.selection())
                action[menu.opt()-1]();
            else
                cerr<<"\nInvalid option\n"<<endl;
        }
    }
    If the situation warranys an array of function pointers then that's good, otherwise the traditional switch/case works too. Comments/suggestions?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. CreateProcess and console handles
    By manannan in forum Windows Programming
    Replies: 1
    Last Post: 11-12-2005, 02:17 AM
  2. Full Screen Console
    By St0rmTroop3er in forum C++ Programming
    Replies: 1
    Last Post: 09-26-2005, 09:59 PM
  3. Problems with a simple console game
    By DZeek in forum C++ Programming
    Replies: 9
    Last Post: 03-06-2005, 02:02 PM
  4. Console Functions Help
    By Artist_of_dream in forum C++ Programming
    Replies: 9
    Last Post: 12-04-2004, 03:44 AM
  5. Output to console window...
    By alvifarooq in forum C++ Programming
    Replies: 2
    Last Post: 10-26-2004, 08:56 PM