I am making a program in Python 3 with tkinter that requires a pause/play button to stop and start a loop. I am doing this using multithreading. The function that runs the loop takes an argument that is a value input by the user.
The example below shows part of the program I am working on. It is based off of this code https://pastebin.com/qC37WJfA.
from tkinter import *
import itertools
import threading
global entry_text
#constants for threading
START = 1
STOP = 0
EXIT = -1
looping = STOP
#stores position of pause/play button
toggle_play_pause = itertools.cycle(['playing', 'paused'])
class ThreadingTest():
def __init__(self, win):
self.win = win
self.win.title('Threading Test')
entry_text = ''
self.build_play_frame()
thread = threading.Thread(target=test_loop, args=(entry_text,))
thread.daemon = True
thread.start()
def build_play_frame(self):
self.play_frame = Frame(self.win, width=150, height=250, padx=20)
#creating widgets
self.play_pause_button = Button(self.play_frame, text='play', command=self.on_play_pause_button_clicked)
self.entry_field = Entry(self.play_frame)
#mapping widgets to location
self.play_pause_button.grid(row=1, column=0)
self.entry_field.grid(row=0, column=0)
self.play_frame.grid(row=0, column=0)
#this function is ran when the play/pause button is clicked
def on_play_pause_button_clicked(self):
global looping
global entry_text
entry_text = self.entry_field.get()
action = next(toggle_play_pause)
if action == 'playing':
self.play_pause_button.config(text='Pause')
looping = START
else:
self.play_pause_button.config(text='Play')
looping = STOP
def run():
win = Tk()
ThreadingTest(win)
win.mainloop()
looping = EXIT
def test_loop(text):
while True:
if looping == START:
print(text)
if looping == EXIT:
break
if __name__ == '__main__':
run()
The program should take the input from the entry field and keep on printing this value when the pause/play button is clicked until it is clicked again to stop it. The problem is that the variable entry_text does not update and therefore it prints the blank string assigned to it at the start rather than the text in the entry field. I assume this is because the value of the variable rather than the actual variable is passed here.
thread = threading.Thread(target=test_loop, args=(entry_text,))
Any guidance as to how to get this to work would be very much appreciated.
Many thanks,
Charlie
Related
I've recently been using python....
I cannot understand why the while loop prevents the creation of the tkinter window.
If I move the while loop before the mainloop I display the tkinter window but the loop stops.
import tkinter as tk
import time
from Adafruit_IO import Client, Feed, RequestError
ADAFRUIT_IO_USERNAME = "***********"
ADAFRUIT_IO_KEY = "**********************"
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)
loop_delay = 5
temp = 25
try:
temperature = aio.feeds('temperature')
except RequestError:
feed = Feed(name="temperature")
temperature = aio.create_feed(feed)
def sendtemp(temp):
aio.send_data(temperature.key,temp)
data = aio.receive(temperature.key)
print(data.value)
window = tk.Tk()
window.title ("Thermometer")
window.geometry("300x100")
label = tk.Label(window, text = temp)
label.pack()
window.mainloop
while True:
sendtemp(temp)
time.sleep(loop_delay)
I solved it this way, what do you think?
import tkinter as tk
from Adafruit_IO import Client, Feed, RequestError
ADAFRUIT_IO_USERNAME = "**********"
ADAFRUIT_IO_KEY = "***************"
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)
try:
temperature = aio.feeds('temperature')
except RequestError:
feed = Feed(name="temperature")
temperature = aio.create_feed(feed)
class Timer:
def __init__(self, parent):
self.temp = 25
self.label = tk.Label(text="--,- °C", font="Arial 30", width=10)
self.label.pack()
self.label.after(5000, self.sendtemp)
def sendtemp(self):
aio.send_data(temperature.key,self.temp)
data = aio.receive(temperature.key)
print(data.value)
self.label.configure(text="%i°C" % self.temp)
self.temp +=1
self.label.after(5000, self.sendtemp)
if __name__ == "__main__":
window = tk.Tk()
window.title ("Thermometer")
window.geometry("300x100")
timer = Timer(window)
window.mainloop()
Your while loop is blocking the program. It literally does nothing but calling sendtemp and sleeping.
while True:
sendtemp(temp)
time.sleep(loop_delay)
There is no room for reacting to events on the TK window, Python is busy doing those two things.
To do actions periodically, you need to set up a timer that runs on TK's main event loop (the one you start with .mainloop()). This is done with the .after() method.
This method takes a millisecond delay and a function you want to call.
window = tk.Tk()
window.title ("Thermometer")
window.geometry("300x100")
timer_id = None
timer_delay = 5000
def sendtemp(temp):
aio.send_data(temperature.key,temp)
data = aio.receive(temperature.key)
print(data.value)
if timer_id is not None:
start_timer()
def start_timer():
global timer_id
timer_id = window.after(timer_delay, sendtemp)
def stop_timer():
global timer_id
if timer_id is not None:
window.after_cancel(timer_id)
timer_id = None
start_timer()
window.mainloop()
You could bind stopping and starting the timer to a button.
I'm making a small GUI aplication with tkinter buttons, when button1 is clicked, a function which has infinite loop runs.
I would like to have the possibility to stop the process with another button and reset the aplication to the initial state. I don't know how to do because when the button1 starts the script , button2 is blocked. I want to stop execution of function1 on button2 click. This is my code .
from tkinter import *
root = Tk()
def function1():
x =0
while True : # infinite loop
print(x)
x = x + 1
def function2():
sys.exit()
btn1 = Button(root, text ="Button 1", command = function1)
btn1.place(x=200, y=200)
btn2 = Button(root, text ="Button 2", command = function2)
btn2.place(x= 300,y=300)
root.geometry("400x400")
mainloop()
Thank you.
you can not run the infinite loop in buttons instead you can use recursion. You can call a function recursively and fuction1, function2 can be the functions used to control any variable. Please go thru the following code:
from tkinter import *
running = False
x=0
def scanning():
global x
if running:
x = x+1
print (x)
# After 10 milli second, call scanning again (create a recursive loop)
root.after(10, scanning)
def start():
global running
running = True
def stop():
global running
running = False
root = Tk()
root.title("Continuous Loop")
root.geometry("100x100")
app = Frame(root)
app.grid()
start = Button(app, text="Start counting", command=start)
stop = Button(app, text="Stop counting", command=stop)
start.grid()
stop.grid()
root.after(1000, scanning) # After 1 second, call scanning
root.mainloop()
Hope this will help.
So I'm trying to make it cycle through each letter of the alphabet when the button is clicked.
I have tried the method i am showing now.
I have also tried many others and i couldn't get anything to work.
If you do have a solution please try keep it simple i am kinda new too this.
from tkinter import *
win = Tk()
win.title('ab')
a = 0
def changetext():
a = a+1
if a == 1:
lbl.config(text='b')
def changetext():
if a == 2:
lbl.config(text='c')
lbl = Label(win,text='a')
lbl.grid(row=1,column=1)
btn = Button(win,text='u', command =changetext)
btn.grid(row=2,column=1)
win.mainloop()```
In python, variables inside functions are local, which means that if you define a variable a = 0 outside the function, then do a = 1 in the function, the a equals 1 inside the function but it still equals 0 outside. If you want to change the value of a outside the function from inside the function, you need to declare a as a global variable (see code below).
import tkinter as tk # avoid import * to because it leads to naming conflicts
win = tk.Tk()
win.title('ab')
i = 0
letters = "abcdefghijklmnopqrstuvwxyz"
def changetext():
global i # change value of i outside function as well
i += 1
i %= 26 # cycle through the alphabet
lbl.configure(text=letters[i])
lbl = tk.Label(win, text='a')
lbl.grid(row=1, column=1)
btn = tk.Button(win,text='u', command=changetext)
btn.grid(row=2, column=1)
win.mainloop()
You can use itertools.cycle to create a cycle list and then use next() function to get the next item in the cycle list:
import tkinter as tk
from itertools import cycle
words = cycle(['hello', 'world', 'python', 'is', 'awesome'])
root = tk.Tk()
lbl = tk.Label(root, text=next(words), width=20)
lbl.pack()
tk.Button(root, text='Next', command=lambda: lbl.config(text=next(words))).pack()
root.mainloop()
I actually used the first method and adapted it by making the variable global because then it will update it for all the functions making my first method work
from tkinter import *
win = Tk()
win.title('ab')
i = 0
def changetext():
global i
i = i + 1
if i == 1:
lbl.config(text='word 2')
if i == 2:
lbl.config(text='word 1 ')
lbl = Label(win,text='a')
lbl.grid(row=1,column=1)
btn = Button(win,text='u', command =changetext)
btn.grid(row=2,column=1)
win.mainloop()
I'm trying to start a counter that displays the value in a label on a separate display window.
The main window has a START, STOP and DISPLAY button.
The START must start the counter, STOP must stop it and the display window must open only when I click on DISPLAY button.
Here's what I have so far. The buttons seem to be unresponsive and the display window pops up without user intervention. How can I fix this?
import tkinter as tk
import time
import threading
import queue
def Run_Device(disp_q,flaq_q):
temp_q = queue.Queue()
temp_q.put(0)
while(flaq_q.empty()):
#time.sleep(0.2)
count = temp_q.get()
count += 1
temp_q.put(count)
disp_q.put(count)
else:
flaq_q.queue.clear()
def P_Window(disp_q):
pw = tk.Tk()
value_label = tk.Label(pw, text=disp_q.get(), relief='sunken', bg='lemon chiffon', font='Helvetica 16 bold')
value_label.pack()
def update_values():
value_label.config(text=disp_q.get())
value_label.after(1000,update_values)
update_values()
pw.mainloop()
def Stop_Dev(flaq_q):
flaq_q.put("Stop")
if __name__ == "__main__":
disp_q = queue.Queue()
flaq_q = queue.Queue()
t_device = threading.Thread(target=Run_Device, args=(disp_q, flaq_q), name="Device 1")
t_disp = threading.Thread(target=P_Window, args=(disp_q, ), name="Display 1")
window = tk.Tk()
start_button = tk.Button(window, text='Start', command=t_device.start(), bg='spring green', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
start_button.pack()
stop_button = tk.Button(window, text='Stop', command=lambda: Stop_Dev(flaq_q), bg='OrangeRed2', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
stop_button.pack()
disp_param_button = tk.Button(window, text='Display', command=t_disp.start(), bg='sky blue', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
disp_param_button.pack()
window.mainloop()
I'm trying to learn how to use multithreading in tkinter so any feedback would be appreciated
There are two issues I see with your code. The first is just simple bugs, e.g.:
start_button = tk.Button(window, text='Start', command=t_device.start(), ...
here it should be command=t_device.start otherwise the command is the function returned by t_device.start()
The second is you've not dealt with various "What if ...?" scenarios, e.g. What if the user pushes 'Start' or 'Display' multiple times?
I've tried to address the above in my rework below:
import tkinter as tk
from time import sleep
from queue import Queue, Empty
from threading import Thread
FONT = 'Helvetica 16 bold'
def run_device():
count = 0
while flaq_q.empty():
count += 1
disp_q.put(count)
sleep(0.5)
while not flaq_q.empty(): # flaq_q.queue.clear() not documented
flaq_q.get(False)
def p_window():
global pw
if pw is None:
pw = tk.Toplevel()
value_label = tk.Label(pw, text=disp_q.get(), width=10, font=FONT)
value_label.pack()
def update_values():
if not disp_q.empty():
try:
value_label.config(text=disp_q.get(False))
except Empty:
pass
pw.after(250, update_values)
update_values()
elif pw.state() == 'normal':
pw.withdraw()
else:
pw.deiconify()
def stop_device():
if flaq_q.empty():
flaq_q.put("Stop")
def start_device():
global device
if device and device.is_alive():
return
while not disp_q.empty():
disp_q.get(False)
disp_q.put(0)
device = Thread(target=run_device)
device.start()
if __name__ == "__main__":
disp_q = Queue()
flaq_q = Queue()
root = tk.Tk()
pw = None
device = None
tk.Button(root, text='Start', command=start_device, width=20, font=FONT).pack()
tk.Button(root, text='Stop', command=stop_device, width=20, font=FONT).pack()
tk.Button(root, text='Display', command=p_window, width=20, font=FONT).pack()
root.mainloop()
I've left out some details to simplify the example. Even with its additional checks, it's still nowhere near perfect. (E.g. it hangs if you don't 'Stop' before you close the window, etc.)
You might be able to adapt something like the following:
import tkinter as tk
import threading
import queue
import time
def run_device():
for i in range(4):
print(i)
pass
def process_queue(MyQueue):
"""Check if we got a complete message from our thread.
Start the next operation if we are ready"""
try:
# when our thread completes its target function (task),
# the supplied message is added to the queue
msg = MyQueue.get(0)
# check message
print(msg)
except queue.Empty:
# .get failed, come back in 100 and check again
print('no message')
threading.Timer(0.001, lambda q=MyQueue: process_queue(q)).start()
class ThreadedTask(threading.Thread):
"""threaded task handler"""
def __init__(self, queue, target, msg):
threading.Thread.__init__(self)
# message to add to queue when the task (target function) completes
self.msg = msg
# function to run
self._target = target
# queue to store completion message
self.queue = queue
def run(self):
"""called when object is instantiated"""
# start users task
try:
self._target()
except Exception as e:
self.queue.put('Thread Fail')
return
# we are done, pass the completion message
self.queue.put(self.msg)
if __name__ == '__main__':
MyQueue = queue.Queue()
MyThread = ThreadedTask(MyQueue, run_device, 'Thread Task: Run')
MyThread.start()
process_queue(MyQueue)
So far, the bat runs but the progress bar doesn't. How do I connect the two with each other? Here is the image of the output.
http://imgur.com/lKbHepS
from tkinter import *
from tkinter import ttk
from subprocess import call
def runBat():
call("mp3.bat")
root = Tk()
photobutton3 = PhotoImage(file="smile.png")
button3 = Button(root, image=photobutton3, command=runBat)
button3.grid()
pbar = ttk.Progressbar(root, orient=HORIZONTAL, length=200, mode='determinate')
pbar.grid()
root.mainloop()
This answer ended up not working. The question is still open.
Try this:
import subprocess
import threading
import ctypes
import re
from tkinter import *
from tkinter import ttk
class RunnerThread(threading.Thread):
def __init__(self, command):
super(RunnerThread, self).__init__()
self.command = command
self.percentage = 0
self.process = None
self.isRunning = False
def run(self):
self.isRunning = True
self.process = process = subprocess.Popen(self.command, stdout = subprocess.PIPE, shell = True)
while True:
#Get one line at a time
#When read() returns nothing, the process is dead
line = b""
while True:
c = process.stdout.read(1)
line += c
if c == b"" or c == b"\r": #Either the process is dead or we're at the end of the line, quit the loop
break
if line == b"": #Process dead
break
#Find a number
match = re.search(r"Frame\=\s(\d+\.?(\d+)?)", line.decode("utf-8").strip())
if match is not None:
self.percentage = float(match.group(1))
self.isRunning = False
def kill(self): #Something I left in case you want to add a "Stop" button or something like that
self.process.kill()
def updateProgress():
progressVar.set(rt.percentage) #Update the progress bar
if rt.isRunning: #Only run again if the process is still running.
root.after(10, updateProgress)
def runBat():
global rt
rt = RunnerThread("mp3.bat")
rt.start()
updateProgress()
root = Tk()
photobutton3 = PhotoImage(file="smile.png")
button3 = Button(root, image=photobutton3, command=runBat)
button3.grid()
progressVar = DoubleVar()
pbar = ttk.Progressbar(root, orient=HORIZONTAL, length=200, mode='determinate', variable = progressVar)
pbar.grid()
root.mainloop()
Basically, there's a thread that reads the data from the process and makes it available to a function that updates the progress bar every so often. You didn't mention the output's format, so I wrote it to use a regular expression to search for the first number and convert it.