I am trying to populate my screen with some data that refreshes every given time interval. I am using Python3, themed tkinter. Every time my screen updates I see grey flickerings on screen for every label. Is there a way to avoid this?
P.S : I am calling the 'after' method to refresh data.
UPDATE: Here is some example code:
def button1Click(self):
self.top = Toplevel(width=600,height=400)
self.top.title("XYZ ORGANIZATION")
self.frame1 = Frame(self.top,bg='#009999')
self.frame1.pack()
self.noOfEmp = Label(self.frame1,text = "Number_Of_Employees : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.noOfEmp.grid(row=1,column=0,sticky=W,padx=0,pady=5)
self.TeamLabel = Label(self.frame1,text = "Team Name : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.TeamLabel.grid(row=2,column=0,sticky=W,padx=0,pady=5)
self.text = Text(self.frame1, bg='#009999')
self.text.grid(row=8,columnspan=17)
self.old_emp = 0
self.EFile = open('/abc','r').readlines()
for line in self.EFile:
if line.startswith('EmpTotal:'):
self.Tot_Emp = int(line.split()[1])
break
t1 = threading.Thread(target=self.__make_layout, args = ())
t1.daemon = True
t1.start()
t2 = threading.Thread(target=self.ProcEmp,args = ())
t2.daemon = True
t2.start()
def self.__make_layout:
self.CLabelVal = Label(self.frame1,text = CSpace, font=('Verdana',9),bg='#009999',fg = '#000000')
self.MLabelVal = Label(self.frame1,text = MSpace , font =('Verdana',9),bg='#009999',fg = '#000000')
self.Label1Val.grid(row=4,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.Label2Val.grid(row=5,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.frame1.after(5000,self.__make_layout)
Part of the problem is that you keep stacking more and more widgets on top of each other. You should create the labels exactly once, and then change what they display every five seconds, instead of creating new widgets every five seconds.
There's also the problem that you're creating the labels in a thread. Tkinter isn't thread safe. Any code that creates or modifies a widget needs to be in the main thread. To update the labels you don't need threads, though you can use a thread to change what actually gets displayed.
def __make_layout(self):
self.CLabelVal = Label(...,text = CSpace, ...)
self.MLabelVal = Label(...,text = MSpace, ...)
self.Label1Val.grid(...)
self.Label2Val.grid(...)
def __update_layout(self):
self.CLabelVal.configure(text=CSpace)
self.MLabelVal.configure(text=MSpace)
self.after(5000, self.__update_layout)
I made a small program based on what you provided.
This is what i got. I chose 500ms instead because i did not want to wait that long. I ran two internet videos at the same time and had no issues.
So my best guess you have a slow video card or over loaded computer.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
def __init__(self):
self.frame1.after(500, self.__make_layout)
def __make_layout(self):
self.poll += 1
CSpace = "Poll count = "*20
MSpace = str(self.poll)
self.CLabelVal = Label(self.frame1, text=CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, text=MSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
print(CSpace, MSpace)
return self.frame1.after(500, self.__make_layout)
MyClass()
mainloop()
This doesn't create more labels and uses the "textvariable" update feature.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
textstg = StringVar()
CSpace = "Poll count"
def __init__(self):
self.frame1.after(500, self.__make_layout)
self.CLabelVal = Label(self.frame1, text=self.CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, textvariable=self.textstg, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
def __make_layout(self):
self.poll += 1
self.textstg.set(str(self.poll))
return self.frame1.after(50, self.__make_layout)
MyClass()
mainloop()
Related
I'm trying to create a basic counting app so that I can go in the field and count multiple items at once. I created the majority of the widgets with a for loop and I am trying to get the value of the label self.count and add to it.
Is there a way to retrieve the values with the references I have stored?
Since the values are in different memory locations and I don't know how to tell which button is pressed based on the below code.
(Note: the buttons should only interact with the values directly above them.)
import tkinter as tk
from tkinter import ttk
from dataclasses import dataclass
#dataclass
class fwidgets:
frame: str
framenum: int
widnum: int
widgets: list
ref: list
class App:
def addcount(self):
pass
def resetcount(self):
pass
def main(self):
win = tk.Tk()
largedoublefrm = ttk.Frame(win)
largesinglefrm = ttk.Frame(win)
smalldoublefrm = ttk.Frame(win)
smallsinglefrm = ttk.Frame(win)
combofrm = ttk.Frame(win)
largedoublefrm.grid(column=2, row=0)
largesinglefrm.grid(column=0, row=0)
smalldoublefrm.grid(column=3, row=0)
smallsinglefrm.grid(column=1, row=0)
combofrm.grid(column=4, row=0)
mainframe = fwidgets('win', 200, 5, ('largesinglefrm', 'smallsinglefrm',
'largedoublefrm', 'smalldoublefrm', 'combofrm'),
(largesinglefrm, smallsinglefrm, largedoublefrm,
smalldoublefrm, combofrm))
self.refframe = []
x=0
for wid in mainframe.ref:
ttk.Label(wid, text=mainframe.widgets[0]).pack()
self.count = ttk.Label(wid, text='0', font=("Arial", 20))
add = tk.Button(wid, text='ADD', height=5, width=15,
command=self.addcount)
reset = tk.Button(wid, text='RESET', height=5, width=15,
command=self.resetcount)
self.count.pack()
add.pack()
reset.pack()
frame = fwidgets(mainframe.widgets[x], x, 3, ('count', 'add', 'reset'),
(self.count, add, reset))
self.refframe.append(frame)
x += 1
print(self.refframe)
win.mainloop()
if __name__ == '__main__':
app = App()
app.main()
I'm not sure where to go from here. Any help would be appreciated.
To find the relevant objects first remove win.mainloop(), since only one
mainloop is permitted per tk.tk instance.
Then modify your code like this.
def addcount(self, i):
print(i.winfo_children())
def resetcount(self, i):
print(i.winfo_children())
add = tk.Button(wid, text='ADD', height=5, width=15,
command = lambda w = wid: self.addcount(w))
reset = tk.Button(wid, text='RESET', height=5, width=15,
command = lambda w = wid: self.resetcount(w))
# win.mainloop()
Now when you click a button a list of relevant objects will be displayed.
Just picked up tkinter recently
I have a program where when a user click a [...] button, it will display a toplevel window containing a calendar and [OK] button inside it.
When the user click the [OK] button, I want it to change [startDate] variable, and [labelStartDate] label in the main window.
I need the [startDate] variable for my next data process. and [labelStartDate] label is to show user that the date is changed.
How to achieve that?
I tried to use command=lambda or stringvar, but honestly I am kinda lost trying to apply it to my program.
from datetime import date
from textwrap import fill
import tkinter as tk
from tkinter import ttk
from tkinter import Toplevel
from tkinter import font
from tkcalendar import Calendar
from turtle import color, width
# Define the GUI
class App(tk.Tk):
def __init__(self):
super().__init__()
# root window
self.title('Main Window')
self.geometry('620x570')
global startDate #variable that I want to use for later data processing
startDate = date.today().strftime("%Y/%m/%d/")
#DATE MENU FRAME
DateMenuBar = ttk.LabelFrame(self.master, borderwidth = 1, text='Setting')
subFrame2 = tk.Frame(DateMenuBar, borderwidth = 1, relief = tk.FLAT, pady=0, padx=0)
#SUB FRAME 2
labelStart = tk.Label(subFrame2, text='Start',font=('meiryoui', 15))
labelStartDate = tk.Label(subFrame2, text=startDate,font=('meiryoui', 15))
btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)
labelStart.pack(side = tk.LEFT, ipadx=10)
labelStartDate.pack(side = tk.LEFT, padx=(30,10))
btnOpenCalendar1.pack(side = tk.LEFT)
subFrame2.pack(fill = tk.X,padx=0, pady=10)
DateMenuBar.pack(fill = tk.X,padx=20, ipadx=20, ipady=20)
def changeStartDate(self):
window = Window(self)
window.grab_set()
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Pick Date")
self.geometry("250x250")
def selectStartDate():
startDate = cal.get_date()
#I got stuck here, trying to figure out how to change the labelStartDate's text
cal = Calendar(self, selectmode = 'day')
cal.pack(padx=20, pady=10)
frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))
btnOK.pack(side = tk.RIGHT, padx=(10,0))
btnCancel.pack(side = tk.RIGHT)
frame.pack(fill = tk.X)
if __name__ == "__main__":
app = App()
app.mainloop()
Edit Note:
I added the missing code to my program so that it can be run by others :)
You can first use tkinter.StringVar() and set the label textvariable to the same, inorder to be able to modify the label's text.
self.labelStartDateVar = tk.StringVar() # Initalizing the text variable
self.labelStartDateVar.set(startDateData.start_date) # Setting initial value of the textvariable.
# Added textvariable as labelStartDateVar
self.labelStartDate = tk.Label(subFrame2, textvariable = labelStartDateVar, font = ('meiryoui', 15))
Further, using some knowledge from this post(of Observer Pattern), it is possible to call a function when a change in the startDate is detected. We do so by defining a new class and using a startDateData object as the global object, and to get the value of startDate itself, we simply need to access it's start_date property startDateData.start_date to set it the same property needs to be set like so -:
startDateData.start_date = cal.get_date()
The full code will look something like this -:
class startDate(object):
def __init__(self):
# Setting the default value as in the OP.
self._start_date = date.today().strftime("%Y年 %m月 %d日")
self._observers = []
return
#property
def start_date(self):
return self._start_date
#start_date.setter
def start_date(self, value):
self._start_date = value
for callback in self._observers:
print('announcing change')
callback(self._start_date)
return
def bind_to(self, callback):
print('bound')
self._observers.append(callback)
startDateData = startDate() # global startDateData object.
# Define the GUI
class App(tk.Tk):
def __init__(self):
super().__init__()
# root window
self.title('Main Window')
self.geometry('620x570')
global startDateData #variable that I want to use for later data processing
###
self.labelStartDateVar = tk.StringVar()
self.labelStartDateVar.set(startDateData.start_date)
startDateData.bind_to(self.updateStartDate) # Binding the updateStartDate function to be called whenever value changes.
###
#SUB FRAME 2
self.labelStart = tk.Label(subFrame2, text='開始',font=('meiryoui', 15))
# Added textvariable as labelStartDateVar
self.labelStartDate = tk.Label(subFrame2, textvariable = self.labelStartDateVar, font = ('meiryoui', 15))
self.btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)
self.labelStart.pack(side = tk.LEFT, ipadx=10)
self.labelStartDate.pack(side = tk.LEFT, padx=(30,10))
self.btnOpenCalendar1.pack(side = tk.LEFT)
subFrame2.pack(fill = tk.X,padx=0, pady=10)
def updateStartDate(self, startDate) :
self.labelStartDateVar.set(startDate)
return
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Pick Date")
self.geometry("250x250")
# Globally fetch the startDateData object.
global startDateData
def selectStartDate():
# All binded callbacks will be called, when the value is changed here.
startDateData.start_date = cal.get_date()
cal = Calendar(self, selectmode = 'day')
cal.pack(padx=20, pady=10)
frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))
btnOK.pack(side = tk.RIGHT, padx=(10,0))
btnCancel.pack(side = tk.RIGHT)
frame.pack(fill = tk.X)
NOTE: As the code provided in the OP, was not adequate enough to be able to test whether this solution works. Further, as the initial code provided seemed to be incomplete, the full code given in the answer at the end may also seem incomplete but still implements all the features present in the code given in the OP.
EDIT: The previous placement of the line startDateData = startDate() was wrong as it was trying to construct an object of a class before it is defined, now the line has been shifted below the class definition of startDate.
EDIT 2: Fixed some of the typos, as mentioned in the comments by #Mario Ariyanto.
I made a simple countdown in my trivia game and every time that I go to the next slide it doesn't cancel the previous countdown timer and overlaps with the previous one.
I searched about that on the web and found the after_cancel function that cancels the timer after I go to the next slide and then I recreate it. But it still overlaps even after I added this function.
I think that I didn't give the after_cancel the correct arguments.
from tkinter import *
window = Tk()
window.attributes('-fullscreen',True)
WaitState = StringVar()
count = 10
button_label = StringVar()
def clear():
WaitState.set(1)
for widgets in window.winfo_children():
widgets.destroy()
def clear_time():
clear()
time_up_label = Label(
window,
text="Time is up",
bg = "#6378ff",
fg="#000000",
font=("Arial", 100,"bold"))
time_up_label.place(relx = 0.5,rely = 0.5,anchor = 'center')
continue_but = Button(
window,
text="Continue",
font=("Knewave",25,"bold"),
bg="#942222",
width=11,
height=5,
command=clear)
continue_but.place(relx = 1.0,rely = 1.0,anchor =SE)
continue_but.wait_variable(WaitState)
def button_countdown(i, label):
if i > 0:
i -= 1
label.set(i)
window.after_id = window.after(1000, lambda: button_countdown(i, label))
else:
window.after_cancel(window.after_id)
clear_time()
for index in range(5):
continue_but = Button(
window,
text="Continue",
font=("Knewave",25,"bold"),
bg="#942222",
width=11,
height=5,
command=clear)
continue_but.place(relx = 1.0,rely = 1.0,anchor =SE)
button_label.set(count)
timer_label = Label(
window,
textvariable=button_label,
bg="#6378ff",
fg="#ff0000",
font=("Arial",46,"bold"))
timer_label.pack()
button_countdown(count, button_label)
continue_but.wait_variable(WaitState)
window.mainloop()
So for this larger program I'm making I want it so when a user presses a button it closes the dialog windows and updates all the values the user input. Therefore, I have one button do these two things: update the values and close the program. However, trying to combine these two functions doesn't work as when I use both of them only the update() command is called, not the close command. Either works separately btw. Any way to fix this?
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import *
from tkinter.ttk import *
propDiameterInch = 10.5
propDiameterMetric = propDiameterInch*0.0254
class Counter_program():
def __init__(self):
self.window = tk.Tk()
self.window.title("Test")
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
#default unit color
unitColor = "slategrey"
boxWidth = 5
# Create some room around all the internal frames
self.window['padx'] = 5
self.window['pady'] = 5
propeller_frame = ttk.LabelFrame(self.window, text="Propeller", relief=tk.RIDGE)
propeller_frame.grid(row=1, column=1, sticky=tk.E + tk.W + tk.N + tk.S)
#propeller diameter
propellerDiameter_label = ttk.Label(propeller_frame, text="Propeller Diameter")
propellerDiameter_label.grid(row=1, column=1, sticky=tk.W + tk.N +tk.S)
propellerDiameter_Units = ttk.Label(propeller_frame, text="inches",foreground=unitColor)
propellerDiameter_Units.grid(row=1, column=3, sticky=tk.W)
propellerDiameter_entry = ttk.Entry(propeller_frame, width=boxWidth)
propellerDiameter_entry.grid(row=1, column=2, sticky=tk.W, pady=3)
propellerDiameter_entry.insert(tk.END, "10")
#now set all global variables from entries - update function
def update():
global propDiameter
propDiameter = propellerDiameter_entry.get()
# Finish button in the lower right corner
#finish_button = ttk.Button(self.window, text = "Submit Input", command = self.window.destroy)
finish_button = ttk.Button(self.window, text = "Submit Input", command=lambda:[update(),self.window.destroy])
finish_button.grid(row=2, column=2)
# Create the entire GUI program
program = Counter_program()
# Start the GUI event loop
program.window.mainloop()
propDiameter
Since your using lambda, its safe to use () with the functions, so just change finish_button to:
finish_button = ttk.Button(self.window, text = "Submit Input", command=lambda:[update(),self.window.destroy()])
Or you could make a new function that does both of this for you, like:
def both():
update()
self.window.destroy()
finish_button = ttk.Button(self.window, text = "Submit Input", command=both)
TIP:
Also its not recommended to use global with OOP, so I recommend you change your code and use proper "methods" and self with OOP for a better experience.
Here is how I think your class should like:
class Counter_program():
def __init__(self):
self.window = tk.Tk()
self.window.title("Test")
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
#default unit color
unitColor = "slategrey"
boxWidth = 5
# Create some room around all the internal frames
self.window['padx'] = 5
self.window['pady'] = 5
self.propeller_frame = ttk.LabelFrame(self.window, text="Propeller", relief=tk.RIDGE)
self.propeller_frame.grid(row=1, column=1, sticky=tk.E + tk.W + tk.N + tk.S)
#propeller diameter
self.propellerDiameter_label = ttk.Label(self.propeller_frame, text="Propeller Diameter")
self.propellerDiameter_label.grid(row=1, column=1, sticky=tk.W + tk.N +tk.S)
self.propellerDiameter_Units = ttk.Label(self.propeller_frame, text="inches",foreground=unitColor)
self.propellerDiameter_Units.grid(row=1, column=3, sticky=tk.W)
self.propellerDiameter_entry = ttk.Entry(self.propeller_frame, width=boxWidth)
self.propellerDiameter_entry.grid(row=1, column=2, sticky=tk.W, pady=3)
self.propellerDiameter_entry.insert(tk.END, "10")
# Finish button in the lower right corner
#finish_button = ttk.Button(self.window, text = "Submit Input", command = self.window.destroy)
self.finish_button = ttk.Button(self.window, text = "Submit Input", command=self.both)
self.finish_button.grid(row=2, column=2)
def update(self):
self.propDiameter = self.propellerDiameter_entry.get()
def both(self):
self.update()
self.window.destroy()
Hope this solved the issue, do let me know if any errors or doubts.
Cheers
I'm trying to create code to incrementally increase the voltage on a DC power supply over the span of an input duration. I've set up a GUI for doing this (it's my first try making a GUI, sorry if the code is weird), and everything works ... except that the GUI freezes while the code is executing so I can't stop the loop. I've looked into this for several hours and learned to use root.after instead of time.sleep, but it doesn't seem to have helped in the HeatLoop function. The GUI updates now, but only sporadically and there's still the "wait cursor" showing up when I mouse over the GUI. Is there some way to fix this?
I modified the code I'm using below so it should work on any computer without needing to be edited.
import datetime
import time
from tkinter import *
class GUIClass:
def __init__(self, root):
"""Initialize the GUI"""
self.root = root
self.percent = StringVar()
self.percent.set("00.00 %")
self.error = StringVar()
self.STOP = False
self.error.set("---")
self.currentvoltage = StringVar()
self.currentvoltage.set("Current Voltage: 00.00 V")
self.DT = datetime.datetime
# Create and attach labels
label1 = Label(root, text='Voltage')
label2 = Label(root, text='Ramp Duration')
label3 = Label(root, text='Percent Done: ')
label4 = Label(root, textvariable=self.percent)
label5 = Label(root, text="Error Message: ")
label6 = Label(root, textvariable=self.error)
label7 = Label(root, textvariable=self.currentvoltage)
label1.grid(row=0, column=0, sticky=W)
label2.grid(row=1, column=0, sticky=W)
label3.grid(row=2, column=0, sticky=W)
label4.grid(row=2, column=1, sticky=W)
label5.grid(row=3, column=0, sticky=W)
label6.grid(row=3, column=1, sticky=W)
label7.grid(row=3, column=2, sticky=E)
# Create and attach entries
self.voltage = Entry(root)
self.duration = Entry(root)
self.voltage.grid(row=0, column=1)
self.duration.grid(row=1, column=1)
# Create, bind, and attach buttons
HeatButton = Button(root, text='Heat')
HeatButton.bind("<Button-1>", self.Heat)
HeatButton.grid(row=0, column=2)
CoolButton = Button(root, text='Cool')
CoolButton.bind("<Button-1>", self.Heat)
CoolButton.grid(row=1, column=2)
StopButton = Button(root, text='Stop')
StopButton.bind("<Button-1>", self.Stop)
StopButton.grid(row=2, column=2)
def HeatLoop(self, condition, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
"""Heat up the cell while the condition is true"""
if condition:
self.percent.set("{:2.2f}%".format(Fraction * 100))
print(MaximumVoltage)
self.currentvoltage.set("Current Voltage: {:2.2f} V".format(Fraction*MaximumVoltage))
self.Update()
CurrentTime = self.DT.now()
ElapsedTime = (CurrentTime.second/3600 + CurrentTime.minute/60 + CurrentTime.hour
- TimeStart.second/3600 - TimeStart.minute/60 - TimeStart.hour)
Fraction = ElapsedTime / TimeDuration
print(Fraction)
self.root.after(5000)
self.HeatLoop(bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage, Fraction)
# Define function to heat up cell
def Heat(self, event):
# Initialize Parameters
self.STOP = False
self.error.set("---")
self.Update()
# Try to get voltage and duration from the GUI
MaxVoltage = self.voltage.get()
TimeDuration = self.duration.get()
try:
MaxVoltage = float(MaxVoltage)
try:
TimeDuration = float(TimeDuration)
except:
self.error.set("Please enter a valid time duration")
self.Update()
self.STOP = True
except:
self.error.set("Please enter a valid voltage value")
self.Update()
self.STOP = True
TimeStart = self.DT.now()
self.HeatLoop(True,
TimeStart, TimeDuration, MaxVoltage)
def Stop(self, event):
self.STOP = True
print("turned off voltage")
def Update(self):
self.root.update_idletasks()
self.root.update()
root1 = Tk()
a = GUIClass(root1)
root1.mainloop()
root.after(5000) is no different than time.sleep(5). It's doing exactly what you're telling it to: to freeze for five seconds.
If you want to run self.HeatLoop every five seconds, the way to do it is like this:
self.root.after(5000, self.HeatLoop,
bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage,
Fraction)
When you give two or more arguments to after, tkinter will add that function to a queue, and will call that function after the time has expired. This allows the event loop to continue to process events during the five second interval.
A slightly better way to write it would be to check for the condition inside the function rather than passing the condition in, so that the condition is evaluated immediately before doing the work rather than five seconds before doing the work.
For example:
def HeatLoop(self, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
if self.STOP and Fraction < 0:
return
...
self.root.after(5000, self.HeatLoop,
TimeStart, TimeDuration, MaximumVoltage,
Fraction)