Console Menu Generator in Python - python

As the title says, I'm writing a Console Menu Generator in Python. I have 2 classes, Menu and Item. But I get into troubles. Here is the code:
class Menu:
def AddItem(self,item):
class Item:
def __init__(self,text,ToDoNext):
self.text=text
??????????????
self.item.append(Item())
def Show():
for i in range(len(self.item)):
print(str(i+1)+") "+str(self.item[i])+"\n")
print("0) Back\n")
option=int(input())
self.item[option].????????????
This code basically do the next:
Main=Menu()
Menu.AddItem("Open file",ToDo1)
Menu.AddItem("Save file",ToDo2)
Menu.Show()
'''1) Open file
2) Save file
0) Back
_
'''
If I write 1 and press enter should do the portion of code ToDo1, for example.
The solution that I thought is the nextone:
def ToDo1():
print("Hello, world!")
Menu.AddItem("Say Hello","ToDo1()")
and use an eval() function inside the Show().
But I'm not pretty sure this is not the correct way to do that.
I would like you to show me a better way, and if you have ever do something like that (Console Menu Generator) to share the code and see another way of doing the same.

I absolutely recommend creating a class Item, even if you only have text and function attributes!
Who knows what kind of complex logic you will need later on.
With this in mind, creating a menu would probably look something like this:
main = Menu()
main.AddItem(Item("Open", openFile))
main.AddItem(Item("Close", closeFile))
Also, on top of your text and function attributes, you should add parent attribute to the Item class. parent simply points at the parent menu of our item:
main = Menu()
# automatically calls main.AddItem(item1)
open = Item("Open", openFile, main)
# automatically sets parent to main
main.Add(Item("Close", closeFile))
Now that we know how a proper Menu and Item should work, we can start coding the classes.
Menu
This shouldn't be too hard, all we need are add_item(), remove_item() and draw() methods and a list of items.
Also it would be good to draw our menu's name, so lets add name attribute.
class Menu:
def __init__(self, name, items=None):
self.name = name
self.items = items or []
def add_item(self, item):
self.items.append(item)
if item.parent != self:
item.parent = self
def remove_item(self, item):
self.items.remove(item)
if item.parent == self:
item.parent = None
def draw(self):
print(self.label)
for item in self.items:
item.draw()
Obviously we could code much more methods and attributes for our menu, but that includes all the essential methods.
Item
Item class should be even easier, it hardly needs any methods at all.
Item obviously needs a name and a function (function will be ran when item gets activated), on top of that it has the earlier mentioned parent attribute.
We probably should create a setter for parent, which would automatically move the item under an other menu, but I'll leave that for you if you want to do it.
Also don't forget the draw()-method for item too, we must be able to draw our items the way they want to be drawn, not the way our Menu wants to draw them.
class Item:
def __init__(self, name, function, parent=None):
self.name = name
self.function = function
self.parent = parent
if parent:
parent.add_item(self) # use add_item instead of append, since who
# knows what kind of complex code you'll have
# in add_item() later on.
def draw(self):
# might be more complex later, better use a method.
print(" " + self.name)
Final thoughts
We've now finished our menu, it works and you should be able to use it as a basic menu.
However, the superior console menu would only have one class called MenuItem.
Each item's parent would be an other MenuItem (each, but the root MenuItem's of course) and the menu would look something like this when it's drawn:
[-] Root
[+] Submenu of Root
[-] An other submenu of Root
This menu runs functions, others open/close
<This menu has focus on it>
Select this menu by pressing mousedown
[+] A third submenu of Root
Where not giving function parameter would create items list and allow users to close/open the menuitem.
If function is given, it will work normally and only execute the function when selected.
To go even a step further, we would have separated MenuItem and two subclasses: ActionMenu and ContainerMenu.
But please keep in mind, this is somewhat hard to code and not for beginners. You might wanna stick with the first version I went through.

Functions can be passed around freely in Python. If you say AddItem("Say Hello", ToDo1), you pass the value ToDo1, which is a function object. You can then store it in self.function, later fish it with fn = self.item[option].function, and then call it with fn(). It's all clearer when you realize that a regular function call like do_stuff() is actually two things: first getting the function object from the variable do_stuff (which is typically a never-modified global variable), then calling this function object.

Here is a working example
usually the Class Menu part would be in another file called "myMenu" and imported with the command from myMenu import myMenu
items is an array of dictionaries. Each list item has a dictionary with two entries, "text" and "func"
The input is called as n-1 as arrays start at zero
import sys
class myMenu:
items=[]
def AddItem(self,text,function):
self.items.append({'text': text, 'func':function})
def Show(self):
c=1
for l in self.items:
print c, l['text'],"\n"
c = c +1
def Do(self,n):
self.items[n]['func']()
def clist():
print "cheeses are wensleydale and cheddar\n"
def bye():
print "bye"
sys.exit(0)
if __name__ == "__main__":
m=myMenu()
m.AddItem("cheese",clist)
m.AddItem("quit",bye)
while(True):
m.Show()
n=input("choice>")
m.Do(n-1)
If you want to have the menu items as a class instead of a dictionary, then declare the class immediately after class MyMenu, so something like this (not tested)
class myMenu:
items=[]
class Item:
func=None
text="default"
def __init__(self,t,f):
self.text=t
self.func=f
def AddItem(self,text,function):
self.items.append(Item(text,function))

for future readers! it's easy to have a python console menu using console-menu.
pip install console-menu
now, let's implement our menu
from consolemenu import *
from consolemenu.items import *
# Create the menu
menu = ConsoleMenu("Title", "Subtitle")
# Create some items
# MenuItem is the base class for all items, it doesn't do anything when selected
menu_item = MenuItem("Menu Item")
# A FunctionItem runs a Python function when selected
function_item = FunctionItem(
"Call a Python function", input, ["Enter an input"]
)
# A CommandItem runs a console command
command_item = CommandItem("Run a console command", "touch hello.txt")
# A SelectionMenu constructs a menu from a list of strings
selection_menu = SelectionMenu(["item1", "item2", "item3"])
# A SubmenuItem lets you add a menu (the selection_menu above, for example)
# as a submenu of another menu
submenu_item = SubmenuItem("Submenu item", selection_menu, menu)
# Once we're done creating them, we just add the items to the menu
menu.append_item(menu_item)
menu.append_item(function_item)
menu.append_item(command_item)
menu.append_item(submenu_item)
menu.show()
you would get a menu like this

Related

Python Tkinter Avoid using "root" name in lower level function

Inspired by this 300+ vote closed Q&A: Best way to structure a tkinter application?, I'm looking to avoid explicitly using root in a function within a class. I think it should be implicitly declared through self or parent or something like that. Here is the code in question:
I have this code...
self.label_this = tk.StringVar()
self.label_last = tk.StringVar()
self.label_total = tk.StringVar()
tk.Label(count_frame, textvariable=self.label_this, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_last, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_total, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
self.update_cnt_labels()
Then later on...
''' Get list of Window ID's on monitor now '''
new_windows = self.windows_on_monitor(new_windows)
new_windows_cnt = len(new_windows) / WIN_CNT
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return
Then later on...
class FlashMessage:
def __init__(self, widget, message, count=5, on=500, off=300):
self.delay_show (1, widget, message)
for i in range(count):
self.delay_show (on, widget, "")
self.delay_show (off, widget, message)
def delay_show(self, ms, widget, message):
root.after(ms, widget.set(message))
root.update_idletasks()
I want to avoid using root in the last two lines and use self or something similar.
My program call chain is something like:
the traditional: root = tk.Tk()
bunch of mainline initialization stuff.
the class: ResizingCanvas(mycanvas)
mainline function: popup(event) which is bound to <ButtonPress-1>
Dynamically formatted menu.tk_popup(event.x_root, event.y_root)
the class: RemoveNewWindows()
the function: remove()
the class: FlashMessage() (show above)
the function: self.delay_show() (shown above)
Each class and function has haphazard self, positional parameters, *args and **kwargs which mostly serve no purpose. Indeed even the __init__ above might be unnecessary. This is a result of copying code all over stack overflow.
Every second word in the program seems to be self but the word parent is only used in the class ResizingCanvas(). Do I have to propagate parent down the call list and use it somehow?
You can call after and update_idletasks on any widget. There are many such functions that can be called on any widget but which have a global effect.
In your case, you'll need to pass some widget into the FlashMessage constructor and save the reference. You can then use the reference to call the functions.
You're passing something called widget that doesn't actually contain a widget. You need to rename it to something more appropriate (eg: var), and then pass in an actual widget.
(Note: you also are calling after incorrectly, which I've fixed in the following example)
For example:
class FlashMessage:
def __init__(self, widget, var, message, count=5, on=500, off=300):
self.widget = widget
...
def delay_show(self, ...):
self.widget.after(ms, var.set, message)
self.widget.update_idletasks()
Then, whenever you create an instance of FlashMessage you need to add a widget as the first parameter.
For example, assuming that count_frame is defined in the context where you create an instance of FlashMessage and it is an actual widget, it might look something like this:
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (count_frame, self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return

How would I change my functions into classes?

I'm working on a coursework for my uni (GUI program) and I run into a problem as my code works but the specification is that we use OOP instead of just functions, and I'm lost.
I tried making new classes for each button but I don't know how to make them work like they do in the functions.
def add():
#get input
task=txt_input.get()
if task !="":
tasks.append(task)
#updating the list box
update_listbox()
else:
display["text"]=("Input a task.")
with open("ToDoList.txt", "a") as f:
f.write(task)
f.close()
txt_input=tk.Entry(root, width=25)
txt_input.pack(pady=15)
add=tk.Button(root, text="Add", fg="DarkOrchid3", bg="blanched almond", command=add)
add.pack(pady=5, ipadx=15)
This allows the user to add a task to his to-do list in the GUI, but like I said it should be using OOP not functions.
If I get to understand this one, I should be able to do the rest of the buttons.
UPDATE:
Ok so I tried the solution given below and I don't really know what is wrong with my code, the GUI appears but the adding functions won't work.
class ToDoList():
def __init__(self):
self.tasks = []
def update_listbox(self):
#calling clear function to clear the list to make sure tasks don't keep on adding up
clear()
for task in self.tasks:
box_tasks.insert("end", task)
def clear(self):
box_tasks.insert("end", task)
class adding():
def add(self):
task=txt_input.get()
if task!="":
self.tasks.append(task)
update_listbox()
else:
display["text"]=("Input a task")
It's not clear what your teacher meant about using classes. I'm going to guess that they want you to create a "todo list" object that has methods for adding and removing tasks. I don't know whether they want the GUI to be part of that class or not. It could be that your entire application is made of classes, or you could only use the class for the business logic.
I think you should start by creating a class just for the business logic. It would look something like this:
class ToDoList():
def __init__(self):
self.the_list = []
def add(self, value):
<code to add the value to self.the_list>
def remove(self, item):
<code to remove a value from self.the_list>
With that, you can write a simple little program without a GUI, which makes it easy to test the logic:
# create an instance of the to-do list
todo_list = ToDoList()
# add two items:
todo_list.add("mow the lawn")
todo_list.add("buy groceries")
# delete the first item:
todo_list.remove(0)
To build a GUI on top of that, you could either add the GUI component to the existing class, or create a new class specifically for the GUI. Each has pros and cons.
In the following example, the GUI is a separate class which uses the ToDoList class to maintain the data. This design lets you re-use the underlying todo list logic in tests, in a GUI, and even in a webapp or possibly even a mobile app.
class ToDoGUI():
def __init__(self):
# initalize the list
self.todo_list = ToDoList()
# initialize the GUI
<code to create the entry, button, and widget to show the list>
def add(self):
# this should be called by your button to the list
data = self.entry.get()
self.todo_list.add(data)

How to correctly create an instance of a class with exec() function?

I'm making a GUI using the TKinter library from Python. I want the user to select an option from a Combobox and then, to press a Button, which should create an instance of a class named as the selected option. In order to save code, I decided to use the exec() fuction in this way:
exec('instance = ' + comboExample.get() + '()').
This starts the __init__() method of the class, but when I try to call an other method (in this case from an inherited class) using instance.method() it displays the following error: NameError: name 'instance' is not defined. Here you have an example of the script:
from tkinter import *
from tkinter import ttk
master = Tk()
#Create classes
class Base():
def method(self):
self.label = Label(master, text = self.sentence)
self.label.pack()
class Example1(Base):
def __init__(self):
print('Example1 created')
self.sentence = 'This is example 1.'
class Example2(Base):
def __init__(self):
print('Example2 created')
self.sentence = 'This is example 2'
#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = ['Example1', 'Example2']
combo.pack()
def callback():
exec('instance = ' + combo.get() + '()')
#Here is the error
instance.method()
button = Button(master, command = callback, text = 'Button')
button.pack()
master.mainloop()
I don't now why but when I try with the following code it works properly:
class Example():
def __init__(self):
self.text = 'This is an example'
def add_text(self):
print(self.text)
exec('instance = Example()')
instance.add_text()
At the moment, I've only found one solution, which consists in not using exec(), but makes me waste more code than using it, especially if I want to create a lot of classes like Example1 and Example2. It's all like the previous big script, but changing the callback() function:
def callback():
if combo.get() == 'Example1':
instance = Example1()
if combo.get() == 'Example2':
instance = Example2()
instance.method()
That's all. I started programming in Python only 2 months ago and I'm also new in stackoverflow, so if I've made some mistake in the explanation or anything, please tell me and I'll fix it.
Thanks for your time. Any help would be appreciated.
The issue isn’t your syntax; it’s that you’re trying to do something illegal. You can’t create new local variables with exec. (The reason the same code outside a function works is that in general you can create a new global variable with exec, but it’s still a bad idea.)
But you also don’t need to do that. In Python, everything is an object, including classes. So, you just need the get the class from the name. Then you can create an instance of that class, and store it in a local variable, by just using the same normal syntax you’d use for instantiating a class statically and storing it in a local variable.
The right way to do this is to store a dictionary mapping names to class objects. If you want to get clever, you can write a decorator that registers classes with that dictionary, but if that sounds like Greek to you, just do it explicitly:
classes = {'Spam': Spam, 'Eggs': Eggs}
If you have dozens of these, you can avoid the repetition with a comprehension like this:
from your_module import Spam, Eggs
classes = {cls.__name__: cls for cls in (Spam, Eggs)}
… but at that point you’re probably better off learning how to write the decorator.
Either way, you can fill your combo box with the keys of that dictionary instead of repeating yourself in the combo['values'] line.
And then, to create an instance, you just do this:
cls = classes[comboExample.get()]
instance = cls()
(Obviously you can collapse that into a single line, but I thought it would be easier to understand if we keep the two parts separate.)
If you really want to do this in a hacky way, you can. Every class that you’ve created in this module is already stored in a dictionary by name—the module’s global namespace. That’s the same place you were trying to find it implicitly with exec, but you can find it explicitly by just looking it up in globals(). However, the global namespace also has the names of all of your functions, imported modules, top-level constants and variables, etc., so this is usually a bad idea. (Obviously, exec has the exact same problems.)
You should not be using exec for this purpose. exec is a powerful tool, but it's the wrong tool for this job.
A much simpler approach is to create a mapping from user inputs to classes. You can then use that mapping both for the combobox and for the callback.
Example:
...
mapping = {"Example1": Example1, "Example2": Example2}
#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = sorted(mapping.keys())
combo.pack()
def callback():
class_name = combo.get()
cls = mapping[class_name]
instance = cls()
instance.method()
...
You could even automatically generate the mapping by iterating over a list of classes, though for this example that seems like overkill.

Can QFrame be triggered by its childs elements

When an item (spinBox, LineEdit etc) changes its value in GUI (via designer) I set a certain button's enable status. For example:
self.ui.lineEdit_1.textChanged.connect(self.pushButton_status)
self.ui.checkBox_1.stateChanged.connect(self.pushButton_status)
self.ui.spinBox_1.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_2.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_3.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_4.valueChanged.connect(self.pushButton_status)
This works fine. Though there are lots of lines here (and even more in the actual code). I have all of these items inside a frame (QFrame). So I was wondering if it is possible to do something like:
self.ui.frame_1.childValueChanged.connect(self.pushButton_status)
which could perhaps stand for all the items inside of it. Is there any way within this logic that could do what I am looking for? If so.. how?
There is no direct way to do what you want, but there is a maintainable way to do it, in this case you just have to filter the type of widget and indicate which signal you will use by adding more options to the function, in your case:
def connectToChildrens(parentWidget, slot):
# get all the children that are widget
for children in parentWidget.findChildren(QtWidgets.QWidget):
# filter if the class that belongs to the object is QLineEdit
if isinstance(children, QtWidgets.QLineEdit):
# Connect the signal with the default slot.
children.textChanged.connect(slot)
elif isinstance(children, QtWidgets.QCheckBox):
children.stateChanged.connect(slot)
elif isinstance(children, QtWidgets.QSpinBox):
children.valueChanged.connect(slot)
And then you use it in the following way:
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
connectToChildrens(self.ui.frame_1, self.pushButton_status)

How come I cannot access an instance of a class defined globally inside another class?

I'm trying to use the ui object within the Pawn class and I define and initiate ui outside everything, so it is global right? I've looked on here for questions relating to using variables outside classes but they seem to all refer to .self which I used in the UI class when initiating the Chess object.
The # represent bits of code I've cut out to help readability.
class UI:
def ___init__(self):
self.chess = Chess()
# Calls main menu function which goes to a new game option
def newGame(self):
self.chess.startGame()
while len(self.chess.pieces[0] == 2): # Which is index for Kings
# Game, which is where *move* is called
class Chess:
def startGame(self):
self.chessBoard = Grid(9,9)
self.pieces = []
self.pawns = []
for i in range(8):
self.pawns.append(PawnObject(x,y,side))
self.pieces.append(self.pawns)
class Grid:
# Init method
def cellOccupied(self,x,y):
# This function checks if a place is empty
# If empty, return false else, true
class Object:
# Sets x, y, width, height
class ChessPiece:
# Child of Object sets side (black or white)
class PawnObject:
def move(self,newX,newY):
if ui.chess.chessBoard.cellOccupied(newX,newY) == False:
# Move
# More code
ui = UI()
Traceback: https://gyazo.com/33e5b37b5290ff88433b29874c117ad7
Am I doing something blindingly wrong? I think the way I've programmed this all is very inefficient as I am still learning so is this a result of that? Thank you.
The problem is that this cascading series of events all happens inside the initialisation function for UI; one class calls the next, all before the original __init__ has had a chance to return. This means that the line that did that initialisation has not completed, so the ui variable does not exist yet.
You should try and move some of this out of that cascade. In particular, I can't see why the pawns should move as a result of initialising the UI class; that doesn't seem to make sense at all.
You should also consider why you need ui to be a global variable; seems more likely that it should be an attribute of one of the classes, perhaps Grid, and the pawn can reference it via that instance.
You're probably using ui in the body of a class, which is executed the moment the interpreter sees the class (and therefore before ui exists). You can only use it inside methods or functions since those are only executed when they're called. In other words:
class UI:
def open(self):
pass
class Chess:
ui.open() # <--- this causes an error because it happens before the last line does
def startGame(self):
ui.open() # <--- this is OK
ui = UI()
Also, I think your message indicates that you wrote global ui somewhere which is only necessary if you're planning on assigning a new value to ui, i.e. ui = something. If you just want to use it, e.g. ui.open(), you don't need the global declaration. But removing it won't solve your problem.
You also need to correct
chessBoard = Grid(9,9)
to
self.chessBoard = Grid(9,9)
to make chessBoard an attribute of Chess, which you need to be able to say ui.chess.chessBoard.

Categories