Difference between .pack and .configure for widgets in TkInter? - python

I am currently studying a text to try and teach myself more about TkInter as I'm trying to improve my Python 3 programming. The text can be found here, if necessary: http://www.ferg.org/thinking_in_tkinter/all_programs.html
In the section labelled "tt040.py" there is an example code, part of it is:
self.button1 = Button(self.myContainer1)
self.button1["text"] = "Hello, World!" ### (1)
self.button1["background"] = "green" ### (1)
self.button1.pack()
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Off to join the circus!") ### (2)
self.button2.configure(background="tan") ### (2)
self.button2.pack()
self.button3 = Button(self.myContainer1)
self.button3.configure(text="Join me?", background="cyan") ### (3)
self.button3.pack()
The explanation for this part of the code is :
"(2) For button2, the process is essentially the same as for button1, but instead of accessing the button's dictionary, we use the button's built-in "configure" method.
(3) For button3, we see that the configure method can take multiple keyword arguments, so we can set multiple options in a single statement."
What does the explanation actually mean? As in, what is the actual difference (with .pack) or need for the .configure method? What is meant by "the button's dictionary"?

Tkinter objects attribute are not handled through python attribute mechanism (ie you can not do self.button1.text = "hello"). Instead, tkinter provide two ways to alter this attribute:
use the object as a dictionnary: self.button1["text"] = "hello"
use the config method with named argument: self.button1.config(text="hello")
Both are equivalent. Note that you could also have passd such initialisation value through constructor named argument to perform both instanciation an initialisation in one step: self.button1 = Button(self.myContainer1, text="hello")
pack serve a totally different purpose. It is a geometry management instruction. Used without argument button1.pack() ask to place button1 in its parent widget below the precedent sibling (if any). You can use options to specify relative position, or resize behavior.
There are other geometry manager for tkinter: grid and place, see this response for a comparison.

Each widget has a dictionary of attributes (text, background, ...). You can access it using the regular dictionary syntax, as in self.button1["text"] = "Hello, World!" or using the configure method that you see in the other examples. That's just to set up the looks and behaviour of the widget.
Once you're done, you call pack to let Tkinter now that the widget is ready to be used. Then it will be displayed, etc.
You can see this by executing Tkinter commands step by step in the interpreter, like this:
>>> from Tkinter import *
>>> root = Tk()
>>> bt = Button(root)
>>> bt['text'] = 'hello'
>>> bt.pack()

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

How to change a global variable without global keyword using a button in tkinter?

I am making a rock paper scissors program and I need to change whose turn it is when they click a button, but I do not want to use the global keyword because the program is inside of a function.
Here is an example of what I am trying to do without using the global keyword:
from tkinter import *
root = Tk()
var = 1
def buttonClick():
global var
var += 1
print(var)
button = Button(root, text="button", command=buttonClick).pack()
root.mainloop()
I have tried to write command=(var += 1) but that did not work.
If the whole script is inside a function (including the buttonClick() function) then use the nonlocal keyword:
def buttonClick():
nonlocal var
var += 1
print(var)
If the function is not nested, the only way is to create a global variable and the global keyword in both functions.
No, you indeed can't. It would be possible to change the contents of a global varif it were a list, for example. And then you could write your command as a lambda expression with no full function body.
But this is not the best design at all.
Tkinter event model couples nicely with Python object model - in a way that instead of just dropping your UI components at the toplevel (everything global), coordinated by sparse functions, you can contain everything UI related in a class - even if it will ever have just one instance - that way your program can access the var as "self.var" and the command as "self.button_click" with little danger of things messing up were they should not.
It is just that most documentation and tutorial you find out will have OOP examples of inheriting tkinter objects themselves, and adding your elements on top of the existing classes. I am strongly opposed to that approach: tkinter classes are complex enough, with hundreds of methods and attributes -wereas even a sophisticated program will only need a few dozens of internal states for you to worry about.
Best thing is association: everything you will ever care to access should eb a member of your class. In the start of your program, you instantiate your class, that will create the UI elements and keep references to them:
import tkinter as tk # avoid wildcard imports: it is hard to track what is available on the global namespace
class App:
def __init__(self):
self.root = tk.Tk()
self.var = 1
# keep a refernce to the button (not actually needed, but you might)
self.button = tk.Button(self.root, text="button", command=self.buttonClick)
self.button.pack()
def buttonClick(self):
# the button command is bound to a class instance, so
# we get "self" as the object which has the "var" we want to change
self.var += 1
print(self.var)
def run(self):
self.root.mainloop()
if __name__ == "__main__": # <- guard condition which allows claases and functions defined here to be imported by larger programs
app = App()
app.run()
Yes, you can. Here's a hacky way of doing it that illustrates that it can be done, although it's certainly not a recommended way of doing such things. Disclaimer: I got the idea from an answer to a related question.
from tkinter import *
root = Tk()
var = 1
button = Button(root, text="button",
command=lambda: (globals().update(var=var+1), print(var)))
button.pack()
root.mainloop()

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

Change label text from variable

I am trying to write a program that has twenty five buttons, when one is pressed, it will read from a text file, store it in a variable, then make the text of the label at the bottom of the page change to the text of the text file. Here is my code so far:
from Tkinter import*
box1 = 'C:/Users/Geekman2/Documents/Tests/box1.txt'
var = StringVar()
var.set("man")
def openfile(filename):
filetxt = (open(filename,"r").read())
#filetxt.set(iletxt)
print filetxt
return filetxt
def Box1():
openfile(box1)
openfile(box1)
donut = Tk()
donut.geometry('450x450')
cupcake = Button(donut,text = "Box #1", command= Box1 )
cupcake.pack()
Whatsin = Label(donut,textvariable = var)
Whatsin.pack(side =BOTTOM)
donut.mainloop()
These two lines are giving me trouble, whenever I uncomment them and try to run the program I get the error "AttributeError: 'NoneType' object has no attribute 'tk'"
var = Stringvar()
var.set("man")
Can anyone tell me what might be the cause of this? I know what the error means, but as far as I can tell it doesn't apply in this situation
You need to instantiate an instance of Tk before you can use StringVar. Move donut = Tk() before your lines and it should work.
StringVar (as well as other Tkinter variable) are wrappers around Tcl variable1.
Your error come from creating a StringVar before the Tcl interpreter is initialized.
Thus you might call Tk()(which perform such initialisation) before creating your variables.
If you look at StringVar constructor signature: __init__(self, master=None, value=None, name=None) you see that as other Tkinter objects, the constructor accepts a master as first argument. This master is essentially needed to access the Tcl interpreter. If not provided, there is a fallback to a global Tkinter.Tk instance _default_root, which is None in your case. Asking the Tcl interpreter (field named tk) on it raise the AttributeError.
Note that for widgets, not providing master lead to the creation of a default one, but not on variables.
1 the whole Tkinter toolkit is a wrapper around a Tcl toolkit called Tk. Tcl variables allow to be traced, ie bind callback on variable change. Tk heavily use this mechanism and thus, Tkinter has to provide access to Tcl variables.

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