Tkinter unexpected behavior - python

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.

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

Define pressed button of drop-down list in Tkinter 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.

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

Categories