Tkinter Labels self update - python-3.x

I have a linkedlist implemented in Python and for every node I need a label in a Tkinter window. The problem I have is that I don't know how to update the label when the corresponding node data changes.
currently I have this code for the window that displays all the nodes and the data within the nodes in its default "no data" state.
def interface(self):
Window = Tk()
Row = 0
current_node = self.get_top_of_list()
while current_node is not None:
Label(Window, text = current_node).grid(row = Row, column = 0)
Label(Window, text = current_node.data).grid(row = Row, column = 1)
current_node = current_node.next_node
Row +=1
mainloop()

As furas also points out in the comment, Label widget will show its textvariable at all times, so you can modify the object that is assigned to textvariable, without referencing the label object again.
I'd add an attribute, dataLabel which later will hold my variable class object.
class Node:
def __init__(self, data):
self.data = data
self.next_node = None
self.dataLabel = None
then assign that variable class object, self.dataLabel to my label's textvariable in my interface method:
def interface(self):
Window = Tk()
Row = 0
current_node = self.get_top_of_list()
while current_node is not None:
current_node.dataLabel = StringVar() #assuming from Window tkinter is imported as wildcard
current.node.dataLabel.set(current_node.data)
Label(Window, textvariable = current_node.dataLabel).grid(row = Row, column = 0)
current_node = current_node.next_node
Row +=1
mainloop()
A similar object can be created for next_node attribute as well.

Label changes text automatically if it uses StringVar with textvariable= in Label and you change text in StringVar.
Linkedlist can't update labels automatically - you have to write function for it.
You could keep access to Label or StringVar inside node and node could change value in Label or StringVar when you change value in node.
See Observer Pattern. Node could be "subject" (class Observable) and Label or StringVar could be "observer" (class Observer)
BTW: StringVar/IntVar/otherVar use pattern Observer. StringVar is "subject" and you can use StringVar.trace() to assign function to StringVar (it will be "observer") and StringVar will execute this function automatically when you change value in StringVar.
More on effbot.org: The Variable Classes (BooleanVar, DoubleVar, IntVar, StringVar)
Other method is to use root.after to execute function which will get all data from nodes and updates Labels
Simple example which use after() to display current time
import tkinter as tk
from datetime import datetime
# --- functions ---
def update_time():
# get current date & time as text
current = datetime.now().strftime('%Y.%m.%d %H:%M:%S')
# update label with current date and time
label['text'] = current
# run update_time again after 1000ms (1s)
root.after(1000, update_time)
# --- main ---
root = tk.Tk()
# create Label without text - update_time will set text
label = tk.Label(root)
label.pack()
# run update_time first time
update_time()
root.mainloop()

Related

Python3 Tkinter, how do I get a variable inside of a def?

I'm trying to get the variable "user_get" inside the def entered(user_input)
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
#put settings in place (username input)
window.geometry('1920x1080')
window.configure(bg = 'blue')
def entered(user_input):
user_get = user_input.widget.get()
user_input.widget.delete(0, 'end')
print(user_get)
return user_get
user_input.widget.destroy()
# TextBox (input)
user_input = tk.Entry(window)
user_input.pack()
user_input.place(x = 100,y = 40)
user_input.bind("<Return>", entered)
thing = user_get
print(thing)
window.mainloop()
I have tried:
-return (I don't really understand it that well)
Here I've made it so the user_input variable is available in the global scope. The global keyword in the entered function allows that function to modify the global variable. Now, when you type into the entry and hit Return, the value of user_input is updated.
I defined an example function that will print this value whenever a button is pressed. Note that until you add text to the Entry and press Return, an empty string will be printed!
Likewise, any calls like print(user_input) made immediately before root.mainloop() will print an empty string because the value of user_input hasn't been updated yet.
import tkinter as tk
def entered(_event):
global user_input # allow this function to modify this variable
user_input = entry.get() # update the variable with the entry text
entry.delete(0, 'end') # clear entry contents
def use_value_from_entry():
"""Example function to use the value stored in 'user_input'"""
print(user_input)
window = tk.Tk()
user_input = '' # create a variable to store user input in the global scope
entry = tk.Entry(window) # create the entry widget
entry.pack() # add the widget to the UI
entry.bind('<Return>', entered) # bind the event handler
# this button runs an example function to get the current value of 'user_input'
button = tk.Button(window, text='Click Me', command=use_value_from_entry)
button.pack()
window.mainloop() # run the app
#Stockmage updated this

How to retrieve entry value in tkinter from a custom form

Wrote a custom frame class so I can easily generate labels, entries etc.
I can input the number of labels, entries I want each form to have. By creating different instances of the class I don't need to write lots of labels and entries each time.
But when I run the code nothing happens. I get no error to guide me.
If I place a print statement in get_first_entry(self) function then it prints the value from the entry, but I would like to access the value from outside the class.
import tkinter as tk
class MyFrame(tk.Frame):
def __init__(self, parent, num_labels, num_entries, num_buttons, label_names=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
# If label names are provided, use them instead of default labels
if label_names is None:
label_names = [f"Label {i+1}" for i in range(num_labels)]
# Create labels and entries
self.entries = []
for i in range(num_labels):
label = tk.Label(self, text=label_names[i])
label.grid(row=i, column=0)
for i in range(num_entries):
entry = tk.Entry(self)
entry.grid(row=i, column=1)
self.entries.append(entry)
# Create buttons
for i in range(num_buttons):
if i == 0:
button = tk.Button(self, text=f"Get RA", command=self.get_first_entry)
elif i == 1:
button = tk.Button(self, text=f"Get DEC", command=self.get_second_entry)
#else:
#button = tk.Button(self, text=f"Button {i+1}")
button.grid(row=num_labels+i, column=0, columnspan=2, pady=2)
def get_first_entry(self):
value = self.entries[0].get()
return value
def get_second_entry(self):
value = self.entries[1].get()
return value
root = tk.Tk()
label_RADEC = ['RA','DEC']
label_Time = ['LST','local time']
# Create first frame
frame1 = MyFrame(root, num_labels=2, num_entries=2, num_buttons=2, label_names=label_RADEC)
frame1.grid(row=0, column=0, padx=10, pady=10)
# Access the first entry
first_entry_value = frame1.get_first_entry()
print(first_entry_value)
root.mainloop()
Updated with method to gel all entries as array.
Now it prints empty array at start, but if I input other values in the entries, still doesn't print anything
'',''
The problem isn't that get_entries isn't working, it's that you're using it in the wrong way.
When you first call get_entries and print the results, it's before the user has had a chance to enter anything so it prints a list of empty strings.
When you call get_entries by pressing the button, it computes the values just fine. However, because it only returns the values, the values are thrown away since the caller (mainloop) ignores the return values of all functions it calls since it doesn't know what to do with them.
You can see this by adding a print statement inside get_entries. When you do that, you can see it's getting the values just fine:
def get_entries(self):
result = [entry.get() for entry in self.entries]
print(f"get_entries result: {result}")
return result
When I insert "this is RA" in the first entry, and "this is DEC" in the second entry, when I press the button I get this output:
get_entries result: ['this is RA', 'this is DEC']
If you leave the return statement in, you can use it to get the values outside of the class. It's just that you must do so after the user has entered values and not before.

tkinter, button returns variable

I am trying to make a GUI text based adventure game in python. I want to be able to take text from a textinput box and store it as string variable.
I have 2 problems:
Making the python wait for the submit button to be pressed, before
processing the input and updating the game.
Getting the text variable out of the command, I would like to not
use global if possible.
Here is some of my code to better understand:
root = tk.Tk()
root.geometry('800x600+100+100')
root.title("my game")
textbox = tk.StringVar()
textboxentry = tk.Entry(root, textvariable=textbox, bd=5, width = "40", font=("times", 20))
textboxentry.pack(in_=bgImageLabel, side = "bottom")
def getInput():
textboxInput = textbox.get() #gets entry
lengthEntry = len(textbox.get())
textboxentry.delete(0,lengthEntry) #removes entry from widget
return textboxInput # I would like this return to work
submit = tk.Button(root, text ="Submit", command = (textboxInput = getInput()))
##I want the command function to use command = getInput and store the return on getInput as textboxInput. This will update the wait_variable down below, and give the inputs(textboxInput) a string to work with.
submit.pack(in_=bgImageLabel, side = "bottom")
while game == True:
root.update_idletasks()
root.update()
submit.wait_variable(textboxentry)
## I need it to wait before proceeding to this next line because i need the textboxInput from the entry widget.
actionInput, extraInput, texts = inputs(textboxInput)
Currently I can't figure a way to use command = (textboxInput = getInput), using lambda or anything else. I just want to store the return which comes off of the Entry as a string variable that can be used by the main function.
All help is appreciated!
Below code processes entry widget's text when Submit button is pressed.
import tkinter as tk
root = tk.Tk()
aVarOutside = 'asd'
def btn_cmd(obj):
#use global variable
global aVarOutside
#print its unmodified value
print("aVarOutside: " + aVarOutside)
#modify it with what's written in Entry widget
aVarOutside = obj.get()
#modify lblTextVar, which is essentially modifying Label's text as lblTextVar is its textvariable
lblTextVar.set(obj.get())
#print what's inside Entry
print("Entry: " + obj.get())
txt = tk.Entry(root)
txt.pack()
lblTextVar = tk.StringVar()
lbl = tk.Label(root, textvariable=lblTextVar)
lbl.pack()
btn = tk.Button(text="Submit", command=lambda obj = txt : btn_cmd(obj))
btn.pack()
root.mainloop()
When the button is pressed:
Value of a global variable, aVarOutside is printed.
Value of aVarOutside is modified to the value of Entry box's
(txt's) content.
Value of a textvariable used by a label (lbl) is modified. Which
means that the text of lbl is updated and can be seen on the GUI.
Finally Entry box, txt's content is printed.
I think you should use inputs() inside getInputs() and then button doesn't have to return any variables - and then you can use root.mainloop() instead of while loop.
import tkinter as tk
# --- functions ---
def inputs(text):
# do something with text
print(text)
# and return something
return 'a', 'b', 'c'
def get_input():
global action_input, extra_input, texts
text = textbox.get()
if text: # check if text is not empty
textbox.set('') # remove text from entry
#textbox_entry.delete(0, 'end') # remove text from entry
action_input, extra_input, texts = inputs(text)
# --- main ---
root = tk.Tk()
textbox = tk.StringVar()
textbox_entry = tk.Entry(root, textvariable=textbox)
textbox_entry.pack()
submit = tk.Button(root, text="Submit", command=get_input)
submit.pack()
root.mainloop()
BTW: you could better organize code
all functions before main part (root = tk.Tk())
PEP8 suggests to use lower_case_names for functions and variables (instead of CamelCaseNames)
global is not prefered method but I think it is better solution than yours.
If you don't need global then you can use classes with self.
import tkinter as tk
# --- classes ---
class Game:
def __init__(self):
self.root = tk.Tk()
self.textbox = tk.StringVar()
self.textbox_entry = tk.Entry(self.root, textvariable=self.textbox)
self.textbox_entry.pack()
self.submit = tk.Button(self.root, text="Submit", command=self.get_input)
self.submit.pack()
def run(self):
self.root.mainloop()
def inputs(self, text):
# do something with text
print(text)
# and return something
return 'a', 'b', 'c'
def get_input(self):
text = self.textbox.get()
if text: # check if text is not empty
self.textbox.set('') # remove text from entry
#textbox_entry.delete(0, 'end') # remove text from entry
self.action_input, self.extra_input, self.texts = self.inputs(text)
# --- functions ---
# empty
# --- main ---
app = Game()
app.run()

Changing entry box background colour in tkinter

So I've been working on this program and I'm finding it very hard to figure out what's wrong. I'm fairly new to tkinter so this may be quite minor.
I'm trying to get the program to change the entry box's background colour when the check button is pressed. Or even better if somehow I can change it dynamically it would be even better.
This is my code at the moment:
TodayReading = []
colour = ""
colourselection= ['green3', 'dark orange', "red3"]
count = 0
def MakeForm(root, fields):
entries = []
for field in fields:
row = Frame(root)
lab = Label(row, width=15, text=field, font=("Device",10, "bold"), anchor='center')
ent = Entry(row)
row.pack(side=TOP, padx=5, fill=X, pady=5)
lab.pack(side=LEFT)
ent.pack(side=RIGHT, expand=YES, fill=X)
entries.append((field, ent))
return entries
def SaveData(entries):
import time
for entry in entries:
raw_data_point = entry[1].get()
data_point = (str(raw_data_point))
TodayReading.append(data_point)
c.execute("CREATE TABLE IF NOT EXISTS RawData (Date TEXT, Glucose REAL, BP INTEGER, Weight INTEGER)")
c.execute("INSERT INTO RawData (Date, Glucose, BP, Weight) VALUES (?, ?, ?, ?)", (time.strftime("%d/%m/%Y"), TodayReading[0], TodayReading[1] , TodayReading[2]))
conn.commit()
conn.close()
def DataCheck():
if ((float(TodayReading[0])>=4 and (float(TodayReading[0])<=6.9))):
colour = colourselection[count]
NAME OF ENTRY BOX HERE.configure(bg=colour)
Thanks for the help. Someone may have answered it already but like i said I'm new to tkinter so if i've seen it already, I haven't figured out how to implement it.
Please see my example below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.var = StringVar() #creates StringVar to store contents of entry
self.var.trace(mode="w", callback=self.command)
#the above sets up a callback if the variable containing
#the value of the entry gets updated
self.entry = Entry(self.root, textvariable = self.var)
self.entry.pack()
def command(self, *args):
try: #trys to update the background to the entry contents
self.entry.config({"background": self.entry.get()})
except: #if the above fails then it does the below
self.entry.config({"background": "White"})
root = Tk()
App(root)
root.mainloop()
So, the above creates an entry widget and a variable which contains the contents of that widget.
Every time the variable is updated we call command() which will try to update the entry background colour to the contents of the entry (IE, Red, Green, Blue) and except any errors, updating the background to White if an exception is raised.
Below is a method of doing this without using a class and using a separate test list to check the value of the entry:
from tkinter import *
root = Tk()
global entry
global colour
def callback(*args):
for i in range(len(colour)):
if entry.get().lower() == test[i].lower():
entry.configure({"background": colour[i]})
break
else:
entry.configure({"background": "white"})
var = StringVar()
entry = Entry(root, textvariable=var)
test = ["Yes", "No", "Maybe"]
colour = ["Red", "Green", "Blue"]
var.trace(mode="w", callback=callback)
entry.pack()
root.mainloop()

Passing OptionMenu into a callback (or retrieving a reference to the used widget)

I'm working on a (toplevel in a) GUI that consists of an array of 8 OptionMenus, each of them containing the same option list. Currently, Im building these widgets using a for-loop, and I save references in a dictionary. All OptionMenus link to the same (lambda) callback function.
To stay practical: the items in the option list represent a sequence of processing steps, and the user can alter the order of processes.
A change in one of the lists will result in one process being executed twice, and one process not at all. However, I want each item to occur only once. Hence, each user input should be accompanied by a second OptionMenu alteration.
For example: initial order 1-2-3 --> user changes the second process: 1-3-3, which autocorrects to: 1-3-2, where each process is again executed only once.
To my understanding, I can only get this to work if I have a reference to the OptionMenu that was just altered (from within the callback function). I was looking into passing the widget into the callback. The sample code is an attempt to implement the second suggested method, but the result is not what I would have expected.
The thing is that the OptionMenu widget seems to behave somewhat differently from other widgets. The OptionMenu does not allow for a re-defintion of the command function. No matter what input I pass along with the command function, the callback only seems to retrieve the OptionMenu selection, which is insufficient information for me to determine my process order.
Suggestions would be much apreciated!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
self.active_procs = ['proc 1','proc 2','proc 3','proc 4',
'proc 5','proc 6','proc 7','proc 8']
itemnr, widgets = dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
itemnr[name_construct] = tk.StringVar(root)
itemnr[name_construct].set(self.active_procs[index])
widgets[name_construct] = tk.OptionMenu(self, itemnr[name_construct], *self.active_procs,
command=lambda widget=name_construct:
self.order_change(widget))
widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,widget):
print(widget)
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()
The OptionMenu will pass the new value to the callback, so you don't have to do anything to get the new value. That's why your widget value isn't the value of name_construct -- the value that is passed in is overwriting the default value that you're supplying in the lambda.
To remedy this you simply need to add another argument so that you can pass the value of name_construct to the callback to go along with the value which is automatically sent.
It would look something like this:
widgets[name_construct] = tk.OptionMenu(..., command=lambda value, widget=name_construct: self.order_change(value, widget))
...
def order_change(self, value, widget):
print(value, widget)
Note: the OptionMenu isn't actually a tkinter widget. It's just a convenience function that creates a standard Menubutton with an associated Menu. It then creates one item on the menu for each option, and ties it all together with a StringVar.
You can get the exact same behavior yourself fairly easily. Doing so would make it possible to change what each item in the menu does when selected.
For those interested, below you can find an example code of how I got the widget behaviour I wanted. I took Bryan's advice to replace the OptionMenu for a Menubutton/Menu combination. I also made use of this post to find duplicate entries in my process order list.
Any thoughts or suggestions on how to implement this in a cleaner or shorter way, or how to get the same functionality with a different interface (e.g. drag and drop), are ofcourse welcome!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
# Assisting text
l1 = tk.Label(self, text = "Data in", font=(None, 15))
l1.grid(row=0, column=2)
l2 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l2.grid(row=1, column=2)
l3 = tk.Label(self, text = "Data out", font=(None, 15))
l3.grid(row=11, column=2)
l4 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l4.grid(row=10, column=2)
# Process list
self.active_procs = ['proc a','proc b','proc c','proc d',
'proc e','proc f','proc g','proc h']
self.the_value, self.widgets, self.topmenu = dict(), dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
self.the_value[name_construct] = tk.StringVar(root)
self.the_value[name_construct].set(self.active_procs[index])
self.widgets[name_construct] = tk.Menubutton(self, textvariable=
self.the_value[name_construct],
indicatoron=True)
self.topmenu[name_construct] = tk.Menu(self.widgets[name_construct],
tearoff=False)
self.widgets[name_construct].configure(menu=self.topmenu[name_construct])
for proc in self.active_procs:
self.topmenu[name_construct].add_radiobutton(label=proc, variable=
self.the_value[name_construct],
command=lambda proc=proc,
widget=name_construct:
self.order_change(proc,widget))
self.widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,proc,widget):
# Get the index of the last changed Menubutton
index_user_change = list(self.widgets.keys()).index(widget)
procs_order = [] # Current order from widgets
for index in range(8):
name_construct = 'nr' + str(index)
procs_order.append(self.widgets[name_construct].cget("text"))
# 1 change may lead to 1 double and 1 missing process
doubles = self.list_duplicates_of(procs_order,proc)
if len(doubles) == 2: # If double processes are present...
doubles.remove(index_user_change) # ...remove user input, change the other
missing_proc = str(set(self.active_procs)^set(procs_order)).strip('{"\'}')
index_change_along = int(doubles[0])
# Update references
self.active_procs[index_user_change] = proc
self.active_procs[index_change_along] = missing_proc
# Update widgets
name_c2 = 'nr'+str(index_change_along)
self.the_value[name_c2].set(self.active_procs[index_change_along])
self.widgets[name_c2].configure(text=missing_proc)
def list_duplicates_of(self,seq,item):
start_at = -1
locs = []
while True:
try:
loc = seq.index(item,start_at+1)
except ValueError:
break
else:
locs.append(loc)
start_at = loc
return locs
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()

Resources