Tkinter gui stops processing (Binary clock) - python

Since I'm new to Python I hope this is a blindingly obvious question.
I'm coding a binary clock (i.e. shows 1s and 0s, but will ultimately display graphics of large LEDs)
Here is the code which I have used so far:
#Simple binary clock
#Python 3.3.2
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme= time.strftime("%H",time.localtime()) + time.strftime("%M",time.localtime()) + time.strftime("%S",time.localtime())
print(tme)
hpos=0
for c in tme: #tme set to HHMMSS, iterate through each digit
col=50+hpos*50 #Set column position
b=format(int(c),'04b') #Covert digit to 4 bit binary
vpos=0
for r in b:
row=50+vpos*50
if r=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
label1.place(x=col,y=row)
vpos+=1
hpos+=1
root.after(1000,task) #reschedule event, 1000=1sec
root.after(100,task)
root.mainloop()
The issue is this- after leaving to run the code for about 15 minutes it slows down and grinds to a halt. I've tried it on more than one PC to the same effect, I want this to work on a Raspberry Pi but again it has the same result.
I will eventually make the form fill the screen and use graphics in the label widgets- I'm open to suggestions to solving the solution in a different way.
Thanks in advance for any help you are able to offer.
JJ

The line label1 = Label(root, ...) creates a new window each time and places this on top of the previous instance. So over time you are creating more and more windows which gradually consumes your memory.
Instead, create the label once and place it. Then just update it's text property in the task so that it displays the new value in the same window instance.
Also, you can format time in one call time.strftime("%H%M%S",time.localtime())
Example
from Tkinter import *
import sys,time
class App():
def __init__(self, parent):
parent.title("Title")
parent.geometry("500x500")
self.labels = []
self.parent = parent
x,y = 50,50
for index in range(3):
label = Label(parent, text='0')
label.place(x = x, y = y)
self.labels.append(label)
y += 50
self.parent.after(1000, self.Task)
def Task(self):
t = time.strftime("%H:%M:%S", time.localtime())
print(t)
index = 0
for c in t.split(':'):
b = format(int(c), '04b')
self.labels[index].configure(text=b)
index += 1
self.parent.after(1000, self.Task)
def main():
root = Tk()
app = App(root)
root.mainloop()
if __name__=='__main__':
sys.exit(main())

I've attempted to put in the changes as outlined, I've added a separate widget for each bit, initialized them, placed them on the form.
On changing the text of the widgets the form is not updated. I have looked to see if the label widget has an update method, but apparently not. I must be missing something really obvious here.
I have a slimmed-down version of the code here which only displays the last digit of the seconds, what have I missed?:
#Simple binary clock
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme=time.strftime("%S",time.localtime()) #testing on just the seconds
tme=tme[1] #testing, just take last digit of seconds
print(tme)
for c in tme: #tme set to HHMMSS, iterate through each digit
b=format(int(c),'04b') #Covert digit to 4 bit binary
print(b)
if b[0]=="1":
label0=Label(root,text="1")
else:
label0=Label(root,text="0")
if b[1]=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
if b[2]=="1":
label2=Label(root,text="1")
else:
label2=Label(root,text="0")
if b[3]=="1":
label3=Label(root,text="1")
else:
label3=Label(root,text="0")
root.after(1000,task) #reschedule event, 1000=1sec
label0=Label(root, text="*")
label0.place(x=50,y=50)
label1=Label(root, text="*")
label1.place(x=50,y=100)
label2=Label(root, text="*")
label2.place(x=50, y=150)
label3=Label(root, text="*")
label3.place(x=50,y=200)
root.after(1000,task)
root.mainloop()

Related

Python Tkinter Label not responding

I am trying to make a loading and a GIF would be a lot helpful if it was supported in python tkinter. But since it is not supported, so I put all the frame-by-frame pictures in the list that makes a loading when played continuously (using assign_pic function) and then I created a label (called lab_loading) of which I change the picture after 200ms by calling the start_anim function. I am calling the assign_pic function in a loop which I think causes this error. See my source code below 👇 and the video I provided to understand this problem clearly.
Video: https://drive.google.com/file/d/1WHwZqvd8vXz-ehXbQ_fRtrKPEyFLKrVe/view?usp=sharing
Source code:
from time import sleep
from tkinter import Tk, Label
from PIL import ImageTk, Image
class Loading(Tk):
def __init__(self):
super().__init__()
self.title('Loading')
self.geometry('250x217')
self.address = getcwd()
self.imgs_list = []
self.loadingImgsList(self.address)
# This method Puts all the images in the list
def loadingImgsList(self, curAddress):
address = f'{curAddress}\\loading'
self.imgs_list = [(ImageTk.PhotoImage(Image.open(
f"{address}\\{i}.png"))) for i in range(1, 4)]
# This mehtod assigns the picture from the list via index (ind) number from the imgs_list list and
# updates the GUI when called.
def assign_pic(self, ind):
lab_loading.config(image=self.imgs_list[ind])
self.update_idletasks()
sleep(0.2)
def start_anim(self):
ind = 0
b = 0
while (b < 300):
if ind == 2:
ind = 0
else:
ind += 1
self.after(200, self.assign_pic, ind)
b += 1
if __name__ == "__main__":
root = Loading()
lab_loading = Label(root, image='')
lab_loading.pack()
root.start_anim()
root.mainloop()
I Tried to make start_anime function recursive but it was still the same. I don't know why this is happening. I also made the loop finite but it was still not working. So a solution to this problem or even a better suggestion would highly be appreciated.
you shouldn't be using sleep inside tk, as it blocks python from handling user actions.
the way you do animation in tk is by using the after method, to call a function that would update the canvas, this function will call after again, until the animation is complete.
# everything before this function should be here
self.ind = 0 #somewhere in __init__
def assign_pic(self):
if self.ind < len(imgs_list):
lab_loading.config(image=self.imgs_list[self.ind])
self.ind += 1
root.after(500,self.assign_pic) # time in milliseconds
else:
print("done") # do what you want after animation is done
if __name__ == "__main__":
root = Loading()
lab_loading = Label(root, image='')
lab_loading.pack()
root.after(100,root.assign_pic)
root.mainloop()
the after function schedules the given function after a certain delay, during which the GUI is free to respond to any action.
Edit: after method takes argument in milliseconds not in seconds, i had the input in it in seconds instead of milliseconds, it's now fixed.

How to update the tkinter text label every half a second? How to avoid .mainloop so that rest of program can execute?

I'm using a Kvaser Leaf Professional and trying to read the can-bus data from a motorcycle through Python coding to be able to use the data to see where values change and which values stays constant.
At the moment I'm struggling to display the data on tkinter and to update it after each time the data is read/updated again, because it changes every few microseconds. Normal ways of using .after() doesn't seem to work 100%, or I'm just doing something wrong. Or is there a better way to display updated data than tkinter?
The other problem I have is when I display the tkinter it uses a root.mailoop() to run the window and when that happens it enters that main loop and doesn't exit it and run the rest of the program and also won't be able to update the existing data displayed on the window. Is there a way to bypass the root.mainloop() maybe?
Any insight will be helpful and/or small errors I've made in my code. I started using Python only 2 days ago and I'm still learning a lot and don't know all the in's and out's yet.
Thank you in advance.
Here is my whole program so far:
import keyboard
import time
import sys
import tkinter as tk
import binascii
from canlib import canlib, Frame
from canlib.canlib import ChannelData
root = tk.Tk()
def setUpChannel(channel,
openFlags=canlib.canOPEN_ACCEPT_VIRTUAL,
bitrate=canlib.canBITRATE_500K,
bitrateFlags=canlib.canDRIVER_NORMAL):
ch = canlib.openChannel(channel, openFlags)
print("Using channel: %s, EAN: %s" % (ChannelData(channel).channel_name,
ChannelData(channel).card_upc_no)
)
ch.setBusOutputControl(bitrateFlags)
ch.setBusParams(bitrate)
ch.busOn()
return ch
def tearDownChannel(ch):
ch.busOff()
ch.close()
def text(t):
tx = binascii.hexlify(t).decode('utf-8')
n = 2
txt = [tx[i:i+n] for i in range(0, len(tx), n)]
return txt
def counter():
while True:
try:
cnt = 1
frame = ch0.read()
firstID = frame.id
while True:
frame = ch0.read()
cnt += 1
if frame.id == firstID:
break
pass
except (canlib.canNoMsg):
break
except (canlib.canError):
print("Rerun")
pass
return cnt
print("canlib version:", canlib.dllversion())
ch0 = setUpChannel(channel=0)
ch1 = setUpChannel(channel=1)
frame = Frame(id_=100, data=[1, 2, 3, 4], flags=canlib.canMSG_EXT)
ch1.write(frame)
print(frame)
cnt = counter()
print("Counter: %d" %(cnt))
time.sleep(1)
while True:
while True:
try:
show = ""
i = 1
while i <= cnt:
frame = ch0.read()
show = show +("%s\t%s\n" %(frame.id, text(frame.data)))
i += 1
print(show)
T = tk.Text(root, height=6, width=80)
T.config(state="normal")
T.insert(tk.INSERT, show)
T.pack()
root.mainloop()
break
except (canlib.canNoMsg) as ex:
pass
except (canlib.canError) as ex:
print(ex)
pass
tearDownChannel(ch0)
tearDownChannel(ch1)
You can edited my code as you want, it will help a lot if I can see how to implement the code better.
You dont need to use multi-threading or anything convoluted. Just use the widget.after() method that calls another function/method of your choosing after the desired time. In the case below, the time is 1 second -> 1000 milliseconds. Just call the class before the mainloop and it will start working.
class UI:
def __init__(self, parent):
# variable storing time
self.seconds = 0
# label displaying time
self.label.configure(text="%i s" % self.seconds)
# start the timer
self.label.after(1000, self.refresh_label)
def refresh_label(self):
#refresh the content of the label every second
# increment the time
self.seconds += 1
# display the new time
self.label.configure(text="%i s" % self.seconds)
# request tkinter to call self.refresh after 1s (the delay is given in ms)
self.label.after(1000, self.refresh_label)

Python Tkinter - Update while funtion in running

I'm learning Tkinter by making a GUI for a sudoku solver program (that was arleady built).
On the click of a button, the algorithm that solves the sudoku runs and sometimes it takes some time.
How could I update the sudoku on the screen when the function is called, so that users can see how its running?
I'm working with a gui script separate from the sudoku one, is it correct, in design terms, to have the gui and the logic separate?
Thank in advance
EDIT
This is my code:
Sudoku.py
class Sudoku(object):
def __init__(self):
self.__matrix = [[(0, Status.Guess) for x in range(9)] for y in range(9)]
...
def solveSudoku(self):
...
GUI.py
class App:
def __init__(self, master, su):
self.__sudoku__ = su
self.__root__ = master
self.__entries__ = {}
fsudoku = Frame(master)
fsudoku.grid(row=0)
self.displaysudoku(fsudoku) """grid of entrys"""
tButton = Button(master,text="Solve", command=self.SolveAndDisplay)
...
def refreshSudokuGrid(self):
"""USED AFTER SOLVING A SUDOKU"""
for i in range(1,10):
for j in range(1,10):
val = self.__sudoku__.value(i,j)
self.__entries__[i * 10 +j].delete(0, END)
if (val!= 0):
self.__entries__[i * 10 + j].insert(0, val)
def SolveAndDisplay(self):
self.scanSudoku()
self.__sudoku__.solveSudoku()
self.refreshSudokuGrid()
...
root = Tk()
su = Sudoku()
s = App(root, su)
root.mainloop()
I guess you must be using some loop which solves the sudoku.If this is true:
The place where your function/command for your button is defined,
place the following code at the beginning of the primary loop which solves the sudoku (assuming root is your tkinter window):
root.update()
As such such a method is not fully threadsafe, but should solve the problem for general cases like yours.
Keeping GUI and logic separate is the best practice.

tkinter (py3) - change image labels, inside functions, in real time

I'm learning to use tkinter, and what I've tried to do is have 4 buttons on a coffee machine, and each button would create a new window, which would show images in order, like a slideshow. What I've done doesn't work, as it only shows the last image in the slideshow, and ignores the rest. In python 2.7, I was able to fix it by printing nothing into the console after every configuration of the label, but it doesn't seem to work. If you could tell me why this happens, and/or how to fix it, it would be greatly appreciated.
(P.S. I know that my code is probably very ugly/inefficient, but bear in mind that I'm very new to tkinter, so I only really care that it works).
def Latte():
global Grind
global Hot_Water
global Cocoa_Poweder
global Steamed_Milk
global Foamed_Milk
global LattePhoto
MakingCoffee=Toplevel(Test, width=200, height=200)
MakingCoffee.wm_title("Latte")
MakingCoffee.iconbitmap("Photos\Latte.ico")
photolabel= Label(MakingCoffee,image=Grind)
photolabel.pack()
time.sleep(2)
photolabel.configure(image=Hot_Water)
time.sleep(2)
photolabel.configure(image=Steamed_Milk)
time.sleep(4)
photolabel.configure(image=Foamed_Milk)
time.sleep(1)
photolabel.configure(image=LattePhoto)
time.sleep(2)
MakingCoffee.destroy()
Here's a small sample of one way to make this work. You can restructure as needed. The list is just so I can iterate through it showing the changes. You don't need all of those globals in your original code. They're already global variables and you're not attempting to reassign them so this a redundancy. You can factor out ~50% of your code at least to be reusable. You can make this a class and make a "factory class" to create different types of slideshows with different types of coffee, and then you could also get rid of the global here as well.
from tkinter import *
import tkinter
import time
Test = tkinter.Tk()
Test.wm_title("Coffee Store")
Test.resizable(0, 0)
americano_images = [
PhotoImage(file='1.gif'),
PhotoImage(file='2.gif'),
PhotoImage(file='3.gif')
]
AFTER = None
AmericanoPhoto= PhotoImage(file="1.gif")
def switch_images(im_list, label, index=0):
global AFTER
label.configure(image=im_list[index])
if index != len(im_list) - 1:
index += 1
else:
index = 0
AFTER = Test.after(1000, lambda: switch_images(im_list, label, index))
def Americano():
MakingCoffee=Toplevel(Test, width=200, height=200)
MakingCoffee.wm_title("Americano")
photolabel= Label(MakingCoffee)
photolabel.pack_propagate(0)
photolabel.pack()
after = switch_images(americano_images, photolabel)
cancel = Button(MakingCoffee, text='Kill Slideshow',
command=lambda: Test.after_cancel(AFTER))
cancel.pack(fill=X, expand=1)
B1= Button(Test, text='BUTTON', command=Americano)
B1.grid(row=0,column=0)
Test.mainloop()

Updating tkinter window from another module

I have a piece of code that does some textual analysis and displays the results in a tkinter window.
Since users can choose to do the same analysis on multiple sources, and this can take quite some time, I would like to display the results as they become available.
However, the tkinter window pops up only after the last result becomes available. From other questions on Stackoverflow, I understand why this does not work (simplified version of my code):
class Output:
def __init__(self):
self.set_main_variables()
def set_main_variables(self):
self.names= []
#and other variables...
def initialize(self):
self.t = Tk()
self.make_frames()
self.populate_frames()
self.t.mainloop()
def update(self):
self.populate_frames()
def populate_frames(self):
<uses the data in self.names to display the results>
output = Output()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
if i == 0:
output.initialize()
else:
output.update()
Therefore, I tried taking the initialize() out of the loop, and creating a button in the window by which the user can update the results (if any are available):
output = Output()
first_results = analyze(s[0])
output.names = first_results
output.initialize()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
This does not solve the problem, though; the window still pops up only after the last source has been analyzed.
I have read many possible options to deal with this (using after, guiloop, etc.) but I don't see how these could help me in this situation.
Could anyone set me on the right path?
A colleague found the solution to my question.
Eric, in the comments, put me partly on the right path. He proposed using update_idletasks() in the GUI, but that did not work: it opened a blank window, which was filled in with widgets only when the "sources" loop was finished.
The solution was to put update() not inside the GUI, but inside the loop that is sending the updates to the GUI.
A simplified example with working code:
from tkinter import *
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
#self.top.mainloop()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
print(i)
gui.names.append(names[i])
print(gui.names)
gui.updateLabel()
gui.top.update()
#gui.top.update_idletasks()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)
Two notes:
using update_idletasks() works as well, but displays a strange black
border at the bottom of the window until the sources loop is
finished.
calling self.top.mainloop() blocks the window update
I also tried an alternative version, where the user can push a button to manually update the list of results, but this did not function well (the button reacts only when the sources loop is finished):
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
Button(self.top, text="update", command=self.updateLabel).pack()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
gui.names.append(names[i])
print(gui.names)
gui.top.update()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)

Categories

Resources