StringVar().set() Not Adjusting StringVar - python

I'm a beginner learning Python and mucking around with the tkinter GUI stuff. I'm trying to make a very basic beginner project that allows a user to type something into a text box and click a button, whereupon that input is added to a label in another part of the window.
However, I'm running into an issue where the StringVar that I'm using as an output isn't being updated by the .set() command.
def __init__(self):
self.window = Tk()
self.window.title("Terminal Writer 9000!")
self.terminalString = StringVar()
self.terminalString.set("This is an example message.")
self.allcontent = ttk.Frame(self.window)
self.allcontent.grid(row=0, column=0, sticky="nwse")
self.mainframe = ttk.Frame(self.allcontent)
self.mainframe.grid(row=0, column=0, sticky = "nwse", columnspan=4, rowspan=5)
self.terminal = ttk.Label(self.mainframe, textvariable=self.terminalString, padding=10, relief="sunken")
self.terminal.grid(row=0, column=0, rowspan=5, columnspan=2, sticky="nwse")
# GUI setup for Buttons and Entry box omitted...
play = TerminalWriterApp()
play.window.mainloop()
However, the area used by the terminal Label is blank, even though it should display "This is an example message." While troubleshooting, I made this, which is basically a complete copy/paste of the functional elements of my original code:
from tkinter import *
from tkinter import ttk
window = Tk()
strvar = StringVar()
strvar.set("Test 2")
allcontent = ttk.Frame(window)
allcontent.grid(row=0, column=0, sticky="nwse")
mainframe = ttk.Frame(allcontent)
mainframe.grid(row=0, column=0, sticky="nwse", columnspan=4, rowspan=5)
text = Label(mainframe, text="Test 1")
text.grid(row=0, column=0, sticky="nwse")
text2 = Label(mainframe, textvariable=strvar)
text2.grid(row=1, column=0, sticky="nwse")
window.mainloop()
This code functions as intended, displaying a window with "Test 1" and "Test 2" on separate lines.
Does anyone know why the set() method wouldn't work in this context? (Also, feel free to get mad at my horrible code - I need to learn good habits somehow!)

For some reasons, the label appears when the app takes focus (when you click on it); maybe it is because of the stack nested frames, IDK.
You could use focus_force to constrain the OS to give focus to your app immediately.
from tkinter import *
from tkinter import ttk
class TerminalWriterApp:
def __init__(self):
self.window = Tk()
self.window.title("Terminal Writer 9000!")
self.terminalString = StringVar()
self.terminalString.set("This is an example message.")
self.allcontent = ttk.Frame(self.window)
self.allcontent.grid(row=0, column=0, sticky="nwse")
self.mainframe = ttk.Frame(self.allcontent)
self.mainframe.grid(row=0, column=0, sticky = "nwse", columnspan=4, rowspan=5)
self.terminal = ttk.Label(self.mainframe, textvariable=self.terminalString, padding=10, relief="sunken")
self.terminal.grid(row=0, column=0, rowspan=5, columnspan=2, sticky="nwse")
self.terminal.focus_force()
play = TerminalWriterApp()
play.window.mainloop()

Related

Is there a way to refresh a tkinter frame in which widgets have been reconfigured?

I'd like to allow the user to configure the appearance of the app (background color, font, font size and color, button color, etc). I've got all of the default settings saved into a config file, which the program interprets and saves into variables within a class for easy access between functions. Now, I can save the changes the user made into the config file and those changes will be reflected when the user closes and reopens the app, but I'd like the changes to be instantaneous, so I tried something like this:
import tkinter as tk
class SetColor:
def __init__(self, color):
self.color = 'green'
current = SetColor('green')
root = tk.Tk()
lbl_color = tk.Label(root, text='Choose button color')
lbl_color.grid(row=0, column=0, pady=5, padx=2)
btn_red = tk.Button(root, text='Red', bg=current.color, command=lambda:update_color('red'))
btn_red.grid(row=0, column=1, pady=5, padx=2)
btn_green = tk.Button(root, text='Green', bg=current.color, command=lambda:update_color('green'))
btn_green.grid(row=0, column=2, pady=5, padx=2)
btn_blue = tk.Button(root, text='Blue', bg=current.color, command=lambda:update_color('blue'))
btn_blue.grid(row=0, column=3, pady=5, padx=2)
def update_color(color):
current.color = color
#THIS is where I'm wondering if there's a way to refresh without individually updating each widget as I've done below
btn_red.config(bg=current.color)
btn_green.config(bg=current.color)
btn_blue.config(bg=current.color)
root.mainloop()
This does work, but in my actual app there are a loooooot more widgets that would need updating than in this^ example. So I have a feeling I'm missing something or going about this in the wrong way. Any help greatly appreciated :)
Your best bet is to store the buttons in a list and loop over that list. This way you can seperate different buttons. But if you are sure you want to change the color of every singlle button you can do: for widget in root.winfo_children():
if isinstance(widget, tk.Button):
widget.config(bg=current.color)
#Maarten's answer is perfect for the tkinter button.
There is another option using ttk.Button can be used in such scenario
create the button object with a custom style
btn_green = ttk.Button(root, text='Green', style="color.TButton", command=lambda: update_color('green'))
create style object
style = ttk.Style()
style.theme_use("default")
set the style
style.configure('color.TButton', background=current.color)
# Activate is when you mouse over the button.
style.map('color.TButton', background=[('active', current.color)])
Full example:
import tkinter as tk
from tkinter import ttk
class SetColor:
def __init__(self, color):
self.color = 'green'
def update_color(color):
current.color = color
# Let's set the style
# naming that style variable as color.TButton
# NOTE: .TButton is important, you can add any other pretix though
style.configure('color.TButton', background=current.color)
# Activate is when you mouse over the button.
style.map('color.TButton', background=[('active', current.color)])
current = SetColor('green')
root = tk.Tk()
# Create style Object
style = ttk.Style()
# Setting theme to default (built in themes can be found https://wiki.tcl-lang.org/page/List+of+ttk+Themes)
style.theme_use("default")
lbl_color = ttk.Label(root, text='Choose button color')
lbl_color.grid(row=0, column=0, pady=5, padx=2)
btn_red = ttk.Button(root, text='Red', style="color.TButton", command=lambda: update_color('red'))
btn_red.grid(row=0, column=1, pady=5, padx=2)
btn_green = ttk.Button(root, text='Green', style="color.TButton", command=lambda: update_color('green'))
btn_green.grid(row=0, column=2, pady=5, padx=2)
btn_blue = ttk.Button(root, text='Blue', style="color.TButton", command=lambda: update_color('blue'))
btn_blue.grid(row=0, column=3, pady=5, padx=2)
update_color(current.color)
root.mainloop()
There are a lot more options with ttk style to play around.
Have a look at
Python ttk Style
ttk Themes
I did this and works perfectly. I hope it works for you.
def groups1(): # This function is to place the widgets for Groups.
# Clean widgets immediately after you call the button inside
# the button function.
for widget in frameleft.winfo_children():
widget.destroy()
groups = tkinter.ttk.Label(frameleft, text='Grupos', font=
('URW Gothic', 20))
groups.place(x=20, y=30)

How to set default value of radio button using TKinter in a class?

I'm trying to set the default value of a radio button using TKinter and Python. It's my first time using it so I'm pretty new. My understanding is that the default value should be set to the second radio button in my example (value=1).
from tkinter import *
from tkinter import ttk
class RadioButtons:
def __init__(self, root):
self.root = root
self.jobNum = IntVar(value=1)
self.create()
def create(self):
content = ttk.Frame(self.root)
radioButtons = ttk.LabelFrame(content, borderwidth=5, relief="ridge", width=400, height=400, text="Radio Buttons")
radioButtonsLbl=ttk.Label(radioButtons, text="Buttons")
# radio buttons
jobType1 = ttk.Radiobutton(radioButtons, text="Button 0", variable= self.jobNum, value=0)
jobType2 = ttk.Radiobutton(radioButtons, text="Button 1", variable= self.jobNum, value=1)
jobType3 = ttk.Radiobutton(radioButtons, text="Button 2", variable= self.jobNum, value=2)
content.grid(column=0, row=0)
# add to grid
radioButtons.grid(column=0, row=0, columnspan=3, rowspan=3)
radioButtonsLbl.grid(column=0, row=5, padx=20, pady=5, sticky=W)
jobType1.grid(column=1, row=5, padx=20, pady=0, sticky=W)
jobType2.grid(column=1, row=6, padx=20, pady=0, sticky=W)
jobType3.grid(column=1, row=7, padx=20, pady=0, sticky=W)
root = Tk()
RadioButtons(root)
root.mainloop()
However no radio button is selected when running the program. (screenshot of program)
The debugger confirms that the value of self.jobNum is set correctly.(screenshot of debugger)
How do I set the default value? I've tried a number of things including self.jobNum.set() before and after creating and adding the radio buttons but to no avail.
What am I missing here? Is this some kind of scope issue?
I suspect this has something to do with python's garbage collector. I can make the problem go away by saving a reference to RadioButtons(root):
root = Tk()
rb = RadioButtons(root)
root.mainloop()

tkinter (unsure of my error possibly due to .destroy())

from Tkinter import *
import random
menu = Tk()
subpage = Tk()
entry_values = []
population_values = []
startUpPage = Tk()
def main_menu(window):
window.destroy()
global menu
menu = Tk()
frame1 = Frame(menu)
menu.resizable(width=FALSE, height=FALSE)
button0 = Button(menu, text="Set Generation Zero Values", command=sub_menu(menu))
button1 = Button(menu, text="Display Generation Zero Values")
button2 = Button(menu, text="Run Model")
button3 = Button(menu, text="Export Data")
button4 = Button(menu, text="Exit Program", command=menu.destroy)
button0.grid(row=0, column=0, sticky=W)
button1.grid(row=2, column=0, sticky=W)
button2.grid(row=3, column=0, sticky=W)
button3.grid(row=4, column=0, sticky=W)
button4.grid(row=5, column=0, sticky=W)
menu.mainloop()
def sub_menu(window):
global subpage
window.destroy()
subpage = Tk()
subpage.resizable(width=FALSE, height=FALSE)
#defining sub page items
button5 = Button(subpage, text="Save Generation Data",command = main_menu(subpage))
juveniles_label0 = Label(subpage,text="Juveniles")
adults_label1 = Label(subpage,text="Adults")
seniles_label2 = Label(subpage,text="Seniles")
population_label3 = Label(subpage,text="Popultation")
survival_rate_label4 = Label(subpage,text="Survival Rate (Between 0 and 1)")
entry0 = Entry(subpage)
entry1 = Entry(subpage)
entry2 = Entry(subpage)
entry3 = Entry(subpage)
entry4 = Entry(subpage)
entry5 = Entry(subpage)
button4.grid(row=1, column= 6, sticky=E)
juveniles_label0.grid(row=0, column=1)
adults_label1.grid(row=0, column=2)
seniles_label2.grid(row=0, column=3)
population_label3.grid(row=1, column=0)
survival_rate_label4.grid(row=2, column=0)
entry0.grid(row=1, column=1)
entry1.grid(row=1, column=2)
entry2.grid(row=1, column=3)
entry3.grid(row=2, column=1)
entry4.grid(row=2, column=2)
entry5.grid(row=2, column=3)
#add entry 6 7 8
subpage.mainloop()
main_menu(subpage)
main_menu(startUpPage)
I'm very new to coding and stackoverflow. I am trying to create a GUI that has a main page which will be opened first and a sub page which will be opened by clicking a button which will be stored in the main page. my issue is that I have no clue why it isn't opening my main page. my thought is that it is something to do with the .destroy() or something similar. any help would be much appreciated.
As a general rule, you should create exactly one instance of Tk for the life of your program. That is how Tkinter is designed to be used. You can break this rule when you understand the reasoning behind it, though there are very few good reasons to break the rule.
The simplest solution is to implement your main menu and your sub menu as frames, which you've already done. To switch between them you can simply destroy one and (re)create the other, or create them all ahead of time and then remove one and show the other.
For example, the following example shows how you would create them ahead of time and simply swap them out. The key is that each function needs to return the frame, which is saved in a dictionary. The dictionary is used to map symbolic names (eg: "main", "sub", etc) to the actual frames.
def main_menu(root):
menu = Frame(root)
button0 = Button(menu, text="Set Generation Zero Values",
command=lambda: switch_page("sub"))
...
return menu
def sub_menu(root):
subpage = Frame(root)
button5 = Button(subpage, text="Save Generation Data",
command = lambda: switch_page("main"))
...
return subpage
def switch_page(page_name):
slaves = root.pack_slaves()
if slaves:
# this assumes there is only one slave in the master
slaves[0].pack_forget()
pages[page_name].pack(fill="both", expand=True)
root = Tk()
pages = {
"main": main_menu(root),
"sub": sub_menu(root),
...
}
switch_page("main")
root.mainloop()
For a more complex object-oriented approach see Switch between two frames in tkinter
heres some code that does what you want.. make a window, destroy it when button is clicked and then make a new window...
from Tkinter import *
import random
def main_menu():
global root
root = Tk()
b = Button(root,text='our text button',command = next_page)
b.pack()
def next_page():
global root,parent
parent = Tk()
root.destroy()
new_b = Button(parent,text = 'new Button',command=print_something)
new_b.pack()
def print_something():
print('clicked')
main_menu()
root.mainloop()
parent.mainloop()
ps. ive done this in python3 so keep that in mind though it wouldnt be a problem in my opinion

tkinter button command function not defined

I am trying to get the input from text box and trying to write them to a file:
I get the error as: retrieve_input is not defined.
Please help me to rectify my code:
coding:
import tkinter as tki
class App(object):
def __init__(self,root):
self.root = root
# create a Frame for the Text and Scrollbar
txt_frm = tki.Frame(self.root, width=600, height=400)
txt_frm.pack(fill="both", expand=True)
# ensure a consistent GUI size
txt_frm.grid_propagate(False)
self.txt1 = tki.Text(txt_frm, borderwidth=3, relief="sunken", height=4,width=55)
self.txt1.config(font=("consolas", 12), undo=True, wrap='word')
self.txt1.grid(row=0, column=1, sticky="nsew", padx=2, pady=2)
scrollb1 = tki.Scrollbar(txt_frm, command=self.txt1.yview)
scrollb1.grid(row=0, column=2, sticky='nsew')
self.txt1['yscrollcommand'] = scrollb1.set
button = tki.Button(self,text=u"Click command=retrieve_input)
button.grid(column=1,row=0)
def retrieve_input():
input = self.txt1.get("0.0",'END-1c')
with open('hello.txt','w') as f:
f.wite(input)
root = tki.Tk()
app = App(root)
root.mainloop()
In addition to the obvious typos, the problem is this line:
button = tki.Button(self,text="Click", command = self.retrieve_input)
Notice that the first parameter you pass to tk.Button is self. The first argument must be a widget, but you're giving it self which is not a widget. Perhaps you meant to use txt_form instead?

Encountered error; Tkinter GUI using python

I'm creating a simple Tkinter gui. However, something appears to be going haywire. Nothing is actually being 'pack'ed to the frame. Can anyone spot what I've done wrong? (Other than the issues caused by using 'from Tkinter import *', and the apparently un-useful 'do_nothing()' function.
#/usr/bin/python
from Tkinter import *
class gui:
def __init__(self, parent):
f = Frame(parent, width=300, height=500)
f.pack(padx=30, pady=15)
self.label = Label(f, text="Japanese Trainer")
self.label.pack(side=TOP, padx=10, pady=12)
self.txtlbl = Entry(f, justify=CENTER, text="", font=("Calibri", 15, "bold"), width=37)
self.txtlbl.pack()
self.txtlbl.grid(row=1, rowspan=2, sticky=E, pady=10, padx=40)
self.button0 = Button(f, text="Kana Trainer", width=20, command=self.do_nothing)
self.button0.pack()
self.button0.grid(row=3, rowspan=2, sticky=W, pady=10, padx=40)
self.button1 = Button(f, text="Vocab Trainer", width=20, command=self.do_nothing)
self.button1.pack()
self.button1.grid(row=3, rowspan=2, sticky=E, pady=10, padx=40)
def do_nothing(self):
self.txtlbl.delete(0, END)
self.txtlbl.insert(END, "Command did nothing...")
root = Tk()
root.title('Eg.')
app = gui(root)
root.mainloop()
You are mixing grid and pack in the same master window. You can't do that. Each one will potentially resize the widgets they manage, and each will respond to resizes in the widgets they manage. So, pack will resize the widgets to fit, grid will recognize the change and try to resize widgets to fit, pack will recognize the change and try to resize widgets to fit, ... resulting in an endless loop.
You can use pack and grid together in the same program, but you cannot use them to manage the same container.

Categories