tkinter window freezes after some time - python

I am new to Tkinter. I was experimenting with this, but while the Tkinter starts just fine, it some times freezes after a while for no reason. Actually, the weird thing that happens, is that while the Tkinter windows do not update the information anymore, the counter keeps printing ON MY WINDOWS DESKTOP LEFT TOP SIDE! (just the black bg box with the white font that shows the counter number) and eventually finds number 777 and ends. But long freezes in between, even without any printing (not on my Windows desktop either)
this is the normal tkinter window
this is the console output
all good here
crazy Windows display here
The simple program here is generating random ints from 1 - 10000 and should 1) if 777 is found it should print the counter and exit 2) if any number between 1-100 is found, it should print the number and counter.
I am trying to learn how to print new content in the same line, and if something happens print more info on another line, while the first (or more) keep updating.
I'm not sure if I exit the program the right way or how exactly I should be using/calling the Tkinter function to update the info on the screen.
Any help or tips most appreciated.
from tkinter import *
import random
import time
tk=Tk()
i = 0
counter = 0
tk.title("Testing...")
tk.geometry('300x200')
canvas_width = 200
canvas_height = 100
canvas = Canvas(tk, width=canvas_width, height=canvas_height, bg='white')
canvas.pack(expand=YES)
def screen(Found):
#canvas.create_text(20, 20, text="Counter:" + str(counter), font="Times 20 italic")
widget = Label(canvas, text="Counter:" + str(counter), fg='white', bg='black')
widget.grid(column=0, row=0)
if Found:
widget = Label(canvas, text="Number " + str(i) +" was found at counter:" + str(counter), fg='white', bg='black')
widget.grid(column=0, row=1)
found = False
loop = True
while loop:
counter += 1
i = random.randint(1, 10000)
if i in range(1, 100):
print("Found i = ", i, " in counter:", counter)
Found = True
else:
Found = False
if i == 777:
loop = False
print("JUST FOUND 777")
print("Counter:", counter)
screen(Found)
tk.update_idletasks()
tk.update()
# tk.mainloop()

As a matter of design, it is usually a better approach to rely on the tkinter mainloop to drive the application. root.after is a great substitute to a while loop. Labels do not need to be recreated; they can use special variables that update the displayed values themselves, and their properties can be reconfigured. Calling update, and update_idletasks is hardly ever needed.
The following is maybe a better approach where one single label is updated from a loop powered by the tkinter mainloop:
(it shows you "how to keep displaying new stuff, without such label issues")
import tkinter as tk
import random
def screen(found, v, c):
if found:
label.configure(bg='red', fg='blue')
print('found')
else:
label.configure(bg='black', fg='white')
update_lbl_txt(v, c)
def update_lbl_txt(v=0, c=0):
txt = f'Number at {v} was found at counter {c}'
lbl_txt.set(txt)
def found_target(t):
return t in range(1, 100) or t == 777
def loop(c=0):
v = random.randint(1, 10000)
screen(found_target(v), v, c)
root.after(100, loop, c+1)
root = tk.Tk()
root.title("Testing...")
root.geometry('300x200')
lbl_txt = tk.StringVar(root)
update_lbl_txt()
label = tk.Label(root, textvariable=lbl_txt, fg='white', bg='black')
label.pack(expand=True, fill=tk.BOTH)
loop()
root.mainloop()

The reason it seems to freeze it at least partly because you have an infinite loop that you never, ever break out of. Most of the time i will not be in the range of 1 to 100, nor will it be 777, but your loop will keep on running and never update the screen.
You're also creating hundreds or thousands of label widgets every second, stacked on top of one another. You will run into performance issues rather quickly.

Related

Tkinter window Completly freeze when you move it

The window of Tkinter just completly freeze with all the widgets when I move the Tkinter window and that's my problem I tested it with another code and it always does the same thing
Is the problem exclusively with tkinter?
just move your tkinter window from left to right you will see that absolutely all the program freeze it's incredible
Someone said to put main in a separated thread but how ? Like without example I don't even know what It means :(
how do you put the threads outside of mainloop() ? What does it mean ? I putted root.mainloop() before the thread1 = threading.Thread(target= lambda : fct(), daemon=True) thread1.start() and it does nothing
from tkinter import *
from tkinter import ttk
import time
import threading
import win32api
import pyautogui
root = Tk()
root.geometry('800x438')
root.resizable(False,False)
root.configure(bg='gray')
label = Label(root, text='Display content', fg='yellow', bg='black', font=('Arial', 13), width=20)
label.place(relx=0.5,rely=0.3)
firstentryvar = StringVar()
secondentryvar = StringVar()
firstentry = Entry(root, textvariable=firstentryvar , justify=CENTER, font = ('Arial', 12))
secondentry = Entry(root, textvariable=secondentryvar, justify=CENTER, font = ('Arial', 12))
def displaycontent(*args):
firstentry.pack()
secondentry.pack()
label.bind('<Button-1>', hidecontent)
def hidecontent(*args):
firstentry.pack_forget()
secondentry.pack_forget()
label.bind('<Button-1>', displaycontent)
label.bind('<Button-1>', displaycontent)
def function1(*args):
count = 0
bool = False
while count < 10:
for i in firstentry.get():
if bool == False:
count +=1
print(i)
bool = True
else:
bool = False
def function2(*args):
while True:
if win32api.GetKeyState(0x45) < 0:
print('you pressed e')
thread1 = threading.Thread(target = lambda : function1(), daemon=True)
thread1.start()
thread2 = threading.Thread(target = lambda : function2(), daemon=True)
thread2.start()
root.mainloop()
the code may not mean much but it's enough to reproduce my example, well you will notice that if you click on the display label and then move the window without entering anything in the entries the window will bug/freeze why?
I played a bit with your code and it seems that problem is with your while loops.
Even though you used threads correctly, using while loops this way makes your program uses all the resources to loop into it. What I means is as you started program, even before you press label to show entry widgets, your loops just iterated thousand of times if not tens of thousands.
However, simply putting a time sleep, you can easily stop this exponential resource consuming. However, you shouldn't use time.sleep with tkinter if you aren't using inside threads. As we are using loops inside threads, there is no problem.
For example:
def function1(*args):
count = 0
bool = False
while count < 10:
time.sleep(0.1)
for i in firstentry.get():
if bool == False:
count +=1
print(i)
bool = True
else:
bool = False

Python: why does first restart works, but second - doesn't?

This is a simple minesweeper game with implementation of windows user interface
The only thing this function has to do is to erase all information (such as text) on the buttons, as well as to create a new random array. It works completely well the 1st time after i press the button, but the second it doesn't work. 2nd time it erases everything (it does its job again as planned), but other functions don't work (I press the buttons after the 2nd restart, and nothing happens, but after the 1st restart everything is fine).
What's going on?? Is it a problem of the memory, where variables are stored, or a specific of the graphical user interface, I am not aware of?
from tkinter import *
def new_game():
lost = False
label['text'] = str(mines) + ' mines left'
global mine_sweep
mine_sweep = mine_randomization().tolist()
for row in range(10):
for col in range(10):
buttons[row][col]['text'] = ''
window = Tk()
window.title('minesweeper')
label = Label(text=str(mines)+' mines left', font=('consolas', 20))
label.pack(side='top')
reset_button = Button(text='restart', command=new_game)
reset_button.pack(side='top')
buttons = buttons.tolist()
frame = Frame(window)
frame.pack()
for row in range(10):
for col in range(10):
buttons[row][col] = Button(frame, text='', font=('consolas', 10),
width=2, height=1,
command= lambda row=row, col=col: cell(row, col))
buttons[row][col].grid(row=row, column=col)
window.mainloop()
(I can't place the whole program here, just part which doesn't work)
here is what the function cell does:
def cell(row, col):
global lost
if buttons[row][col]['text'] == '' and mine_sweep[row][col] == 0 and not lost:
open_fields(row, col)
elif buttons[row][col]['text'] == '' and mine_sweep[row][col] == 1 and not lost:
buttons[row][col].config(bg='red', font=('consolas', 10))
buttons[row][col]['text'] = '*'
label['text'] = 'You lost!'
lost = True
if check_win():
label['text'] = 'You win!'
Yes, #Matiiss was wright, and the solution was that the variable lost is used in different functions, that's why it should be global. Moreover, when the first game is complete, the lost should again be set to false, in order to start a new game and the computer to know you have actually not yet 'lost'.

Python how to use Tkinter GUI without interfering the main code loop

I would like to implement a very simple GUI for my project. I was previously using just Print statements to output some text and data. However, that is not very conveneint and since a person will need to operate a device that I am coding, he needs to be clearly see the instructions that I am going to display on GUI.
my code:
main()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.title("GUI")
window.geometry("400x200")
while(1):
# OPERACIJOS KODAI:
# 0 - PILDYMAS
# 1 - KOMPLEKTAVIMAS
# 2 - NETINKAMAS KODAS
tk.Label(window,text = "Scan barcode here:").pack()
entry = tk.Entry(window)
entry.pack()
var = tk.IntVar()
button = tk.Button(window,text="Continue",command = lambda: var.set(1))
button.pack()
print("waiting...")
button.wait_variable(var)
result = entry.get()
print("Entry string=",result)
var.set(0)
operacijos_kodas=Scanning_operation(myConnection,result)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
tk.label(window,text = "PILDYMO OPERACIJA:").pack()
pildymo_operacija(myConnection)
elif(operacijos_kodas == 1):
tk.Label(window,text = "PAKAVIMO OPERACIJA:").pack()
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
picking_operation();
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
#break
window.mainloop();
Nothing is being displayed. It just opens up an empty GUI window.
First of all, I am unsure where should I call function window.mainloop().
Secondly, since my system runs in an infinite while loop ( the operation starts when a user scans a bar-code, then he completes an operation and the while loop starts over again (waiting for user to scan a bar-code). So I just simply have to display some text and allow user to input data in the text box.
Could someone suggest me whether this GUI is suitable for my needs or I should look for an alternatives?
UPDATE*********************
I have tried to use mainloop:
print ("Using mysql.connector…")
main()
GPIO_SETUP()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.resizable(False,False)
window_height = 1000
window_width = 1200
#window.attributes('-fullscreen',True)
#window.config(height=500,width=500)
#can = Canvas(window,bg='red',height=100,width=100)
#can.place(relx=0.5,rely=0.5,anchor='center')
window.title("GUI")
screen_width = window.winfo_screenwidth()
screen_height= window.winfo_screenheight()
x = int((screen_width/ 2) - (window_width / 2))
y = int((screen_height/ 2) - (window_height / 2))
window.geometry("{}x{}+{}+{}".format(window_width,window_height,x,y))
label1=Label(window,text = "SKENUOKITE BARKODA(GUID) ARBA DAIKTO RIVILINI KODA:")
label1.pack()
entry = Entry(window)
entry.pack()
var = tk.IntVar()
button = Button(window,text="Testi operacija",width = 30,command = lambda: var.set(1))
button.pack()
#button2 = Button(window,text="RESTARTUOTI SISTEMA",width = 30,command = restart_devices())
#button2.pack()
print("waiting...")
button.wait_variable(var)
Scanned_serial = entry.get()
print("Entry string=",Scanned_serial)
var.set(0)
label2=Label(window,text = "Vykdoma operacija:")
label2.pack()
window.update()
window.after(1000,Full_operation(Scanned_serial,label2,window))
window.mainloop()
This is my code. As you can see. i call Full_operation function and then window.mainloop()
my Full_operation:
def Full_operation(Scanned_serial,label2,window):
operacijos_kodas=Scanning_operation(myConnection,Scanned_serial)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
label2.config(text = "SPAUSKITE MYGTUKA ANT DEZES KURIA NORITE PILDYTI:")#update the label2
window.update()#call update to update the label
pildymo_operacija(myConnection,Scanned_serial,label2,window)
elif(operacijos_kodas == 1):
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
#label2.config(text = "IMKITE DAIKTUS IS ZALIOS DEZUTES:")#update the label2
picking_operation(myConnection,label2);
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
label2.config(text = "NUSKENUOTAS NEGALIMAS KODAS:")#update the label2
window.update()#call update to update the label
How can I ensure that everytime I enter FUll_operation function I start from clean GUI again and start another operation.
Now I am able to complete operation once. After that, the GUI is not responsive.
I have added a print statement at the beggining of my full_operation and it does not execute after I complete it once so my mainwindow does not seem to work properly.
You'll need to adapt your code to work with a GUI. You can't introduce infinite loops in to tkinter GUI's without causing all sorts of problems.
Mainloop should only be called once.
I'd suggest that you move all of your scanning/saving operations in to a separate function which you schedule to occur periodically using the tkinter after method.
For example if you call your function scan you would schedule it to occur after 1 second using
root.after(1000, scan)
A more advanced method would be to have your scanning code running on a separate thread.
Also, you are currently trying to create the label each time you go round the while loop rather than just creating and packing them once and updating the text of the labels when you perform the "scanning". You can update the text of a label using the config method, for example
## Create a label
label1 = tk.Label(window,text = "PAKAVIMO OPERACIJA:")
##Pack the label
label1.pack()
## Update the text later
label1.config(text="New Text")
Here is an example of updating tkinter widgets periodically from a function.
import tkinter as tk
import random
def scanning():
num = random.randint(0,100)
entryTemperature.delete(0, tk.END) #Delete the current contents
entryTemperature.insert(0, f"{num} K") #Add new text
root.after(1000, scanning) #Schedule the function to run again in 1000ms (1 second)
root = tk.Tk()
entryTemperature = tk.Entry(root)
entryTemperature.grid(padx=50,pady=50)
root.after(1000, scanning)
root.mainloop()

Why isn't the label animation working till the last value of the loop?

I am new to python and I have been learning tkinter recently. So I thought with myself that using the grid_forget() function I can remove a widget and redefine it. I thought of this animation that changes the padding of a label so it would create space (kind of like moving the label but not exactly). However, the animation does not work at all. The program freezes until the label reaches the last value of the padding. How can I fix this? Or is there a better way to animate a label moving in the screen?
Here is my code:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
You need to update the screen for changes to be shown.
Here is a working version using the .update() method:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
root.update()
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
Here is a way I also use to animate stuff on the screen, I am not able to understand what you were trying to achieve with your code snippet above, I tried making some changes to it but I feel this way is much better and let's you get more control of your window.
This uses the widely used Canvas widget in the tkinter library.
The Canvas is a general purpose widget, You can use it for a lot of things. Visit the hyper link for more clarity
Here is a short example of how you would create text on the screen.
from tkinter import *
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = '-----')
c.pack()
# This is all you need to create this text on your screen!
root.mainloop()
The idea is that you put your canvas up on your window , and then place whatever you want on it.
There are a lot more attributes that you can add to make your text look even better. Here is an in-depth tutorial on it.
Now that we have made your text widget, It is now time to move it around. Let us move it to 90,20 From our initial position which is 20,20
Here is how we will do it. If we simply move to text object to 90,90, We won't see any animations, it will just directly have it there. So what we will do is first create it at 21,20. Then 22,20. And so on...
We do this really fast till we reach 90,20
This looks like we are moving the text
from tkinter import *
import time
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = 'weee')
c.pack()
def animation():
y = 0.1
x = 0
for _ in range(1000):
c.move(my_text,x,y)
root.update()
anlabel = Button(root,text = 'Animate!',command = animation).pack()
root.mainloop()
This is not only applicable to text, but everything (like other images)that is there on the canvas. The canvas also has Events which will let you use mouse-clicks and other keys on the computer too.
I have made some changes from the previous code, But it is executable and you can try it for yourself to see how it works. increasing the value in time.sleep() makes the animation slower, the lesser the value, the faster.
Are you sure you aren't trying to do something more like the below example? Animating the padding on one of your widgets is going to screw up the rest of your display.
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='')
lbl.grid(row=0, column=0)
def animation(step=12):
step = 12 if step < 0 else step
lbl['text'] = ' ------ '[step:step+6]
root.after(200, lambda: animation(step-1))
Button(root, text='Animate', command=animation).grid(row=1, column=0, sticky='w')
root.mainloop()

Fast changing labels in Tkinter?

I would like to display some numbers, as fast as possible in Tkinter. The Program, I am trying to do, gets many numbers send and should show those.
Here is an similar environment, where tinter has to change a label very quickly.
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
time.sleep(2)
x +=1
window.mainloop()
The Tkinter window doesn't even open on my computer. Is that because i have too weak hardware? What could I change that this Program also runs on my Computer. Thank you for every answer!
The infinite while loop will keep the program from getting to the line where you call window.mainloop(). You should call window.update() repeatedly instead of window.mainloop() at the end:
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
window.update()
x +=1
Using after and a proper mainloop is probably a more more flexible way to achieve what you want; it is also reusable in different contexts, and can be used in an application that does more than trivially increment a number on a label:
maybe something like this:
import tkinter as tk
if __name__ == '__main__':
def increment():
var.set(var.get() + 1)
label.after(1, increment)
window = tk.Tk()
var = tk.IntVar(0)
label = tk.Label(window, textvariable=var)
label.pack()
increment()
window.mainloop()

Categories