Python Tkinter: Parsing dropdown menu value into a function as a variable? - python

I am trying to create a Tkinter dropdown menu to add into the main GUI. The purpose of the menu is to give the user several options to choose the image segmentation neural network (currently there are only two options).
I am having trouble getting the value of the dropdown menu and parsing it through the function as a parameter (variable, not a string).
I have tried creating a staticmethod within the GUI window class to search for the value of the dropdown menu and parse the value through the image segmentation function when the segment button in the GUI is pressed. The segmentation function takes two parameters, net and path, where it is neural network and image file pathway respectively.
I am not too sure how to change the individual parameters of a variable so I just changed the whole variable depending on the value of the dropdown menu. The only thing being changed is the net parameter of the segment function bind to the Tkinter button.
The code:
#staticmethod
def find_option():
if menu1.get() == "fcn":
App.btn2 = Button(App.btn_frame, text="Segment", width = 10, height = 1, cursor = "hand2", command=lambda: App.segment(net=App.fcn, path=App.path))
else:
App.btn2 = btn2 = Button(App.btn_frame, text="Segment", width = 10, height = 1, cursor = "hand2", command=lambda: App.segment(net=App.dlab, path=App.path))
The dropdown menu code:
fcn = models.segmentation.fcn_resnet101(pretrained=True).eval()
dlab = models.segmentation.deeplabv3_resnet101(pretrained=1).eval()
options = ["fcn", "dlab"]
variable = StringVar(btn_frame)
variable.set(options[0]) # default value
menu1 = OptionMenu(btn_frame, variable, *options)
menu1.pack(side=LEFT)
btn_frame is the frame within the main window containing the buttons.
App is the main GUI class. menu1 needed to be referenced as App.menu1 but it would, for some reason, cause several errors (other class variables would become undefined and it would say App object has no attribute menu). I also tried referencing menu1 as App().menu1 but would initiate a new window each time I pressed the segment button, also it never actually showed the segmented image.

Edit: I think you want something like this. It's hard to know when I can't see the entire code, but could it be that you are forgetting ot pass self ot the find_option method?
# This is the option menu part
self.options = ["fcn", "dlab"]
self.variable = tk.StringVar(btn_frame)
self.variable.set(options[0]) # default value
self.menu1 = tk.OptionMenu(
btn_frame, self.variable, *self.options)
self.menu1.pack(side=LEFT)
# And then later you have this method (remember to pass self to find_option)
def find_option(self):
if self.variable.get() == "fcn":
# your code

Related

How do I make a button that allows me to send two variables into the same function in Tkinter?

def openCipher():
cipher = Toplevel()
cipher.title("decryptt - CIPHER")
cipherLabel = Label(cipher, text="cipher").pack()
cipherEntry = Entry(cipher, width=20, borderwidth=5) #separating pack now allows you to use get() on this
cipherEntry.pack()
cipherChoices = [
("Binary","bcipher"),
("Caesar","ccipher"),
("Hexadecimal","hcipher"),
("Atbash","acipher"),
("Letter-to-Number","lcipher")
]
cipherType = StringVar()
cipherType.set("Binary")
for text, cipherChoice in cipherChoices:
Radiobutton(cipher, text=text, variable=cipherType, value=cipherChoice).pack()
cipherButton = Button(cipher, text="Cipher", padx=10, pady=5, command=lambda:[ciphering(cipherEntry.get()), ciphering(cipherChoice.get())]).pack() #lambda allows you to pass arguments to functions
quitButton = Button(cipher, text="Exit Cipher", padx=10, pady=5, command=cipher.destroy).pack()
# This is the function that is suppose to split the input from cipherEntry into individual characters in an array.
def ciphering(entry,choice):
ciphering = Toplevel() #needed to add new label to
cipherLabeling = Label(ciphering, text = "You have inputted " + entry).pack() #couldn’t add a list to string like that, nor use get() on a list, changed to just use the string
seperatedWord = list(entry)
cipherLabeling = Label(ciphering, text = seperatedWord[2]).pack()
seperatedWordLength = len(seperatedWord)
cipherLabeling = Label(ciphering, text = seperatedWordLength).pack()
selection = Label(ciphering, text = choice).pack()
Above is part of the code I have for my ciphering app I am making in Tkinter. Took out the less important parts.
Basically, what is being created in OpenCipher() functions is an entry box that is named cipherEntry. Then there are radio buttons with different names of different ciphers and the value and variable of each radio button is the same as each other for that radio button. Then there is another button that takes whatever cipherEntry is and brings it to another window using the ciphering() function.
What I need to know is how do I also get whatever the value and/or variable of whatever radio button they have selected to that window using the same button they pressed to get to that window ( cipherButton ). Because I want to then use their selection and input to know what cipher type they want their input to be changed to. I already have the function for it sorted.
I have tried using cipherType, cipherChoice, cipherChoices but have no idea how to get them both in there. With the current code above. It works as if there was no second command. It totally disregards whatever selection I put in and the 'selection' label widget doesn't display their choice. I have also made each variable a global to see if that did anything but no luck.
I would really appreciate any assistance :)
First of all, the code should give an error because def ciphering(entry,choice) expects two positional arguments to be passed at the same time. Even after fixing that, it should give another error because cipherChoice is a string(from the list of tuples) and does not have a get attribute.
The thing to focus on here is:
command=lambda: [ciphering(cipherEntry.get()), ciphering(cipherChoice.get())]
When you say something like lambda: [func1(arg1),func1(arg2)] you are set to executing the function func1 and again func1 one after the other(so twice). What you want is to pass multiple arguments to the same function just using a normal lambda without any list, like:
command=lambda: ciphering(cipherEntry.get(), cipherType.get())
Also notice how I changed cipherChoice.get() to cipherType.get(), it is because cipherChoice is a string and also does not have a get attribute, but the value of the radiobutton should be acquired from the associated tkinter variable(StringVar) only. So you have to use cipherType.get()

Python tkinter: Delete Menu checkbutton

I want to delete a menu checkbutton when i right click on it.
Its usually done with bind("Mouse3", deletefunction) , BUT i need an actual instance of a checkbutton to bind it with, and the only way to add a checkbutton to a menu i know is a add_checkbutton() method (and i have no access to instance this way). Is there any way i could do this?
import tkinter as tk
root = tk.Tk()
menubar = tk.Menu(root)
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=1, offvalue=False)
# I want to do something like this:
# c = Checkbutton(label="Right click on me to delete")
# c.bind("Mouse3", my_delete_function())
# view_menu.add(c)
menubar.add_cascade(label='View', menu=view_menu)
root.config(menu=menubar)
root.mainloop()
To the best of my understanding, there's essentially two parts to your question:
Can the menu bar item be assigned for manipulation after?
Can the referenced item be then bound to an event?
The first answer is, sort of. While you can't exactly assign the object, you can reference it by index like this:
view_menu.delete(0)
Since you added the checkbutton first, it'll have an index of 0. You can either keep track of the indices, or refer to the item by its label. See this related answer from Bryan Oakley. e.g.:
view_menu.delete(view_menu.index("Right click on me to delete"))
The .index() method will locate the index by the menu entry's label, which can be handy unless you have the same label more than once...
The second answer, as far as I'm aware, there doesn't seem to be any effective binding for typical events like mouse clicks. However after some search I did come across a rather hidden <<MenuSelect>> binding that at least triggers an event. That by itself is not useful to your quest, but you can combine the event state with the checkbutton's command argument as well as a boolean flag to trigger an event on click:
# Add a BooleanVar for flagging:
delete_checkbutton = tk.BooleanVar()
# Add a binding to your view_menu
view_menu.bind('<<MenuSelect>>', event_state)
# Define the callback function:
def event_state(e):
if bool(e.state & 0x0400): # watch for the Mouse Right click state
# you might opt to use 0x0004 or 0x0001 instead
# i.e. Ctrl+click or Shift+Click
delete_checkbutton.set(True)
else: # If the state is not right click, reset the flag
delete_checkbutton.set(False)
# Define a self_delete command for the checkbutton
def self_delete():
if delete_checkbutton.get():
view_menu.delete(view_menu.index("Right click on me to delete"))
# Add the command to your checkbutton
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=lambda:print('hey'), offvalue=False, command=self_delete)
Note: You will actually have to hold right click and then left click on the checkbutton to delete it. Obviously the drawback is you have now triggered the on/off value, and you might need to have some additional handling on those.
If right + left click is too awkward, Ctrl/Shift is another mod state you might consider.
Another side note: I'm a proponent of OOP when it comes to tkinter, it makes accessible variables and flags much easier without needing to worry the global and nonlocal namespaces. Here since delete_checkbutton is set in the global namespace I avoied using the global keyword and accessed it via the tk.BooleanVar() object. However if you were to use a python boolean (e.g. flag = True) then it won't be as effective unless you indicate global flag in both functions. If however you took an OOP approach you can reference the flags directly via self.flag without ambiguity.
Finally, here are the comprehensive changes implemented into your code for sampling:
import tkinter as tk
def event_state(e):
if bool(e.state & 0x0400):
# you might opt to use 0x0004 or 0x0001 instead
# i.e. Ctrl+click or Shift+Click
delete_checkbutton.set(True)
else:
delete_checkbutton.set(False)
def self_delete():
if delete_checkbutton.get():
view_menu.delete(view_menu.index("Right click on me to delete"))
root = tk.Tk()
menubar = tk.Menu(root)
delete_checkbutton = tk.BooleanVar()
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_command(label='dude', command=lambda: print('dude'))
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=lambda:print('hey'), offvalue=False, command=self_delete)
menubar.add_cascade(label='View', menu=view_menu)
root.config(menu=menubar)
view_menu.bind('<<MenuSelect>>', event_state)
root.mainloop()
All that said, I am of the opinion that this is not a very smooth User Experience and is somewhat confusing. Just the permanent deletion of the menu item alone is questionable at best, combined with the method you are trying to call upon the deletion feels even more contrived. I'd suggest revisiting your UX flow to consider how to streamline this.

How to Use a Setter Function to Check Whether a Function Parameter has Changed - Python

Here is my code:
def DisplayFiles(which):
contentLabel = tk.Label(window, text = '\n'.join(files[indexlist[which]][1:]))
contentLabel.place(x = 150, y = 80)
I am using Tkinter and am trying to display files with the above function when a button is pressed. The variable "which" is the string name of a button. "indexlist" is a dictionary holding indexes for button names (I dynamically created them). My problem is trying to display files for two different buttons. When I click one button, the function above displays the files. But when I click another button, the label displays over the previous one. I am working on a destroy() method, but I need to know how to check when the parameter "which" is changed. Help would be appreciated!
Also, the values of indexlist, and files are not the problem. I just want to find a way to check when the function parameter is changed. Thanks!
Don't make a new Label every time, just update the old Label.
# make an empty Label
contentLabel = tk.Label(window)
contentLabel.place(x = 150, y = 80)
def DisplayFiles(which):
# update the Label contents
contentLabel.config(text = '\n'.join(files[indexlist[which]][1:]))

Python: Referring to single labels in a Pmw RadioSelect

I would like to create a hover box (or info box) which opens up when the user places the mouse cursor on top of a Pmw RadioSelect label. For example, when the cursor is placed on top of "Primary" the program opens an info box explaining what "Primary" means.
Problem: I don't know how to access the individual labels inside the RadioSelect object. I need to bind a method to the individual labels, but I don't know how to refer to them.
Extra: How could I have solved this myself? I tried looking at the RadioSelect attributes with dir() and I read the Pmw manual online, but couldn't find the information.
EDIT This is what I have found out thus far: The manual says that the labels only start to exist if their position is set explicitly:
labelpos
Initialisation option. Specifies where to place the label component.
If None, a label component is not created. The default is None
After setting it explicitly for example as so:
self.rs = Pmw.RadioSelect(parent, labelpos = 'w')
you can refer to it with
self.rs.component('label')
But I still don't know how to reach the individual labels.
EDIT 2: The trick was just to assign the RadioSelect "items" into variables like the accepted answer suggests:
self.cb1 = self.radio_select.add("text")
After assigning the "item" into a variable you can simply bind methods to the variable, like such:
self.balloon = Pmw.Balloon(self, initwait=500, relmouse='both')
self.balloon.bind(self.cb1, "Balloon text example")
If I understand well your problem, I think you are looking for:
To rely on Pmw to draw the widgets (unlike what I did with Tkinter previously)
when the cursor is placed on top of "Primary" the program opens an info box explaining what "Primary" means. (the effect I produced on the demo below)
Identify individual checkbuttons (or what you call in your own terms reaching the individual labels within the Pmw.RadioSelect)
Solution:
The solution for the first problem you know it already.
For the second problem, as I explained previously, you will need to instantiate Pmw.Balloon() and bind it to individual checkbuttons (or labels as you call them). I re-programmed that as you can see below but using an other method. I mean I relied mainly on add() which returns the component widget. Then I binded the instance of Pmw.Balloon() to the returned value from add(). Doing this, you already offer yourself a way to access individually the checkbuttons (and you play more with this if you want)
You can access individual checkbuttons (labels) by using getvalue() or getcurselection() methods which work similarly by returning the return the name of the currently selected button. But in practice, you will get tuples ( I mean these functions return the names of all selected checkbuttons, as I showed in the access_to_labels_individually() that I used as a callback method to display the names of the checkbuttons you select; of course you can play with that also depending on your needs)
Code
Here is an MVCE program:
'''
Created on Jun 18, 2016
#author: Billal BEGUERADJ
'''
# -*- coding: utf-8 -*-
import Pmw
import tkinter as tk
class Begueradj:
def __init__(self, parent):
self.balloon = Pmw.Balloon(parent)
# Create and pack a vertical RadioSelect widget, with checkbuttons.
self.checkbuttons = Pmw.RadioSelect(parent,
buttontype = 'checkbutton',
orient = 'vertical',
labelpos = 'w',
command = self.access_to_labels_individually,
hull_borderwidth = 2,
hull_relief = 'ridge',
)
self.checkbuttons.pack(side = 'left', expand = 1, padx = 10, pady = 10)
# Add some buttons to the checkbutton RadioSelect
self.cb1 = self.checkbuttons.add('Primary')
self.cb2 = self.checkbuttons.add('Secondary')
self.cb3 = self.checkbuttons.add('Tertiary')
# Bind the Balloon instance to each widget
self.balloon.bind(self.cb1, 'Primary:\n This is our primary service')
self.balloon.bind(self.cb2, 'Secondary:\n This is our primary service')
self.balloon.bind(self.cb3, 'Tertiary:\n This is our primary service')
# You can use getvalue() or getcurselection() to access individual labels
def access_to_labels_individually(self, tag, state):
print(self.checkbuttons.getvalue())
# Main program starts here
if __name__ =='__main__':
begueradj = Pmw.initialise(fontScheme = 'pmw1')
begueradj.title('Billal BEGUERADJ')
d = Begueradj(begueradj)
begueradj.mainloop()
Demo
(I am keeping the same screenshots because the above program produces the same results)
Here are screenshots of the running program related to the mouse hovering over each tkinter.Checkbutton() instance whether it is selected or not:

accessing the return value of function that was bound to an event (tkinter)

Basically, what I've done is bound a click event to a function. For example:
self.button1.bind("<Button-1>",self.chooseDice)
What I want now is to access the result of chooseDice() in another function. What is the best way to go about doing that?
class GraphicsInterface:
#we want to initialize the game board here, set up the dice and buttons
def __init__(self):
self.window = Tk()
self.window.geometry("720x500")
#buttons under each die
self.clicked=[] #empty list to collect all the buttons that were clicked (see chooseDice function)
self.button1 = Button(self.window, text="Dice 1", width=13) #create the button object
self.button1.place(x=60, y=160)
#bind button click event to a function (chooseDice())
self.button1.bind("<Button-1>",self.chooseDice)
self.button2 = Button(self.window, text="Dice 2", width=13)
self.button2.place(x=185, y=160)
self.button2.bind("<Button-1>",self.chooseDice)
#using the event as an argument, append the text to the clicked list
def chooseDice(self, event):
self.clicked.append(event.widget.cget('text'))
self.diceList=[] #create a new empty list
for i in range(len(self.clicked)):
self.diceList.append(int(self.clicked[i][5])) #get just the int value of the last character (i.e. the dice number)
self.deactivate(event.widget) #deactivate the button
return self.diceList
You are already doing what you need to do. Your example code sets self.diceList to some value. Anywhere else in your code you can directly use self.diceList.
By the way -- you're writing code that is going to be hard to maintain over time. For example, what if you change the dice label to "Dice One" or simply "One" rather than "Dice 1"? Or, as your app progresses you might want graphical images instead of text on the buttons. You'll have to modify the code that parses the button name. You are essentially encoding information in a button label which is not a good idea.
A simple solution, that also makes your chooseDice method simpler and easier to understand, is to pass in the dice number in the callback. For example:
self.button1.configure(command=lambda btn=self.button1: self.chooseDice(btn, 1))
The above passes two parameters to the chooseDice method: the button instance (so you can disable it) and the button number (so you don't have to parse the button name to get it)
This also allows you to create your dice in a loop rather than hard-coding multiple copies of the same block of code. Here's a complete working example:
from Tkinter import *
class GraphicsInterface:
def __init__(self):
self.window = Tk()
self.window.geometry("720x500")
self.clicked=[]
self.buttons = []
for n in range(1, 3):
btn = Button(text="Button " + str(n))
btn.configure(command=lambda btn=btn, n=n: self.chooseDice(btn, n))
btn.pack()
self.buttons.append(btn)
btn = Button(text="Go!", command=self.go)
btn.pack()
self.window.mainloop()
def go(self):
print "buttons:", self.clicked
self.reset()
def reset(self):
'''Reset all the buttons'''
self.clicked = []
for button in self.buttons:
button.configure(state="normal")
def chooseDice(self, widget, number):
self.clicked.append(number)
widget.configure(state="disabled")
app = GraphicsInterface()
Finally, some last bits of advice:
Don't use place, it makes your GUIs harder to create, and they won't react well to changes in window size, changes in font, changes in platform, etc. Use pack and grid instead. Also, don't create fixed-width buttons. Again, this is to better handle changes in fonts. There are times when you want fixed width buttons, but it doesn't look like your code has any reason to use them.
Finally, I don't know what you're actually trying to accomplish, but usually if you're using buttons to track state (is this thing pressed or not?) you want to use checkboxes (pick N of N) or radiobuttons (pick 1 of N). You might want to consider switching to those instead of to buttons.
Refactor. Split this into two functions.
One returns the proper result, usable by other objects.
The other is bound to a GUI control, and uses the proper result to activate and deactivate GUI objects.
Indeed, you should always do this. You should always have functions that do normal Python stuff, work correctly without the GUI and can be unit tested without the GUI. Then you connect this working "model" to the GUI.
just add a self.result attribute to your class and set it at chooseDice()

Categories