Define pressed button of drop-down list in Tkinter Python - python

I created a drop-down list using Menubutton from Python Tkinter, but i can't detect which button was pressed ('button-1', 'button-2' or 'button-3')
from Tkinter import *
widget = Frame()
widget.pack()
btnMenu = Menubutton(widget, text='Select action')
contentMenu = Menu(btnMenu)
btnMenu.config(menu=contentMenu)
btnMenu.pack()
btnList = ['button-1', 'button-2', 'button-3']
for btn in btnList:
contentMenu.add_command(label=btn, command=???)
mainloop()
What should i use for "command=" in the string
contentMenu.add_command(label=btn, command=???)
in order to define particular button? Thank you!

What you're looking for is lambda. You can use lambda in your command call like such:
contentMenu.add_command(label=btn, command = lambda btn=btn: buttonClicked(btn))
Then make a method called buttonClicked which would take one argument which would reflect which button has been pressed. Here's a minimal example of what that would look like:
def buttonClicked(btn):
print btn
Ideally though if each button has an entirely different set of execution instructions then they should each get their own method and perhaps you change the list to a tuple of (name, method). This is usually the case for why you would use a menubutton instead of an optionmenu. If you're simply calling the same method for all of them then you might want to consider switching to an optionmenu instead.

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()

Label appearing conditionally

I am trying to make a label appear if the condition of my entry (textbox) is met. Unfortunately I cannot see anything when I am pressing the button on the testing. Here is what I have:
from tkinter import *
main= Tk()
firstname=Entry(main).place(x=30, y=50)
def register():
if any(i.isdigit() for i in firstname.get())== True:
print (Label(main,text='no numbers please').place(x=30, y=180))
else:
print(Label(main, text='pass').place(x=40, y=170))
register=Button(main,text='REGISTER', command= lambda :register).place(x=300, y=200)
There are at least three problems with your code. The first is in how you define the button's command:
register=Button(main,text='REGISTER', command= lambda :register)
When you do command=lambda: register, you're telling the button "when you're clicked run the code register". register all by itself does nothing. Since register is (supposed to be) a function, you need to call it like register() inside the lambda.
Since you aren't passing any values to the function, the lambda is completely unnecessary. Instead, just directly reference the function: command=register without the parenthesis.
The second problem is that you've used the name register to be two different things: a function and a reference to a widget. Because of the ordering of the code, command=register or command=lambda: register() will try to call the button rather than the function.
The third problem is a very, very common mistake. In python, when you do x = y().z(), x is given the value of z(). Thus, register = Button(...).pack(...) returns the value of pack(...) and pack (and grid and place) always returns None.
Therefore, you've set register to None, and when you try to call it you get NoneType object is not callable.
In addition to fixing the command, you need to pick a different name for either the function or the button. And you should not be calling place (or pack or grid) in-line with creating the widget. They should be separate steps.
So, putting that all together, you need to define firstname like this so that firstname is not None:
firstname=Entry(main)
firstname.place(x=30, y=50)
And then you need to define the button like this:
register_button = Button(main,text='REGISTER', command= register)
register_button.place(x=300, y=200)

receiving selected choice on Tkinter listbox

I'm trying to build a listbox using Tkinter and receive the selected option by clicking it.
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
lst=Listbox(root, height=30, width=50)
lst.insert(1, "hy")
lst.insert(2, "hello")
lst.insert(3, "hey")
lst.pack()
sel = lst.curselection()
print sel
root.mainloop()
However, when I run the code it prints me an empty tuple before I pressed any choise.
Does someone know how to get the selected choise after I press one and not right after I run it?
Thanks a lot :)
You are getting the selection about a millisecond after creating the widget, well before the user has a chance to see the UI much less interact with it.
GUI programs are event based, meaning that things happen in response to events. Events are things like clicking buttons, inserting data into input widgets, and selecting items from listboxes.
You need to do one of two things: create a button or other widget which will get the selected item, or configure it so that a function is called whenever an item is selected.
No matter which solution you use, you will need a function that ultimately calls the curselection method of the listbox to get a list of indices. You can then call the get method to get the selected item or items.
Here's a function definition that will print the selected item, or print "no selection" if nothing is selected. So that it can be resused without modification. we'll define it to take the listbox as an argument.
Note: this example assumes the widget only supports a single select, to keep it simple:
def print_selection(listbox):
selection = listbox.curselection()
if selection:
print(f"selected item: {listbox.get(selection[0])}")
else:
print("nothing is selected")
Using a button
To call this from a button is straight-forward. We just create a button after we create the listbox, and use the command attribute to call the function. Since the function we wrote earlier needs a parameter, we'll use lambda to create a temporary function for the button.
button = tk.Button(root, text="Print Selected Item", command=lambda: print_selection(lst))
button.pack()
Calling the function when the selection is made
To call the function whenever the user changes the selection, we can bind a function to the <<ListboxSelect>> event. We'll create a separate function for this, and then pull the widget from the event object that is automatically passed to the function.
def print_callback(event):
print_selection(event.widget)
lst.bind("<<ListboxSelect>>", print_callback)
First of all, the reason you are getting an empty tuple is because you have executed the statements:
sel = lst.curselection()
print(sel)
before you have executed the root.mainloop()
Secondly, your setup for listbox fails to include a StringVar variable to hold your list.
Once the variable has been defined, you should be able to use the .insert statements to add your list items one at a time, or you can initialize the StringVar variable using a .set('hy', 'hello', 'hey') command.
To provide a return of a selected variable, you must incorporate an event handler to determine the list position selected onclick or some other triggering method.
For a pretty clear explanation of these characteristics check here

How can I set the focus to a widget (that isn't an entry) by clicking on it?

The reason I need to do this is because I need to change the text of a label without having to use entries; I want to use events instead.
I have tried this:
import tkinter as tk
root = tk.Tk()
root.bind("<Button-1>", lambda _: root.focus())
l = tk.Label(root, width=50, height=50, bg="white")
l.bind("<Button-1>", lambda _: l.focus())
l.bind("1", lambda _: l.config(bg="yellow"))
l.bind("2", lambda _: l.config(bg="white"))
l.pack()
root.mainloop()
When I ran the program, I expected to be able to change the colour of the label l to yellow by clicking on it (which I thought would set the focus to it) then pressing 1, and changing it back to white by pressing 2; provided that I didn't click outside of the label and set the focus to the root widget (where the keys 1 and 2 weren't bound to any callback).
I know that you can bind keys to callbacks (tested it), and I also know that it is possible to set the focus to widgets which aren't entries (tested that too), yet this doesn't seem to work.
Can anybody help me?
The problem is that you have two bindings for a button click: one on the label widget itself and one on the root window. Because of the way that events are processed, the binding on the root window will fire after the event on the label. That means that whatever focus you set on the label binding will get undone with the binding on the root window.
One solution is to change your binding on the click to set the focus to whatever was clicked on. With that, you don't need to set a binding on the label widget for a click.
root.bind("<Button-1>", lambda event: event.widget.focus_set())
Another solution would be to modify your binding on the label to prevent the binding on the root window from firing. You can do that by returning the string "break" from the function that is called.
def callback(event):
l.focus()
return "break"
l.bind("<Button-1>", callback)

Tkinter unexpected behavior

I have a set of methods in my program the use Tkinter that don't behave like I thought they would. I want to be able to push a button in the window and have more text fields appear, and be able to return a list of the results in the text fields. Here is what I have:
def expandChoice(self):
root = Tk()
choices = []
plusButton = Button (root, text='+', command=self.addChoice(root, choices))
plusButton.pack()
quitButton = Button (root, text='Ok', command=root.destroy )
quitButton.pack()
root.mainloop()
return choices
def addChoice(self, parent, variables):
variables.append(StringVar())
text = Entry(parent, textvariable=variables[len(variables)-1])
text.pack()
What happens is that one text field appears when the window loads (above the buttons), and the plus button does nothing. What am I doing wrong? It seems like the addChoice method get called automatically when the first button's constructor is called and then doesn't work after that.
The command option takes a reference to a callable. You, however, are calling addChoice immediately, then assigning what that retuns (None) to the command option.
You need to do something like Button(...command=self.addChoice)
If you need to pass arguments you will need to either use a lambda or functools.partial. Search for either of those on this site -- variations of this question has been asked and answered many times on SO.

Categories