Python themed tkinter: Entry variable will not set - python

The following Python 3 code has a label and an Entry field that are correctly initialized with a string ("junk" in the example). But when the second "import" line is uncommented to replace the old Entry widget with the new themed widget, the label and Entry fields are not initialized.
Any clue why the themed widget initialization is broken?
from tkinter import *
# from tkinter.ttk import *
class myApp:
def __init__(self, root):
v = StringVar()
v.set("junk")
label = Label(root, textvariable=v)
label.pack()
text_entry = Entry(root, textvariable=v)
text_entry.pack()
root = Tk()
root.title("MyApp")
app = myApp(root)
root.mainloop()

The problem is that v is a local variable. When it goes out of scope (ie: when __init__ finishes executing), v is getting garbage-collected. Change v to self.v and the problem goes away.
Why you see the problem with the ttk Entry widget and not the standard one, I don't know. I guess one is just more sensitive to the garbage collector, or perhaps importing both libraries somehow triggers the garbage collector sooner. Regardless, even with the stock widgets you would eventually have some sort of problem because v will always eventually get garbage-collected.

Related

Tkinter: ttk.Label displaying nothing when given StringVar as textvariable, inside a class

I am trying to use the textvariable attribute of ttk.Label to display & update text according to a given StringVar.
from tkinter import *
from tkinter import ttk
class RenderEvent():
def __init__(self, root):
self.root = root
self.frame = ttk.Frame(self.root, padding="20 20 20 20")
self.frame.grid(column=0, row=0, sticky=(N, W, E, S))
self.dialogue = StringVar(self.frame, value="Placeholder")
L = ttk.Label(self.frame, textvariable=self.dialogue)
L.grid(column=1, row=2, sticky=W)
self.dialogue.set("some text here")
And for reference, root is passed in from another file which looks like this, and is used to start the application:
from tkinter import *
from tkinter import ttk
from renderevent import RenderEvent
root = Tk()
RenderEvent(root)
root.mainloop()
If I use text instead of textvariable in the Label creation, it displays a static string just fine. However, once it is set to textvariable (as shown above), nothing will be displayed at all.
I have tried the same with giving the StringVar() no parameters at all in initialization, or passing in self.root.
Emulating this code outside of a class seems to work as intended (the text appears and updates along with the textvariable), but I can't think of why having a class would cause an issue like this.
The reason is that you aren't keeping a reference to the instance of RenderEvent. Since you don't save a reference, python's garbage collector will try to clean it up which causes the variable to be cleaned up as well. The ttk widgets are more sensitive to this than the normal tk widgets.
The simple and arguably best solution is to assign the result of RenderEvent(root) to a variable so that it isn't affected by python's memory management.
re = RenderEvent(root)

Entanglement between Tkinter Checkbuttons

Hey so i'm making a program that has a checkbutton on the main window and a toplevel window that has one aswell. the problem is that for some reason the toplevel checkbutton affects the state of the main checkbutton, or the main checkbutton mimics the top level one (if you check/uncheck the toplevel one, the main one checks/unchecks aswell). Here's an example code which displays the problem:
import tkinter as tk
def toplevel():
top = tk.Toplevel()
top.geometry('200x50')
top_chekbutton = tk.Checkbutton(top, text='top')
top_chekbutton.pack()
top.mainloop()
main = tk.Tk()
main.geometry('200x50')
open_top = tk.Button(main, text='open top', command=toplevel)
main_checkbutton = tk.Checkbutton(main, text='main')
main_checkbutton.pack()
open_top.pack()
main.mainloop()
i didn't define the state variables because they don't seem to be the source of the problem. i'm using python 3.7.7 and tkinter 8.6 on win10.
plz help :(
As a general rule of thumb, every instance of Checkbutton should have a variable associated with it. If you don't, a default value will be used that is identical for all Checkbuttons. All widgets that share the same variable will display the same value.
You can verify this yourself by printing out the value of top_chekbutton.cget("variable") and main_checkbutton.cget("variable"). In both cases the value is "!checkbutton" (at least, with the version of python I'm using).
So, assign a variable for your checkbuttons, such as a BooleanVar, IntVar, or StringVar.
main_var = tk.BooleanVar(value=False)
main_checkbutton = tk.Checkbutton(main, text='main')

Tkinter Checkbutton not updating when changing variable

I have a tkinter GUI that I'm working on in Python 3.8 on my Macbook. I've encountered a problem where changing the variable associated with a checkbutton doesn't change the appearance of the checkbutton itself. I'd like the checkbutton to show up as checked if I set the IntVar() associated with it to 1, and from everything I've read, this should be happening.
Here's some extremely simplified code showing the problem:
import tkinter as tk
class Window():
def __init__(self, master):
var = tk.IntVar()
checkbutton = tk.Checkbutton(master, variable=var)
checkbutton.pack()
var.set(1)
root = tk.Tk()
Window(root)
root.mainloop()
When I run the script, the checkbutton isn't checked. I am still able to check the checkbutton by clicking on it though. Is this a known bug or am I missing something?
Solved: The issue, as jasonharper pointed out, was garbage collection. The tkinter variable wasn't being used for anything and was just being stored as a local variable, so it was thrown out and couldn't be referenced by the checkbutton. Saving the IntVar somewhere that stuck around fixed the problem. One solution was saving the variable in the var attribute of the checkbutton itself:
import tkinter as tk
class Window():
def __init__(self, master):
var = tk.IntVar()
checkbutton = tk.Checkbutton(master, variable=var)
checkbutton.pack()
var.set(1)
checkbutton.var = var
root = tk.Tk()
Window(root)
root.mainloop()

I want to get value of Entrybox and Checkbutton but I get nothing why? (I am new to tkinter)

I want to get value of Entrybox and Checkbutton but I get nothing why? (I am new to tkinter)
from tkinter import *
def m1():
m1 = Tk()
entry_val = StringVar()
check_val = IntVar()
Entry(m1, textvariable=entry_val).pack()
Checkbutton(m1, text='CheckButton', variable=check_val).pack()
def show():
print(entry_val.get())
print(check_val.get())
Button(m1, text='click!', command=show).pack()
m1.mainloop()
def main():
main = Tk()
Button(main, text='click! (main)', command=m1).pack()
main.mainloop()
main()
The short and simple answer:
In your code you need to change m1 = Tk() to m1 = Toplevel(). This will fix your issue.
The long answer:
When writing a Tkinter GUI 99.99% of the time you are only ever going to use 1 tkinter instance Tk(). The reason for this is that each instance of Tk() is contained within its own personal "Sandbox". Meaning it cannot play with others. So one instance of Tk() cannot communicate with a separate Tk() instance.
It is my understanding that if you do not specify what instance a method belongs to within the method then it will default to the 1st instance of Tk(). So the StringVar() and IntVar() you have create cannot be printed due to them belonging to main. Because main cannot talk to m1 you cannot update this value.
We can actually test this if you change:
entry_val = StringVar()
check_val = IntVar()
To:
entry_val = StringVar(m1)
check_val = IntVar(m1)
You will see your variables update properly.
Or if you change m1 = Tk() to m1 = Toplevel() (the correct solution) you will see that everything works as needed.
Toplevel() is specifically designed for creating new windows in tkinter so everything can stay in the same "Sandbox" and work together.

Tkinter GUI: Adding new entry boxes using .grid() when button is pressed

I'm trying to create new entry boxes when the "ADD entry" is used for my program. I do realize that the 'pack codes' works when I run it individually, but when I combine with existing program which is in grid(), the window is not showing when I run my program.
I also understand that we should not to use both .pack() when I have other things using .grid() in the same program. Hence, my question is, how do I create new entry boxes in grid.
I have tried finding elsewhere but they all suggested pack. For instance: I have looked here here, etc etc, to name a few; but couldn't find anything similar to mine. I would like to add entry boxes below the current entry boxes which is at row 3.
I'm kind of new to Python; (am using Python 2.7 and tkinter module in this program. Thank you very much for the help!
My simplified error codes are as follows:
from Tkinter import *
import tkFileDialog
import tkMessageBox
import Tkinter
import os
class Window:
def __init__(self, master):
self.filename3=""
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
bar=Entry(master).grid(row=3, column=3)
self.entryText3 = StringVar()
self.bar = Entry(root, textvariable=self.entryText3).grid(row=3, column=3)
#BUTTONS
self.cbutton= Button(root, text="OK", command=self.process_csv)
self.cbutton.grid(row=15, column=6, sticky = W + E)
####PROBLEM FROM HERE#####
all_entries = []
addboxButton = Button(root, text='ADD', fg="BLUE", command=self.addBox)
addboxButton.pack()
#I have also tried replacing the last 2 lines with the following 2 lines instead but to no avail:
#self.addboxButton = Button(root, text='ADD THA-ID', fg="BLUE", command=self.addBox)
#self.addboxButton.grid(row=3, column=6)
frame_for_boxes = Frame(root)
frame_for_boxes.pack()
def addBox(self):
print "ADD"
next_row = len(all_entries)
lab = Label(frame_for_boxes, text=str(next_row+1))
lab.grid(row=next_row, column=0)
ent = Entry(frame_for_boxes)
ent.grid(row=next_row, column=0)
all_entries.append( ent )
def process_csv(self):
#own program here
print "program"
root = Tk()
window=Window(root)
root.mainloop()
There are few issues with your program other than the one you stated:
Inside the initializer (__init__()) you attached the widgets to root which is not defined within the scope of your Window class. The reasonable way to fix this issue is when you use an instance of Tk(), id est root id est master in Window class, make it as an instance variable. This means the first thing you have to do in the inititializer is this : self.master = master. This will result in you having to replace all root occurrences within __init__() by self.master
The second issue to fix is the one you specified in your question's title: you can not mix the grid() and pack() layout managers for the same widget container. You have to decide which one. Since you placed most of the widgets using grid(), I suggest you to get rid of pack(). This means:
addboxButton.pack() becomes, for example, addboxButton.grid(row=0, column=1)
frame_for_boxes.pack() becomes, for example, frame_for_boxes.grid(row=0, column=0)
The previous list item fixes the problem but it will make you discover other issues within your program which are related:
NameError: global name 'all_entries' is not defined
NameError: global name 'frame_for_boxes' is not defined
This is because those widget variables are not reachable within the scope of addBox() function. To resolve this issue, you have to make those 2 elements as instance variables. This means:
all_entries = [] becomes self.all_entries = []
frame_for_boxes = Frame(root) becomes self.frame_for_boxes = Frame(self.master) (remember we replaced root by self.master in 1.)
The consequence of this error fixing is that you have to use all over inside your program:
self.all_entries instead all_entries
self.frame_for_boxes instead of frame_for_boxes
For scalability reasons, I think you will have at least to make the rest of widgets as instance variables (i.d. prefix them with the self keyword)
As your real project is more complicated than what you show in this MCVE, I encourage you to adopt the SaYa idiom when creating and placing widget elements. This means you will need to replace:
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
by
self.csvfile = Label(self.master, text="NUMBERS")
self.csvfile.grid(row=3, column=0)
To avoid unexpected bugs in your program, you must do the same for the remaining widgets you declared in the inititialzer.
There are also other things I would like to mention, but most of them are available on PEP8
What you have to do it to create a command which creates the entries and stores the new entries inside of a variable.
In my case, I use Entry_i and store in Entries but you can use self.Entries to make communication easier. (python 3.5)
def Make_Entry(self, root, Entries, x, y):
Entry_i = Entry(root, bd = 5)
Entry_i.grid(row = x, column = y, sticky = W+E+N+S)
Entries.append(Entry_i)
return(Entries, x+1, y+1)

Categories

Resources