I am building a simple GUI with multiple entry boxes, check boxes, etc. and came across a very peculiar behavior in the Tkinter.Entry class and I'm wondering if anyone else has run into it and/or if I'm just doing something silly.
I created a simple class to wrap each Tkinter.Entry object and interface with. I wanted to implement a way to change how wide each box is, so I added a parameter width to the class. When I did this, all of my boxes became "linked" and when I type into one box, I typed into every box. before I implemented this functionality it worked fine and when I take it out it works again. Here is my class:
import Tkinter as tk
class EntryBox:
def __init__(self, master, row, column, default_val="", width=20):
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
self.e.grid(row=row, column=column, sticky=tk.E)
self.e.insert(0, self.default_val)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Here is my program WITHOUT the width parameter included:
Here is my program WITH the width parameter included:
As you can see, all of the default values filled in to every box and whenever I edit one, I edit all of them.
Here is how I instantiate each object (I don't suspect this to be the issue, but just to be thorough):
import Tkinter as tk
ip_addr_box = EntryBox(root, 1, 1, default_val="192.168.201.116")
ip_addr_label = tk.Label(root, text="IP Address").grid(row=1, column=2, sticky=tk.W)
# set up the IP port entry box
ip_port_box = EntryBox(root, 2, 1, default_val="8000")
ip_port_label = tk.Label(root, text="IP port").grid(row=2, column=2, sticky=tk.W)
# set up the number of plot points scroll box
# set up the filename entry box
filename_box = EntryBox(root, 4, 1, default_val="log.xlsx")
filename_label = tk.Label(root, text="File Name").grid(row=4, column=2, sticky=tk.W)
# set up how long the test lasts
meas_time_box = EntryBox(root, 3, 4, default_val="5", width=10)
meas_time_label = tk.Label(root, text="Measurement Period")
meas_time_label.grid(row=3, column=5, columnspan=2, sticky=tk.W)
test_time_box = EntryBox(root, 4, 4, default_val="30", width=10)
test_time_label = tk.Label(root, text="Test Duration").grid(row=4, column=5, columnspan=2, sticky=tk.W)
My guess is that this is a weird bug in Tkinter or something to do with namespaces that I don't know enough about.
EDIT: I updated the class code to specify how I don't include the width parameter. I just don't even include it as a named argument when calling tk.Entry.
Remove the attribute text='cb_text'. I don't know what you think that's doing, but text is just an abbreviation of textvariable, so using it is the same as doing Entry(..., textvariable='cb_text', textvariable=self.var, ...).
Apparently there's a bug in how the tkinter Entry widget processes named arguments, and it is triggered when you add the width argument. Every entry widget ends up with the textvariable attribute set to "cb_text", meaning they all share the same storage for the value.
Inheriting from the Tkinter Entry class works well. I have slightly manipulated your code.
import Tkinter as tk
class EntryBox(tk.Entry):
def __init__(self, master, row, column, default_val="", width=20):
tk.Entry.__init__(self, master, text="cb_text")
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
# self.e.grid(row=row, column=column, sticky=tk.E)
self.config(textvariable=self.val, width=self.width)
self.insert(0, self.default_val)
self.grid(row=row, column=column, sticky=tk.E)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Unfortunately I don't have enough rep points to comment directly.
Related
So far I have tried the advice from u/OA998
"""https://www.reddit.com/r/learnpython/comments/45h05k/solved_kernel_crashing_when_closing_gui_spyder/
My code is supposed to just open up a window with two labels and an entry field. The code works fine if I restart the Kernel but just running it after closing the window will result in an window popping up that has no text (from the StringVar). The third time it doesn't even open anymore. I'm not quite sure what causes this."""
Code:
""" create a GUI inside a class (this allows variables to be added and changed by several parts in the GUI). By using the “self” keyword we can access the attributes and methods of the class in python. It binds the attributes with the given arguments.
from tkinter import *
""" create a GUI inside a class (this allows variables to be added and changed by several parts in the GUI)"""
""" By using the “self” keyword we can access the attributes and methods of the class in python. It binds the attributes with the given arguments."""
class TESTGUI:
def __init__(self, window):
"""We will use an entry widget, and StringVar to keep track of the current text in the
box, and a label to display a message."""
"""labelpositioning"""
margin = 2
self.spacer = Label(window, width=margin, height=margin)
self.spacer.grid(column=0,row=0)
"""for toogle button create StringVar in tkinter class to hold the text"""
"""labeltexts"""
self.labelText_1 = StringVar()
self.labelText_1.set("begin experiment")
self.labelText_2 = StringVar()
self.labelText_2.set("calibrate")
"""labelformat"""
self.label = Label(window, textvariable=self.labelText_1, width=12, height=3, borderwidth=3, relief=SOLID)
self.label.grid(column=1, row=1)
self.label = Label(window, textvariable=self.labelText_2, width=12, height=3, borderwidth=3, relief=SOLID)
self.label.grid(column=2, row=1)
"""labelbutton"""
self.button = Button(window, text="press", command=self.pressed_button_1)
self.button.grid(column=1,row=2)
self.button = Button(window, text="press", command=self.pressed_button_2)
self.button.grid(column=2,row=2)
"""Entry(password)Label"""
self.entryLabel_1 = Label(window, text = "enter your password")
self.entryLabel_1.grid(column=0,row=3)
""" Next add a StringVar to hold the password and Entry box to type the password."""
""" The trace function will call a checkStrength() function when the StringVar is changed."""
self.password = StringVar()
self.password.trace("w", lambda name, index, mode, password=self.password:self.checkStrenght())
self.entry = Entry(window, textvariable=self.password)
self.entry.grid(column=1, row=3)
""" then create the StringVar to hold the strength string, and the label to display it. """
self.strenghtText = StringVar()
self.strenghtText.set("")
self.strenghtLabel = Label(window, textvariable=self.strenghtText, width=10)
self.strenghtLabel.grid(column=3, row=3)
"""CheckStrenghtFunctionOfEntry"""
def checkStrenght(self):
lenght = len(self.password.get())
if lenght == 0:
self.strenghtText.set("")
self.strenghtLabel.config(bg="SystemWindowBody")
elif lenght >= 1:
self.strenghtText.set("strong")
self.strenghtText.config(bg = "green3")
"""ButtonFunction"""
def pressed_button_1(self):
if self.labelText_1.get() == "begin experiment":
self.labelText_1.set("abort experiment")
else:
self.labelText_1.set("begin experiment")
def pressed_button_2(self):
if self.labelText_2.get() == "calibrate":
self.labelText_2.set("recalibrate")
else:
self.labelText_2.set("calibrate")
""" define Variables for window size """
width=300
height=300
""" define tk for used variable to use tkinter class """
if __name__ == "__main__":
window = Tk()
window.minsize(width, height)
window.title("Photonics Lab")
gui = TESTGUI(window)
window.mainloop()
From tutorials, I have kind of been under the impression that grid just "kind of figures it out" for width, but it's clearly not the case when it comes to Text (I suppose when combined with list).
With the following code, listbox has is tiny and Text is absolutely massive (Width wise). I can make the listbox equal to the size of the Text by changing sticky="ew", but that's not what I want - I want a reasonable, equivalently styled "grid".
If I hardcode the size of the width, it's even more frustrating, because listbox width seems to equate to approximately 2/3 of Text width.
I've read up on rowconfigure and columnconfiugre, but this seems to actually do nothing with the below code (note - rowconfigure and columnconfigure are not in the below code, but I have tried them, perhaps I'm using them wrong).
Anyways, with the below code - can anyone explain to me how to make these more reasonably sized width wise, and also the same? Should I hardcode a width to Text and then set listbox to sticky="ew"? Seems counter intuitive to the grid layout concept.
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.frame = tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
self.lst_bx = tk.Listbox(self.parent, height = 15)
self.lst_bx.grid(row=0, column=0, columnspan=2, padx=(10,10), sticky="w")
self.exe_field = tk.Text(self.parent, height=15)
self.exe_field.grid(row=1, column=0, columnspan=2, padx=(10,10), sticky="w")
self.pick_exe_btn = tk.Button(
self.parent, text="Choose Location", width=15
)
self.pick_exe_btn.grid(row=0, column=2)
if __name__ == "__main__":
root = tk.Tk()
#window_config(root)
MainApplication(root).grid(stick="nesw")
root.resizable(False, False)
root.mainloop()
I'm sorry for being such a noob, I swear I have searched this a lot before posting here - I just cannot find a straight answer on why ListBox is a completely different width from Text (and even moreso when I specify a width).
The how tkinter calculates the dimensions tkinter.ListBox() and tkinter.Text() widgets is a little bit different from most of other widgets where it uses pixels. For these 2 specific widgets, the width is calculated in terms of characters whereas the height is assessed in terms of lines.
You did not set the width of self.lst_bx, so tkinter sets it by default to 20 characters.
You did not specify the width of self.exe_field either. So tkinter calculates this width based on the current default font size.
From 1) and 2) we can conclude that it is quite normal we can not expect from self.exe_field to have the same width as self.lst_bx. This means, you have no option other than hard coding them and visually check the GUI.
With minor changes of your code (mainly provided in the comments below your question) this is how I solved your issue:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
self.lst_bx = tk.Listbox(self, height = 15, width=70)
self.lst_bx.grid(row=0, column=0, padx=(10,10), sticky="w")
self.exe_field = tk.Text(self, height=15, width=80)
self.exe_field.grid(row=1, column=0, padx=(10,10), sticky="w")
self.pick_exe_btn = tk.Button(self, text="Choose Location", width=15)
self.pick_exe_btn.grid(row=0, column=2, columnspan=2)
if __name__ == "__main__":
root = tk.Tk()
#window_config(root)
MainApplication(root).grid(stick="nesw")
root.resizable(False, False)
root.mainloop()
Note where I moved the columnspace option (it quite does not make a sens in the two places where you have set it previously). Note also that tkinter calculate the character somehow differently between the listbox and text widgets. Depending your operating system (and maybe machine also), you may have to change the 2 width dimensions I set to the widgets in questions.
Here is what I got on my machine:
I wanted to make a class that would be the template of a "custom widget" for a "game" I'm trying to make. The problem is that I can only get it to work if when I call the class, I specify the Frame object that holds everything together. I've been searching my whole afternoon and couldn't find a concrete answer...
The following code works but instead of just needing to write StatusButton().grid() I have to use StatusButton().frame.grid()
from tkinter import *
from tkinter.ttk import Progressbar
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__()
self.frame = Frame(master, padx = 10, pady = 10, bd= 5, relief = RAISED)
self.label = Label(self.frame, text = "Hunger Bar")
self.pgbar = Progressbar(self.frame)
self.button = Button(self.frame, text = "Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady = 5, ipadx = 15)
return
root = Tk()
buttonslist = [StatusButton(root) for x in range(16)]
for r in range(4):
for c in range(4):
StatusButton(root).frame.grid(row = r, column = c)
root.mainloop()
I'm guessing I'm not properly "conecting" the frame object to the class, even though the former is inside the latter, because when the previous code gets executed, but with StatusButton().grid() instead, the TKinter window pops up normally, but without any content, like if i was "gridding" an empty Frame object. How can I fix this, so that when StatusButton().grid() is run, my "custom widget thing" appears?
Sorry if this is a noob error, it's my first week in programming
The problem is that you aren't taking advantage of your subclass. You initialize Frame with no arguments and then create a new one with arguments. You then create widgets with self.frame as a parent instead of self. Change it to this:
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__(master, padx=10, pady=10, bd=5, relief=RAISED)
self.label = Label(self, text="Hunger Bar")
self.pgbar = Progressbar(self)
self.button = Button(self, text="Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady=5, ipadx=15)
I have a problem i want to put a image as the background for this little converter can someone help me? I was looking online and on stackoverflow but none of the things i found would work with the class.
__author__ = 'apcs'
from tkinter import *
class FarenheitToCelsius(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Farenheit To Celsius Conversion")
self.grid()
self.farenheitLabel = Label(self, text="Farenheit")
self.farenheitLabel.grid(row=0, column=0)
self.farVar = DoubleVar()
self.farEntry = Entry(self, textvariable=self.farVar)
self.farEntry.grid(row=0, column=1)
self.celsiusLabel = Label(self, text="Celsius")
self.celsiusLabel.grid(row=1, column=0)
self.celVar = DoubleVar()
self.celEntry = Entry(self, textvariable=self.celVar)
self.celEntry.grid(row=1, column=1)
self.button = Button(self,
text="Convert to Celsius",
command=self.convertToFarenheit)
self.button2 = Button(self,
text="Convert to Farenheit",
command=self.convertToCelsius)
self.button.grid(row=2, column=1, columnspan=1)
self.button2.grid(row=2, column=0, columnspan=1)
def convertToFarenheit(self):
fare = self.farVar.get()
cels = (fare - 32) * 5 / 9
self.celVar.set(cels)
def convertToCelsius(self):
cel = self.celVar.get()
far = cel * 9 / 5 + 32
self.farVar.set(far)
def main():
FarenheitToCelsius().mainloop()
main()
I can think of at least three ways to do this:
Create an image in a label, and use place to put it in the frame. Then create all of the other widgets and use pack or grid as you normally would. Make sure you create the frame first, then the label with the image, then the children of the frame, so that the stacking order is correct.
Use a canvas instead of a frame, and use the create_image method to add an image. Then you can pack/place/grid children as normal.
Instead of a frame, use a label as the container, and then pack/place/grid widgets into the label (yes, you can add children to a label widget).
Been trying to pick up on Python (3.4) and this is my first posting on Stack Overflow.
I have questions regarding the behaviour of the textvariable option in the tkinter.Spinbox() widget constructor.
The following codes are working. It's a layout of 6 spinboxes, there are 3 pairs of spinboxes (each pair side by side). Each spinbox in a pair are suppose to be independent of each other, i.e. when one changes, it's not suppose to affect the other.
Pair A receives their textvariable parameter as type StringVar() (e.g. var = StringVar())
Pair B receives their textvariable parameter as string declared between quotes (e.g. var = '0')
Pair C receives their textvariable parameter as type integer (e.g. var = 0)
Note: I'd like to post a screen capture of the resulting window but the page said "You need at least 10 reputation to post images"... of which I apparently do not have at the moment...
I'd just like to understand what is the reason behind why the members of Pair C seems to be "non-independent" of each other? -> Change one, and the other follows the with the same changes made.
How is the intepretation for the textvariable parameter in the tkinter.Spinbox() different for different types of value assigned?
Here are my codes:
from tkinter import *
class spinModuleStrA():
''' spinModuleNum() - Spinbox taking textvariable as <StringVar() value> '''
def __init__(self, master, moduleName):
self.root = master
self.moduleName = moduleName
self.root.grid()
self.var = StringVar()
self.var.set('r')
self.label1 = Label(self.root, text=self.moduleName, bg=self.root["bg"])
self.label1.pack(expand = True)
self.spinBox1 = Spinbox(self.root ,from_=0, to=100, width=10, textvariable=self.var)
self.spinBox1.pack(expand = True)
class spinModuleStrB():
''' spinModuleNum() - Spinbox taking textvariable as <'' string value> '''
def __init__(self, master, moduleName):
self.root = master
self.moduleName = moduleName
self.root.grid()
self.var = ''
self.label1 = Label(self.root, text=self.moduleName, bg=self.root["bg"])
self.label1.pack(expand = True)
self.spinBox1 = Spinbox(self.root ,from_=0, to=100, width=10, textvariable=self.var)
self.spinBox1.pack(expand = True)
class spinModuleNum():
''' spinModuleNum() - Spinbox taking textvariable as <numerical value> '''
def __init__(self, master, moduleName):
self.root = master
self.moduleName = moduleName
self.root.grid()
self.var = 0
self.label1 = Label(self.root, text=self.moduleName, bg=self.root["bg"])
self.label1.pack(expand = True)
self.spinBox1 = Spinbox(self.root ,from_=0, to=100, width=10, textvariable=self.var)
self.spinBox1.pack(expand = True)
class app():
def __init__(self):
self.root = Tk()
self.root.geometry('300x300+500+200')
for i in range(2): # number of columns
self.root.columnconfigure(i, weight=1)
for i in range(3): # number of rows
self.root.rowconfigure(i, weight=1)
self.frame1 = Frame(self.root, bg='#f55')
self.frame1.grid(row=0, column=0, padx=10, pady=10, sticky=W+E+N+S)
self.module1 = spinModuleStrA(self.frame1, "Spin <StringVar()> A")
self.frame2 = Frame(self.root, bg='#faa')
self.frame2.grid(row=0, column=1, padx=10, pady=10, sticky=W+E+N+S)
self.module2 = spinModuleStrA(self.frame2, "Spin <StringVar() B>")
self.frame3 = Frame(self.root, bg='#5f5')
self.frame3.grid(row=1, column=0, padx=10, pady=10, sticky=W+E+N+S)
self.module3 = spinModuleStrB(self.frame3, "Spin <''> A")
self.frame4 = Frame(self.root, bg='#5fa')
self.frame4.grid(row=1, column=1, padx=10, pady=10, sticky=W+E+N+S)
self.module4 = spinModuleStrB(self.frame4, "Spin <''> B")
self.frame5 = Frame(self.root, bg='#55f')
self.frame5.grid(row=2, column=0, padx=10, pady=10, sticky=W+E+N+S)
self.module5 = spinModuleNum(self.frame5, "Spin <numerical> A")
self.frame6 = Frame(self.root, bg='#5af')
self.frame6.grid(row=2, column=1, padx=10, pady=10, sticky=W+E+N+S)
self.module6 = spinModuleNum(self.frame6, "Spin <numerical> B")
app1 = app()
The reason is irrelevant. Passing a string or integer to the textvariable option is incorrect usage. While it will sometimes work (depending on how you define "work"), it is incorrect. The textvariable requires one of the special variable classes provided by tkinter. Without using one of those, there's no point in defining the textvariable attribute at all.
However, to answer the specific question:
Pair A each get a unique textvariable, so it works as expected. The underlying tcl/tk engine gets a unique string for each instance, which is why the two are independent.
Pair B effectively gets no textvariable because you specify the empty string. To the underlying tcl/tk engine an empty string in this situation is equivalent to python's None. If, instead of '' you had used a non-empty string (eg: 'w00t'), you would notice the same behavior as pair C. This is because each spinbox gets a variable with the same name, which to the underlying tcl/tk engine means they are the same textvariable and thus tied together.
Pair C uses a constant 0 (zero). Because it is a constant, both widgets effectively get the same textvariable because ultimately this parameter becomes the name of an tcl/tk variable. Since the name is identical for both spinboxes, they are tied together.
Haha... I guess Pair B was not suppose to work after all. I didn't test it using a non-empty double-quote string.
From the way you put it, when passing any value which is not type StringVar() to Spinbox's textvariable, the value essentially was intepreted as the "name" or what I would describe as a pointer to a memory location that would be storing the value in the associated spinbox's text entry.
This seems to be the case after I tested the script again by:
adding a new class definition class spinModuleStrC()
modifying class spinModuleStrB()
appending a few more lines of codes to class app()
===============================================================
Here are the codes for the new class spinModuleStrC() (Let's associate this with a new "Pair D"):
class spinModuleStrC():
''' spinModuleNum() - Spinbox taking textvariable as <'' string value> '''
def __init__(self, master, moduleName):
self.root = master
self.moduleName = moduleName
self.root.grid()
# tested this with 'r' and then with 'p' (anything different from 'r')
self.var = 'r'
self.label1 = Label(self.root, text=self.moduleName, bg=self.root["bg"])
self.label1.pack(expand = True)
self.spinBox1 = Spinbox(self.root ,from_=0, to=100, width=10, textvariable=self.var)
self.spinBox1.pack(expand = True)
Modification to the class spinModuleStrB():
I changed self.var = '' to self.var = 'r'
And the modifications to the class app():
3a. modifying the for loop from configuring 3 rows to 4
for i in range(4): # number of rows
self.root.rowconfigure(i, weight=1)
3b. appending new codes to the end of the class app()
self.frame7 = Frame(self.root, bg='#5f5')
self.frame7.grid(row=3, column=0, padx=10, pady=10, sticky=W+E+N+S)
self.module7 = spinModuleStrC(self.frame7, "Spin <'p'> A")
self.frame8 = Frame(self.root, bg='#5fa')
self.frame8.grid(row=3, column=1, padx=10, pady=10, sticky=W+E+N+S)
self.module8 = spinModuleStrC(self.frame8, "Spin <'p'> B")
From observation, when I used self.var = 'p' in spinModuleStrC(), the spinboxes in Pair D will follow each other without intefering with the other Pairs A, B and C.
However, when I use self.var = 'r' in spinModuleStrC() - which is similar to the new spinModuleStrB(), the spinboxes in Pair B and Pair D will follow each other... a display of pointer-like behaviour in the NON-StringVar() type value passed to textvariable...
Hmmm... Perhaps this "unofficial" behaviour can be exploited