Python tkinter image can't change immediately? - python

My code:
from tkinter import *
import random
from PIL import ImageTk, Image
import time
class App():
def __init__(self, root, dice_image):
self.dice_label = Label(root, image=dice_image)
self.dice_label.pack()
self.button = Button(root, text="Roll", font=("Fixedsys", 25) , width=20, height=20, bg="red" , command=rolling)
self.button.pack(pady=70)
I want to make the image change fast to make it look like rolling. But I don't know why it change nothing before the for loop end.
def rolling():
global dices
for i in range(1, 10 + 1):
time.sleep(0.3)
random.shuffle(dices)
app.dice_label.configure(image=dices[0])
if __name__ == '__main__':
root = Tk()
root.geometry("800x600")
# Var
dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
dice1 = ImageTk.PhotoImage(Image.open("Dice1.jpg"))
dice2 = ImageTk.PhotoImage(Image.open("Dice2.jpg"))
dice3 = ImageTk.PhotoImage(Image.open("Dice3.jpg"))
dice4 = ImageTk.PhotoImage(Image.open("Dice4.jpg"))
dice5 = ImageTk.PhotoImage(Image.open("Dice5.jpg"))
dice6 = ImageTk.PhotoImage(Image.open("Dice6.jpg"))
dices = [dice1, dice2, dice3, dice4, dice5, dice6]
app = App(root, dice_image)
root.mainloop()

Tkinter and sleep() are not friends. The problem is that Tkinter functions inside of a mainloop and sleep pauses that loop until all the sleep time is over.
This will always freeze your application.
Try using after() and a refactored function instead.
I have not tested this but I believe something like this should fix your problem or at least get rid of the problem that sleep() will cause.
I have changed your button command to send a call to the function using lambda so that the call wont go out at runtime but only when pressing the button.
from tkinter import *
import random
from PIL import ImageTk, Image
class App():
def __init__(self, root, dice_image):
self.dice_label = Label(root, image=dice_image)
self.dice_label.pack()
self.button = Button(root, text="Roll", font=("Fixedsys", 25),
width=20, height=20, bg="red" ,
command=lambda: rolling(1,11))
self.button.pack(pady=70)
I have updated your rolling function to handly a timed loop.
root.after(300, lambda: rolling(stop, counter)) will call the function it is in only if the counter has not finished counting down and only once every 0.3 seconds. Again lambda is used here to make sure the call to rolling does not happen at runtime.
def rolling(s, e, start=False):
global dices
counter = 0
stop = 0
if start:
counter = s
stop = e
if counter > stop:
random.shuffle(dices)
app.dice_label.configure(image=dices[0])
counter -= 1
root.after(300, lambda: rolling(stop, counter))
I have also updated this portion to use a loop so you don't have to repeat yourself when crating a list of images.
if __name__ == '__main__':
root = Tk()
root.geometry("800x600")
dice_image = ImageTk.PhotoImage(Image.open("Dices.jpg"))
dices = []
for i in range(6):
dices.append(ImageTk.PhotoImage(Image.open(f"Dice{i+1}.jpg")))
app = App(root, dice_image)
root.mainloop()

Related

Stop function doesn't work using progressbar

I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()

Tkinter GUI freezes while running looping

I'm new to python coding and I have been working on a project which could click on an image based on a chosen color. I have been using a program which loops the search 50 times when I click the start button. However, I have been trying to implement a stop button, but the problem is that my code freezes when the loop is running. Any ideas?
I have heard to try threading but it seems very complicated and I have been unable to follow any tutorials properly in relation to my code. By the way, the image searched has been testing images I've been using stored inside the program files.
from imagesearch import *
import pyautogui
import tkinter as tk
from tkinter import *
from tkinter.ttk import *
import time
import threading
# ---Defined Programs---
def run():
global enterColor
enterColor = str(enterColorField.get())
program(enterColor)
def program(color):
whitePos = imagesearch_numLoop(str(color) + ".PNG", 0, 50)
pyautogui.moveTo(whitePos[0] + 20, whitePos[1] + 10)
pyautogui.click()
def stop():
print("Placeholder")
# ---Main Runner---
window = tk.Tk()
window.geometry("250x250")
window.configure(background="#181b54")
app = tk.Frame(window)
app.grid()
enterColorLabel = tk.Label(window, text="Enter Color:", bg="#181b54", fg="white")
enterColorLabel.place(x=10, y=50)
enterColorField = Combobox(window)
enterColorField['values'] = ("Black", "White")
enterColorField.current("0") # set the selected item
enterColorField.place(x=10, y=70)
submitButton = tk.Button(window, text="Start", bg="#66ff00", command=run)
submitButton.place(x=10, y=130)
stopButton = tk.Button(window, text="Stop", bg="red", command=stop)
stopButton.place(x=50, y=130)
window.mainloop()
#---New Python Script---
import cv2
import numpy as np
import pyautogui
import random
import time
def imagesearch_numLoop(image, timesample, maxSamples, precision=0.8):
pos = imagesearch(image, precision)
count = 0
while pos[0] == -1:
print(image+" not found, waiting")
count = count + 1
if count>maxSamples:
break
pos = imagesearch(image, precision)
return pos
Whenever clicking start, the whole code freezes. I can't even (x) out.
Here's a hopefully simple multiprocessing recipe that will work for you. We'll have three main functions. The first will be an example loop that you would put your processing inside. I included arguments in the function to show you that it's possible to pass args and kwargs while using multiprocessing.
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
Next is a function we will use to queue the multiprocessing process.
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
Then, you may be interested in knowing the status of your process throughout its execution. For example it is sometimes desirable to disable certain buttons while a command is being run.
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
Throwing this all together into one snippet:
import tkinter as tk
import multiprocessing
import time
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
if __name__ == "__main__":
root = tk.Tk()
mp_button = tk.Button(master = root, text = "Using MP", command = queue_loop)
mp_button.pack()
label = tk.Label(master = root, text = "MP Not Running")
label.pack()
not_mp_button = tk.Button(master = root, text = "Not MP", command = lambda: loop(1,2,3,4))
not_mp_button.pack()
root.mainloop()
The result is that when you click the "Using MP" button, the command buttons will be disabled and the process will be started without freezing your UI. Clicking the "Not MP" button will start the function like 'normal' and will freeze your UI as you noticed in your own code.
A simple answer is you cannot use while loop in GUI design.
But you can use the method .after(delay, callback=None) instead.
Here is an example:
from tkinter import *
root = Tk()
def loop():
print("Hi!")
root.after(1000, loop) # 1000 is equal to 1 second.
root.after(1000, loop) # This line is to call loop() in 1 second.
root.mainloop()

tkinter multiprocessing-root window no responding while counting down

Currently, I am working on my course work project and there is a simple function I want to achieve. The program is mainly on tkinter, I want the label shows up for 3 seconds, hide for 7 seconds for one period, and in the next period the text in label should change; while the label changes I am trying to disable an entry box from the first 3 seconds then normalise it.
Countdown and change of label text were alright, but the entry box does not respond at all when it is normalised.
here is my code
def c_time():
from tkinter import *
import time
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
root.update()
def la_diappear():
root.after(3000)
la.pack_forget()
root.update()
def la_appear():
root.after(7000)
la.pack()
la_diappear()
la_appear()
root.mainloop()
c_time()
Both root.after and time.sleep methods were tried
and I tried multiprocessing when I reached information about GIL in python:
from multiprocessing import Process
import time
from tkinter import *
def count_down():
global total
total = 5
for i in range(total):
time.sleep(1)
total -= 1
print(total)
def tkwindow():
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
root.update()
count_down()
if total == 3:
la.pack_forget()
root.update()
if total == 5:
la.pack()
root.mainloop()
if __name__ == "__main__":
a = Process(target=count_down)
b = Process(target=tkwindow)
b.start()
the code above should be work straight away.
plz reply if any thought related
Thank you very much.
You can wrap the for loop in a function, and thread that function. Then you can also use time.sleep without blocking the main thread.
from tkinter import *
from threading import Thread
import time
root=Tk()
en = Entry(root)
en.pack(side=TOP)
en.focus_force()
la = Label(root, text='6666')
la.pack(side=BOTTOM)
def la_diappear():
la.pack_forget()
def la_appear():
la.pack()
def actions():
li = ['a', 'b','c','d']
for i in li:
la.config(text=i)
time.sleep(3)
la_diappear()
time.sleep(7)
la_appear()
t = Thread(target=actions)
t.start()
root.mainloop()

Remove buttons for a few seconds in Python/Tkinter?

I'm writing a program where the user will make a selection based on a target image. I'm trying to get the program to remove the selection buttons and wait 2 seconds after updating the target image before the selection choices are re-presented. The code that I have seems to "disable" the clicked button for 2 seconds, but does not remove either button.
from tkinter import *
import random
root = Tk()
root.geometry("500x500")
def click_b(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns())
def click_c(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns())
def new_a():
k = random.randrange(1, 3)
if k == 1:
btn_a.configure(image=a1)
elif k == 2:
btn_a.configure(image=a2)
def show_btns():
btn_b.pack(side=LEFT)
btn_c.pack(side=RIGHT)
a1 = PhotoImage(file="A1.gif")
a2 = PhotoImage(file="A2.gif")
orange = PhotoImage(file="orange_btn.gif")
green = PhotoImage(file="yellowgreen_btn.gif")
btn_a = Button(root, image=a1)
btn_a.pack()
btn_b = Button(root, image=orange)
btn_b.bind('<Button-1>', click_b)
btn_b.pack(side=LEFT)
btn_c = Button(root, image=green)
btn_c.bind('<Button-1>', click_c)
btn_c.pack(side=RIGHT)
root.mainloop()
the issues is in your after() methods.
You need to remove the brackets for the show_btns function call or else tkinter will not run this command properly. If you have a function with no arguments you leave off the () portion.
If you do have arguments then you will need to either provide those arguments in the after statement IE after(2000, some_func, arg1, arg2) or use lambda to create a one off function to do the work like after(2000, lambda: some_func(arg1, arg2)). lambda can be more complicated but this is the basic concept.
change:
after(2000, show_btns())
To:
after(2000, show_btns)
As long as your paths to your images work fine the below code should work as intended.
from tkinter import *
import random
root = Tk()
root.geometry("500x500")
def click_b(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns)
def click_c(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns)
def new_a():
k = random.randrange(1, 3)
if k == 1:
btn_a.configure(image=a1)
elif k == 2:
btn_a.configure(image=a2)
def show_btns():
btn_b.pack(side=LEFT)
btn_c.pack(side=RIGHT)
a1 = PhotoImage(file="A1.gif")
a2 = PhotoImage(file="A2.gif")
orange = PhotoImage(file="orange_btn.gif")
green = PhotoImage(file="yellowgreen_btn.gif")
btn_a = Button(root, image=a1)
btn_a.pack()
btn_b = Button(root, image=orange)
btn_b.bind('<Button-1>', click_b)
btn_b.pack(side=LEFT)
btn_c = Button(root, image=green)
btn_c.bind('<Button-1>', click_c)
btn_c.pack(side=RIGHT)
root.mainloop()

Python tkinter GUI freezing/crashing

from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()

Categories

Resources