Re: menu-based console application
From: Karl Heinz Buchegger (kbuchegg_at_gascad.at)
Date: 07/22/04
- Next message: Francis Glassborow: "Re: Should I migrate from C to C++?"
- Previous message: Alwyn: "Re: Should I migrate from C to C++?"
- In reply to: Roberto Dias: "menu-based console application"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Thu, 22 Jul 2004 13:14:56 +0200
Roberto Dias wrote:
>
> Could anyone give me some tips about how to start a menu-based console
> application using C++ function oriented programming techniques?
You could eg. start with nailing down some requirements.
Eg.
A menu consists of menuentries
A menuentry consists of
a trigger (such as a character the user has to enter)
a text
some action to perform
So what about the action to perform? The simplest thing is to
assume, that the action has to be encapsulated into a single function
thus storing an action in a menu is equivalent to storing a pointer
to a function. In order to work with function pointers, a typedef
is almost always a good idea.
So what do we have right now
typedef void (*ActionFnct)();
struct MenuEntry
{
char m_Trigger;
std::string m_Text;
ActionFnct m_Action;
};
class Menu
{
std::vector< MenuEntry > m_Entries;
};
Fine. In order to compile this, you need some includes, namely
#include <string>
#include <vector>
Now that we know what we want to store in order to create a menu, we should
spend some thoughts about what functionality a Menu should have. Clearly there
should be an easy way to create MenuEntries in it and also a function for
showing the Menu, handling the user input and performing the action (that
is call the specified function) would be a good idea. There should also
be thoughts about: Can all actions be performed by calling a function? If
you think of it, then the answer will be: no. The action of: return to
previous menu hierarchy (or exit the program) has no such thing. It could
be represented by having a NULL function pointer and executing a simple
return.
So lets see how this develops:
class Menu
{
public:
void Add( const MenuEntry& Entry ) { m_Entries.push_back( Entry ); }
void Perform();
private:
std::vector< MenuEntry > m_Entries;
};
Hmm Menu::Add proposes to take a MenuEntry. Thus it would be convinient if
one could generate such an entry 'on the fly' in order to be able to do something
like this
MyMenu.Add( MenuEntry( '1', "Show help", help ) );
For this MenuEntry will need a constructor which takes the arguments and creates a
MenuEntry Object from it:
struct MenuEntry
{
MenuEntry( char Trigger, std::string Text, ActionFnct Action )
: m_Trigger( Trigger ),
m_Text( Text ),
m_Action( Action )
{}
char m_Trigger;
std::string m_Text;
ActionFnct m_Action;
};
There is enough for it right now, that a first version should be compiled and
feed through the compiler, to see if there are syntax errors. For this you write
void help()
{
std::cout << "This is help" << std::endl;
}
int main()
{
Menu MainMenu;
MainMenu.Add( MenuEntry( '1', "Show help", help ) );
MainMenu.Add( MenuEntry( '2', "Quit", NULL ) );
}
Compile everything, fix typing errors and run the program. Nothing should
happen right now, but we'll change that. Time to think about the functionality
of Menu::Perform(). What does it have to do
* show all menu entries
* wait for the user to enter a response
* validate the response
* perform the action, if any
Lets start with the first part, show all menu entries. Thats easy. It is
a loop through the m_Entries vector and in turn output all the entries.
Something like
void Menu::Perform()
{
for( int i = 0; i < m_Entries.size(); ++i ) {
std::cout << m_Entries[i].m_Trigger << '\t'
<< m_Entries[i].m_Text
<< std::endl;
}
}
What about the next part? Wait a minute. The above can be tested and should
be tested, just to make sure.
So go ahead, insert a call to MainMenu.Perform() into main(), compile it and
let the program run. I am waiting for you to.
Compiles? Runs? Outputs the expected text?
Fine.
Wait for the user to respond can be done in various flavours. I'm doing the
simplest thing right now and read what the user provides in a single string
and just take the first character from what the user has entered.
Your milage may be different (and after all it is your project by the way).
void Menu::Perform()
{
for( int i = 0; i < m_Entries.size(); ++i ) {
std::cout << m_Entries[i].m_Trigger << '\t'
<< m_Entries[i].m_Text
<< std::endl;
}
std::string Response;
std::getline( std::cin, Response );
if( Response.length() > 0 ) {
char Trigger = Response[0];
}
}
This is something that can and should be tested again. For this I insert
some test code, that will vanish after the test:
void Menu::Perform()
{
for( int i = 0; i < m_Entries.size(); ++i ) {
std::cout << m_Entries[i].m_Trigger << '\t'
<< m_Entries[i].m_Text
<< std::endl;
}
std::string Response;
std::getline( std::cin, Response );
if( Response.length() > 0 ) {
char Trigger = Response[0];
std::cout << "You entered '" << Trigger<< "'" << std::endl;
}
}
You know the game right now: compile, run, check if it behaves as expected.
OK. Now that we have the response, it is time to look up the MenuEntries
if one of them matches and perform the action (if any).
void Menu::Perform()
{
for( int i = 0; i < m_Entries.size(); ++i ) {
std::cout << m_Entries[i].m_Trigger << '\t'
<< m_Entries[i].m_Text
<< std::endl;
}
std::string Response;
std::getline( std::cin, Response );
if( Response.length() > 0 ) {
char Trigger = Response[0];
for( int i = 0; i < m_Entries.size(); ++i ) {
if( m_Entries[i].m_Trigger == Trigger ) {
std::cout << "You selected '" << m_Entries[i].m_Text << "'" << std::endl;
if( m_Entries[i].m_Action )
m_Entries[i].m_Action();
else
return;
}
}
}
}
Compile it, run it.
Select the first menu item by pressing '1', 'return'
You should see the confirmation of what you have entered
(this time in form of repeating the menu text) and function
help should be called which outputs some text on its own.
After that the program finishes :-) Well. It might be a good
idea to put some sort of loop into Perform():
void Menu::Perform()
{
while( 1 ) {
for( int i = 0; i < m_Entries.size(); ++i ) {
std::cout << m_Entries[i].m_Trigger << '\t'
<< m_Entries[i].m_Text
<< std::endl;
}
std::string Response;
std::getline( std::cin, Response );
if( Response.length() > 0 ) {
char Trigger = Response[0];
for( int i = 0; i < m_Entries.size(); ++i ) {
if( m_Entries[i].m_Trigger == Trigger ) {
std::cout << "You selected '" << m_Entries[i].m_Text << "'" << std::endl;
if( m_Entries[i].m_Action )
m_Entries[i].m_Action();
else
return;
}
}
}
}
}
Now what if there is another menu in help().
Lets see:
void general()
{
std::cout << "This is general help\n";
}
void specific()
{
std::cout << "This is specific help\n";
}
void help()
{
Menu HelpMenu;
HelpMenu.Add( MenuEntry( 'a', "General help", general ) );
HelpMenu.Add( MenuEntry( 'b', "Specific help", specific ) );
HelpMenu.Add( MenuEntry( '9', "Return", NULL ) );
HelpMenu.Perform();
}
int main()
{
Menu MainMenu;
MainMenu.Add( MenuEntry( '1', "Show help", help ) );
MainMenu.Add( MenuEntry( '2', "Quit", NULL ) );
MainMenu.Perform();
}
Again: Test it. Note especially how the menus stack up as
you select '1' at the main menu
I'l stop right now. Just wanted to show you a simple way it can
be done.
-- Karl Heinz Buchegger kbuchegg@gascad.at
- Next message: Francis Glassborow: "Re: Should I migrate from C to C++?"
- Previous message: Alwyn: "Re: Should I migrate from C to C++?"
- In reply to: Roberto Dias: "menu-based console application"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|