Simpler text menu in Python 3 - python

I have to make a program with a text based menu system for a school project. It includes a main menu and multiple sub-menus. I have made my current version of the menu with LOTS of if-statements, prints and inputs. But it is neither a elegant nor easy solution. I was hoping there was a easier / better way to create a menu.
To be more precise, I need a method of calling the menu (to display it) after which I can choose an option, which then executes some code.
The structure looks as follows.
Input data Filters
Filters
Add filter
Type 1
Type 2
Type 3
Remove filter
Edit filter.
Do calculations
Mean
Standard variation
etc.
Create plot
Exit

The easiest way to handle this kind of problem is by recursion. Once you
establish a useful data structure it can be handled recursively quite easily, since the job of creating a sub-menu is essentially the same as creating a menu.
In the solution below I define the menu as a tuple. The items in the menu are either the commands, represented as strings, or sub-menus, represented by nested tuples.
menu_data = (
"Input data filters",
("Filters",
("Type 1",
"Type 2",
"Type 3"),
"Remove Filter",
"Edit Filter"),
"Do Calculations",
("Mean",
"Std Deviation",
"etc"),
"Create Plot",
"Exit"
)
def make_menu(menu, indent=0):
for item in menu:
if type(item) == tuple:
make_menu(item, indent+1)
else:
print(" "*indent+item)
make_menu(menu_data)
This should print the structure you require.

Use the plot functions to plot menu's. You can import menus that are already made and then modify them so they suit what you want. Ploted windows's can interact with your mouse and and keyboard to move through the menu and select a locations in it. You can make it look like a text menu, with the only diference it is in a window instead of a shell.
The library "pygame" has a lot of this.
I hope this helps.

Related

How to know all the disabled options in an Options Menu Tkinter Closed

I have disabled an option in an OptionMenu to fix something in the code where if I click on it just duplicates it, so I thought by disabling the option is an easy work around, but when the user may want to change to another option both the options are now disabled making the first one not usable again. I now need to return the option to normal. I thought of getting all the options which are disabled but couldn't figure out on how to do that. Sorry for the long para.
Any suggestions are useful.
Assume optmenu is the instance of OptionMenu, the following code will return all the items that are disabled:
# optmenu is the instance of OptionMenu
menu = optmenu['menu']
items = [] # list to hold all disabled items
# go through all option items
for i in range(menu.index('end')+1):
if menu.entrycget(i, 'state') == 'disabled':
items.append(menu.entrycget(i, 'label'))
print(items)
You might want to consider an object-oriented approach, defining for your object either a dict, list or some other array of settings, from which you can yourself easily fetch the status of any single control. Even if you don't want to do OOP, you should probably yourself save the current settings to an array.

How to add options in OptionMenu without using the add_command function in Tkinter?

Is there any way to add options in an OptionMenu object without using the command flag?
I have seen many posts, such as this one, showing how the add_command will update/remove/add options to an already existing OptionMenu object. For example, this snippet of code removes all options in serialPortOptionMenu and repopulates the option menu with different options:
serialPortOptionMenu["menu"].delete(0, "end")
for serialPort in serialPortsArray:
serialPortOptionMenu["menu"].add_command(label=serialPort[1], command=lambda v=serialPort: serialPortFunc(v))
However, something like this seems to overwrite the original command flag I wrote when creating the OptionMenu object:
serialPortOptionMenuValue = Tkinter.StringVar(optionMenuFrame)
serialPortOptionMenuValue.set(serialPorts[0])
serialPortOptionMenu = Tkinter.OptionMenu(optionMenuFrame, serialPortOptionMenuValue, *serialPorts, command=lambda *args: callbackFuncWhenOptionMenuSelectsAnotherOption(*args))
serialPortOptionMenu.grid(row=3, column=0, columnspan=2, sticky="we")
As I'm sure many people are wondering why I am setting a command within an OptionMenu (weird I know), it is because I want a callback function to be called when the user picks a new option.
"What about the trace option"-Everyone...Yes, I am aware of this as well, but because I am reading/writing new values to the Tkinter variable in code without having the OptionMenu solely change it, using trace within Tkinter would not be an effective substitute for just tracking when the user selects a new option in the OptionMenu.
Why am I changing the Tkinter value in code and not having the OptionMenu do it? Because I thought it would be nice to modify the Tkinter value string with something like a ~ at the end of the string to signify something is happening behind the scenes that isnt completed yet, and only when it is completed will the ~ go away, hence the reading and writing to the value without having the OptionMenu solely change it.
What I am primarily interested in is if anyone knows of other ways to add options to an OptionMenu object without using
myMenu["menu"].add_command(..., command=...)
As it seems to be removing my original callback function.
Note: Having two OptionMenus and performing grid_remove/grid on them to hide/reveal them also crossed my mind, but just seems to messy.
There is no other way. An option menu is nothing more than a menubutton and a menu with a custom binding. You can create your own OptionMenu and add any methods you want.
I don't know if it will be useful to you anymore, but maybe somebody else will be looking for this as I was. You actually just need to put the command as a callback parameter in the command you use to add the item (I found tk._setit):
def refresh_dropdown():
# Reset var and delete all old options
var.set('')
dropdown['menu'].delete(0, 'end')
# Insert list of new options (tk._setit hooks them up to var)
new_choices = ['a', 'b', 'c', '...'] # you can make a dictionary[key_from_previous_dropdown.get()] as I did in my case
for choice in new_choices:
dropdown['menu'].add_command(label=choice, command=_setit(var, choice, new_command))
dropdown = OptionMenu(app, var, command=new_command)

Tkinter, Entry widget, is detecting input text possible?

I have an Entry widget on a simple calculator. The user can choose to enter an equation via the keypad. I was wondering if there was a way to detect a character(from the keypad in my case) being typed into the Entry widget. So, focus is on the widget, user presses '4', it comes up on the widget... can I detect this act, for basic purposes of logging the input?
Every time you press a key inside a Tkinter window, a Tkinter.Event instance is created. All you need to do is access that instance. Here is a simple script that demonstrates just how:
from Tkinter import Tk, Entry
root = Tk()
def click(key):
# print the key that was pressed
print key.char
entry = Entry()
entry.grid()
# Bind entry to any keypress
entry.bind("<Key>", click)
root.mainloop()
key (being a Tkinter.Event instance) contains many different attributes that can be used to get almost any type of data you want on the key that was pressed. I chose to use the .char attribute here, which will have the script print what each keypress is.
Yes. There are a few different ways to do this, in fact.
You can create a StringVar, attach it to the Entry, and trace it for changes; you can bind all of the relevant events; or you can add a validation command that fires at any of several different points in the sequence. They all do slightly different things.
When a user types 4, there's a key event with just the 4 in it (which doesn't let you distinguish whether the user was adding 4 to the end, or in the middle, or replacing a whole selected word, or…), and then a modification event is fired with the old text,* and then the "key" or "all" validation function is called with the (proposed) new text, and the variable is updated with the (accepted) new text (unless the validation function returned false, in which case the invalidcommand is called instead).
I don't know which one of those you want, so let's show all of them, and you can play around with them and pick the one you want.
import Tkinter as tk
root = tk.Tk()
def validate(newtext):
print('validate: {}'.format(newtext))
return True
vcmd = root.register(validate)
def key(event):
print('key: {}'.format(event.char))
def var(*args):
print('var: {} (args {})'.format(svar.get(), args))
svar = tk.StringVar()
svar.trace('w', var)
entry = tk.Entry(root,
textvariable=svar,
validate="key", validatecommand=(vcmd, '%P'))
entry.bind('<Key>', key)
entry.pack()
root.mainloop()
The syntax for variable trace callbacks is a bit complicated, and not that well documented in Tkinter; if you want to know what the first two arguments mean, you need to read the Tcl/Tk docs, and understand how Tkinter maps your particular StringVar to the Tcl name 'PY_VAR0'… Really, it's a lot easier to just build a separate function for each variable and mode you want to trace, and ignore the args.
The syntax for validation functions is even more complicated, and a lot more flexible than I've shown. For example, you can get the inserted text (which can be more than one character, in case of a paste operation), its position, and all kinds of other things… but none of this is described anywhere in the Tkinter docs, so you will need to go the Tcl/Tk docs. The most common thing you want is the proposed new text as the argument, and for that, use (vcmd, '%P').
Anyway, you should definitely play with doing a variety of different things and see what each mechanism gives you. Move the cursor around or select part of the string before typing, paste with the keyboard and with the mouse, drag and drop the selection, hit a variety of special keys, etc.
* I'm going to ignore this step, because it's different in different versions of Tk, and not very useful anyway. In cases where you really need a modified event, it's probably better to use a Text widget and bind <<Modified>>.
If you just need to do simple things without using trace module you can try
def objchangetext(self, textwidget):
print(textwidget.get()) #print text out to terminal
text1 = tk.Entry(tk.Tk())
text1.bind("<KeyRelease>", lambda event, arg=(0): objchangetext(text1))

How do I read what's been selected in a MULTIPLE listbox

I'm very new to Python and can't get my head round how to capture just ONE item that's been selected from a listbox, let alone more than one. I particularly don't understand WHEN the items I select are noted by the program because there is no "command" option with a listbox. I think that is so. I don't really understand binding. I think that's my problem (amongst others!).
The following code is extracted from my "app" class.
l = Listbox(self, height=10, selectmode=EXTENDED)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
l.grid(column=0, row=11, sticky=(N,W,E,S))
s = Scrollbar(self, orient=VERTICAL, command=l.yview)
s.grid(column=0, row=11, sticky=(N,S,E))
l['yscrollcommand'] = s.set
for i in range(1,101):
l.insert('end', 'Line %d of 100' % i)
self.ichoose = l.curselection()
As you experts will realise, when I print app.ichoose, I just get an empty tuple.
What do I need to do?
Thanks,
John Rowland
Normally the item is selected when a user clicks on it, and the default binding fires. If you want to print it out (or do anything else) as soon as possible once that happens, create a binding on the event <<ListboxSelect>>. This event will be generated immediately after the selection has changed, even if it changed via the keyboard.
okay... the way I've done this in the past is by:
listbox.bind("<Double-Button-1>", entered)
which then the function entered would look something like this:
def entered(event):
global listEx
items = map(int, listbox.curselection())
result= listEx[items[0]]
print result
listEx is a list of all the entries in the listbox, and the items = map(int, listbox.curselection()) section will return the index value of the selected entry... if you want to get multiple values, I imagine it would be very simple to for loop through the values in items
EDIT:
def entered(event):
print listbox.selection_get()
will just print out the selection from the listbox, the reason I like to use my double mouse click is because it is much more likely to be a real world use, normally I use double click and <Return> as standard listbox controls, especially if in a use with multiple selections as yours

Handling events from file menu

I am trying to code a program with a regular file menu. (e.g. File, Edit, View, etc).
I want the action they take in that menu to update my status bar (a label).
The problem is, the way I have it setup now, I believe it's executing the command and then trying to take the result as what it should do.
Currently a menu item is defined like so:
fileMenu.add_command(label="Insert", command=self.statusUpdater(statusLabel,"Insert Triggered")
And the function statusUpdater is defined as such:
def statusUpdater(self,status,commandName):
status.config(text=commandName)
status.update_idletasks()
So the problem is, right at the start of the program, the status changes to "Insert Triggered". What I want is for that to only happy once I have actually clicked "Insert"
From hints I've seen elsewhere it seems like I need some way to pass and handle the event of Insert being clicked.
Could someone supply a generic and basic function that does what I ask? I think the problem lies in the () attached to the command function, but I don't know any other way to pass arguments.
All i need is a function that is called on the click event, and knows which fileMenu command triggered it.
Thanks!
Commands take a reference to a function. You can se a lambda if you want to pass it arguments:
...command=lambda l=statusLabel, self.statusUpdater(l, "Insert Triggered"))

Categories