I am trying to learn Python / programming and have a question about Console style menus.
I was wondering if I am doing it the right way. About the menu: It is a simple console menu that just holds a main menu and a few menu "buttons" (numbers and titles which the user has to enter for the button to do its action). It does not need submenus or more complicated input handling.
I went with an object-oriented approach and defined three classes: Menu, Button, and Controller. The Menu initialises with a name and a list of menu buttons.
class Menu(object):
"""Base class for the menu"""
def __init__(self, name, buttons):
# Initialize values
self.name = name
self.buttons = buttons
def display(self):
"""Displaying the menu alongside the navigation elements"""
# Display menu name
print self.name
# Display menu buttons
for button in self.buttons:
print " ", button.nav, button.name
# Wait for user input
self.userInput()
def userInput(self):
"""Method to check and act upon user's input"""
# This holds the amount of errors for the
# navigation element to input comparison.
errSel = 0
inputSel = raw_input("Enter selection> ")
for button in self.buttons:
# If input equals to button's navigation element
if inputSel == str(button.nav):
# Do the button's function
button.do()
# If input != navigation element
else:
# Increase "error on selection" by one, for
# counting the errors and checking their
# amount against the total number of
# buttons. If greater to or equal that means
# no elements were selected.
# In that case show error and try again
errSel += 1
# No usable input, try again
if errSel >= len(self.buttons):
print "Error on selection; try again."
The Button class is accepting a name, a function and a navigation element (the number you have to press for it to do the button's action):
class Button(object):
"""Base class for menu buttons"""
def __init__(self, name, func, nav):
# Initialize values
self.name = name
# Function associated with button
self.func = func
# Navigation element; number which user has to enter to do button action
self.nav = nav
def do(self):
# Do the button's function
self.func()
Finally the Controller. Did I do it the right way? I mean it works but having that while loop cycling with only the raw_input()
function stopping it from spamming infinite messages - is that correct or should I write something that prevents the while loop while a menu was already displayed and is awaiting user input? Here is the code:
class Controller(object):
def __init__(self, menu):
# Initialize values
self.menu = menu
# Start menu displaying / cycling
self.cycle()
def cycle(self):
"""Method for displaying / cycling through the menus"""
while True:
# Display menu and redisplay after button's function completes
# Is this right? Will this loop correctly and never interfere
# with raw_input()?
self.menu.display()
Finally, the code that puts it all together:
import sys
def main():
mainMenuButtonName = Button("Show name", showName, 1)
mainMenuButtonVersion = Button("Show version", showVersion, 2)
mainMenuButtonAbout = Button("Show about", showAbout, 3)
mainMenuButtonQuit = Button("Quit", quit, 0)
mainMenuButtons = [mainMenuButtonName, mainMenuButtonVersion, mainMenuButtonAbout, mainMenuButtonQuit]
mainMenu = Menu("Main menu", mainMenuButtons)
controller = Controller(mainMenu)
controller.cycle()
def showName():
print "My name is..."
def showVersion():
print "My version is 0.1"
def showAbout():
print "I am a demo app for testing menus"
def quit():
sys.exit(0)
if __name__ == "__main__":
main()
EDIT:
I did some more coding and came up with a simpler solution and also solve the while loop problem. Does that seem correct to you? Basically I am setting another variable in the Display class, shouldCycle
. When the cycle begins, just before the user is asked for raw_input()
, it sets this variable to False and prevents the while loop. However, when the user has entered which "button" he wants to click, shouldCycle
gets set to True again by the button's function.
import sys
class Display(object):
"""
Base class for the menu
"""
def __init__(self, menu):
# Initialise values
self.menu = menu
# Switch to start / stop menu displaying process
# Set to True by default, will get switched to False
# in the display() method
self.shouldCycle = True
def display(self):
"""
Display the menu alongside the navigation elements
"""
while self.shouldCycle:
# Stop displaying process to prevent
# infinite messages
self.shouldCycle = False
# Display menu buttons
for button in self.menu:
print " ", button.nav, button.name
# Wait for user input
self.userInput()
def userInput(self):
"""
Method to check and act upon user's input
"""
# This holds the amount of errors for the
# navigation element to input comparison
errSel = 0
inputSel = raw_input("Enter selection> ")
for button in self.menu:
# If input equals to button's navigation element
if inputSel == str(button.nav):
button.do()
else:
# Increase "error on selection" by one, for
# counting the errors and checking their
# amount against the total number of
# buttons. If greater to or equal that means
# no elements were selected.
# In that case show error and try again
errSel += 1
# No usale input, try again
if errSel >= len(self.menu):
print "Error on selection; try again."
# Switch shouldCycle to True to
# display the menu again
self.shouldCycle = True
class Button(object):
"""
Base class for menu buttons
"""
def __init__(self, name, func, nav):
# Initialise values
self.name = name
# Function associated with button
self.func = func
# Navigation element; number which user has
# to enter to do button action
self.nav = nav
def do(self):
# Do button's action
self.func()
class App(object):
def __init__(self):
self.buttonVersion = Button("Show version", self.showVersion, 1)
self.buttonName = Button("Show name", self.showName, 2)
self.buttonQuit = Button("Quit", self.quit, 0)
self.buttons = [self.buttonVersion, self.buttonName, self.buttonQuit]
self.menu = Display(self.buttons)
def showVersion(self):
print "1.0"
self.menu.shouldCycle = True
def showName(self):
print "A name"
self.menu.shouldCycle = True
def run(self):
# Start menu displaying
self.menu.display()
def quit(self):
sys.exit(0)
if __name__ == "__main__":
app = App()
app.run()