tkinter change stringvar in loop but no effect on window - python

I want to change label every sec when i press the button in tk:
# --coding:utf-8 -----
from Tkinter import *
import time
import random
def test(a):
begin =time.time()
end =time.time()
while True:
ran = random.random()
after = time.time()
if(after-begin >1):
a.set(str(ran))
print a.get()
begin =after
if(after-end>10):
a.set('over')
break
t = Tk()
a = StringVar()
a.set('0')
b = Label(t,textvariable = a)
b.pack()
Button(t,text ='test',command = lambda x=a:test(a)).pack()
t.mainloop()
My console output is right,but it doesnot effect on windows.WHY?

You can start the test() function in a separate thread, like this:
# --coding:utf-8 -----
from Tkinter import *
import time
import random
import threading
def startThread(a):
threading.Thread(target=test, args=(a,)).start()
def test(a):
begin =time.time()
end =time.time()
while True:
ran = random.random()
after = time.time()
if(after-begin >1):
a.set(str(ran))
print a.get()
begin =after
if(after-end>10):
a.set('over')
break
t = Tk()
a = StringVar()
a.set('0')
b = Label(t,textvariable = a)
b.pack()
Button(t,text ='test',command = lambda x=a:startThread(a)).pack()
t.mainloop()
However, the thread won't end until the end of the 10 seconds, even if you close the window. You'll need to find some code for the while loop to check if the application is still running.

Creating a behavior that must execute for a given time period is a frequent problem; you can tackle it with a dedicated Waiter class; this avoids the use of threads that are poorly supported by tkinter.
The following example opens a window with a label and a button. When the button is pressed, the label will update every seconds for 10 seconds, then stop.
Pressing the button again within 10 seconds has no effect; after that, it restarts the process for 10 more seconds.
import Tkinter as tk # tkinter if python >= 3
import time
import random
class Waiter(object):
def __init__(self, waiting_time):
"""
:param waiting_time: int, in seconds
"""
self.waiting_time = waiting_time
self.expiring_time = time.time() + self.waiting_time
self.waiting = True
# print('waiter started')
def stop(self):
# print('waiter stopping')
self.expiring_time = None
self.waiting = False
def is_waiting(self):
"""returns True while waiting, false otherwise"""
if time.time() > self.expiring_time:
self.stop()
return self.waiting
def atest():
global waiter
if waiter is None:
waiter = Waiter(10)
_atest()
def _atest():
""" equivalent to:
while the waiter is waiting,
change the label every second),
then destroy the waiter
"""
global waiter
if waiter.is_waiting():
a.set(random.random())
# print(time.time())
t.after(1000, _atest)
else:
waiter = None
if __name__ == '__main__':
waiter = None
t = tk.Tk()
a = tk.StringVar()
a.set('0')
tk.Label(t, textvariable=a).pack()
tk.Button(t, text='test', command=atest).pack()
t.mainloop()
Note:
You could make _atest an inner function of atest, but maybe it is easier to understand as it is?
using import Tkinter as tk instead of from Tkinter import * prevents cluttering the namespace, and arguably makes the code clearer.
You should probably consider using python 3.

Related

How to stop playing a sound from playsound module [duplicate]

This question already has answers here:
How to stop audio with playsound module?
(7 answers)
Closed 12 months ago.
I'm making a timer program and I would like it to play a song at the end of the timer but I would also like to stop the sound when I press a button.
I've seen other posts that use the multiprocessing module (How to stop audio with playsound module?) to stop the audio but I am already using the threading module, so I was wondering if its possible to use that to stop the audio instead.
Edit: Matiis gave a solution different to what i was looking for but it still works perfectly. glory9211 also gave the solution i was looking for later on
from tkinter import *
from time import sleep
from threading import Thread
from threading import Event
import playsound
class App(Tk):
def __init__(self):
super().__init__()
self.title("Clock")
self.t_evt = Event()
self.frame2 = Frame(self)
self.timerStart = Button(self.frame2, text="Start", command=self.tt_Start)
self.timerStart.config(height=2, width=5)
self.timerStart.grid(row=1)
self.timerEnd = Button(self.frame2, text="End", command=self.timer_End)
self.timerEnd.config(height=2, width=5)
self.timerEnd.grid(row=2)
def tt_Start(self):
t = Thread(target=self.timer_Start)
t.setDaemon(True)
t.start()
def timer_Start(self):
self.t_evt.clear()
timer_seconds = int(self.timerS_Entry.get())
timer_minutes = int(self.timerM_Entry.get())
if timer_seconds > 59:
timer_seconds -= 60
timer_minutes += 1
while not self.t_evt.is_set():
print(f"Minutes: {timer_minutes}, Seconds: {timer_seconds}")
self.timerSeconds.config(text=timer_seconds)
self.timerMinutes.config(text=timer_minutes)
self.update()
time = (timer_minutes * 60) + timer_seconds
timer_seconds -= 1
sleep(1)
if time == 0:
break
if timer_seconds < 0:
timer_seconds = 59
timer_minutes -= 1
playsound.playsound('C:\\Users\\nonon\\mp3\\song.wav')
print("TIMER UP")
return
def timer_End(self):
self.t_evt.set()
here is some code for you to work off of, let me know if you need more.
Again, I would like to be able to stop playsound.playsound('C:\\Users\\nonon\\mp3\\song.wav') when I press the end button
Short Answer
You can use threading Events instead of threads. Or switch to multiprocessing and use p.terminate()
Long Answer
You cannot stop a python threading.Thread using any provided function. People achieving this using flags. good reads: Is there any way to kill a Thread?
i.e.
def thread_func():
while flag:
print("I'm running")
def run_button():
flag = True
t = threading.Thread(target=thread_func)
t.start()
def stop_button():
flag = False
# This will make the function exit
But in your case the playsound function isn't a looping function where you can sneak a flag to stop the function. You can imagine it to be indefinite sleep function i.e.
def play_sound():
time.sleep(1000000) # Replacing with playsound.playsound
So using the threading.Events() we can achieve this with this sample code
import random
import signal
import threading
import time
exit_event = threading.Event()
def bg_thread():
for i in range(1, 30):
print(f'{i} of 30 iterations...')
if exit_event.wait(timeout=random.random()):
break
print(f'{i} iterations completed before exiting.')
def signal_handler(signum, frame):
exit_event.set() # same as setting the flag False
signal.signal(signal.SIGINT, signal_handler)
th = threading.Thread(target=bg_thread)
th.start()
th.join()
This solution effectively gives you an "interruptible" sleep, because if the event is set while the thread is stuck in the middle of the call to wait() then the wait will return immediately.
Read the detailed example here: https://blog.miguelgrinberg.com/post/how-to-kill-a-python-thread

Python multiprocesing pool opens new window

Good evening all,
I have created a CPU heavy function which I need to iterate 50 times with input from a list and then I am using the multiprocessing pool to synchronously compute the 50 functions. It works but creates a new window for each new process and only after I close these windows I do get the result. Is there a way to not open the window each time? I have tried multiple multiprocessing methods, but all do the same.
import sqlite3
import random
import time
import concurrent.futures
from multiprocessing import Pool
from tkinter import *
import time
window = Tk()
window.title("APP")
def bigF(list):
n=list[0] # Extract multiple var from list
n2=list[1]
new = f(n) # The CPU heavy computation.
if n2 == new:
return True
else:
return False
def Screen1():
window.geometry("350x250")
# Lables and entry windows.
btn = Button(window, text='S2', command=Screen2)
btn.pack(pady=10)
def Screen2():
window.geometry("350x220")
# Lables and entry windows.
def getP():
user = user.get()
cursor.execute(database)
try:
return cursor.fetchall()[0][0]
except IndexError:
raise ValueError('')
def getS():
user = user.get()
cursor.execute(database)
return cursor.fetchall()[0][0]
def getS2():
user = user.get()
cursor.execute(database)
return cursor.fetchall()[0][0]
def function():
try:
var = getP()
except ValueError:
return False
s = getS()
s2 = getS2()
t = t.get()
if __name__ == '__main__':
list1=[]
listt=[]
list3=[]
list1[:0]=somestring
random.shuffle(list1)
for i in range(len(list1)):
listt.append(list1[i])
listt.append(var)
listt.append(s)
listt.append(s2)
listt.append(t)
list3.append(listt)
listt=[]
with Pool(50) as p:
values = p.map(bigF, list3)
if True in values:
someF()
else:
# Lables
btn = Button(window, text='function', command=function)
btn.pack(pady=10)
sgn = Button(window, text='S1', command=Screen1)
sgn.pack(padx=10, side=RIGHT)
def someF():
window.geometry("350x350")
# Lables and entry windows.
Screen1()
window.mainloop()
I don't know if the problem is in bigF(list) or the multiprocessing way. I aim to shorten the processing time to less than 2 seconds where 1 bigF(list) takes around 0.5 seconds.
Thank you for any suggestions and comments.
You need to read the cautions in the multiprocessing doc. When your secondary processes start, it starts a new copy of the interpreter and re-executes your main code. That includes starting a new main window. Anything that should be run in the main process only needs to be inside if __name__=='__main__':. So you need:
if __name__ == '__main__':
windows = Tk()
window.title("APP")
Screen1()
window.mainloop()

How to pause a thread (python)

The context:
I'm building a Graphical Interface with Qt creator and the "behaviour" file in python. A test version of my GUI is:
The expected behaviour:
I am running 2 different threads which are referred to the same function with different input arguments. With the SELECTOR button I can assign the value of 1 or 2 to a variable (and display it)
The button Start thread enables the correct thread to start (the first time).
The loop should be turned off by the stop button by modifying the global running variable.
This is my code
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui, uic
import sys
import threading
import time
import Queue
running = False
first_thread = None
second_thread = None
form_class = uic.loadUiType("simple2.ui")[0]
q = Queue.Queue()
select = 0
def action(string, queue): #function called by threads
global running
while(running):
phrase = string
if queue.qsize() < 10:
queue.put(phrase)
#else:
# print queue.qsize()
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
#buttons
self.startButton.clicked.connect(self.start_clicked)
self.stopButton.clicked.connect(self.stop_clicked)
self.selector.clicked.connect(self.sel_click)
#variables
self.first = False
self.second = False
#queue
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_phrase)
self.timer.start(1)
def start_clicked(self): #start button callback
global select
if select > 0:
global running
running = True
print "started"
if (not self.first) & (select == 1):
first_thread.start()
self.first = True
if (not self.second) & (select == 2):
second_thread.start()
self.second = True
self.startButton.setEnabled(False)
self.startButton.setText('Starting...')
def stop_clicked(self): #stop button callback
global running
running = False
print "stopped"
self.startButton.setEnabled(True)
self.startButton.setText('Start Thread')
def sel_click(self): #selector button callback
global select
if select < 2:
select = select + 1
else:
select = 1
self.thread_counter.setText(str(select))
def update_phrase(self): #looping function
global running
if (not q.empty()) & running:
self.startButton.setText('Thread on')
abc = q.get()
print abc
def closeEvent(self, event):
global running
running = False
if __name__ == "__main__":
first_thread = threading.Thread(target=action, args = ("first", q))
second_thread = threading.Thread(target=action, args = ("second", q))
app = QtGui.QApplication(sys.argv)
w = MyWindowClass(None)
w.setWindowTitle('Multiple threads test in python')
w.show()
app.exec_()
For now, each thread should simple print on terminal their arguments ("First" or "Second").
If threads are started for the first time, my code works. But I would like to switch between threads infinite times.
Since threads cannot be stopped, is there a way to "pause" them?
I cannot find a solution, I hope someone will help me also with a piece of code. Thank you in advance
You can use Lock class to do that, a simple example would be:
import threading
lock = threading.Lock()
//here it will be lock
lock.acquire() # will block if lock is already held
...
then in other side do
//this will wake up
lock.release()
you can read more here http://effbot.org/zone/thread-synchronization.htm

Python - breaking while loop with function (Raspberry Pi)

I have a project that involves a raspberry pi, the dothat 16x2 lcd display and some python code. I am essentially trying to make a dynamic while loop that displays information on the lcd. I am also trying to add a function that cancels the while loop by pressing one of the touch buttons (reference: https://github.com/pimoroni/dot3k/blob/master/python/REFERENCE.md)
This is what I got so far:
import dothat.lcd as l
import dothat.backlight as b
import dothat.touch as t
from time import sleep
import signal
import os
def main():
i=0
k=0
while True:
l.clear() # Clear LCD screen
b.hue(1.5) # Set background color
l.set_cursor_position(0, 1) # Set cursor position on LCD
l.write("%s" % k) # Write variable "k" to LCD
#t.on(t.CANCEL) # When CANCEL button is pressed then go to function
def cancel(ch, evt):
i=1 # Set variable "i" as 1
return
if i == 1:
break
k=k+1
sleep(1)
l.clear() # Clear LCD screen
b.off() # Turn the LCD Backlight off
cmd='pkill python' #
os(cmd) # Kill all python processes
signal.pause()
main()
The while loop is running but it won't break when the button is pressed. Ideas?
I fixed it, though I'm getting errors about the 'module' object not being callable regarding os(cmd).
The code:
def main():
global i
i=0
k=0
while True:
l.clear()
b.hue(1.5)
l.set_cursor_position(0, 1)
l.write("%s" % k)
#t.on(t.CANCEL)
def cancel(ch, evt):
global i
i=1
return
if i == 1:
break
k=k+1
sleep(1)
l.clear()
b.off()
cmd='pkill python'
os(cmd)
signal.pause()
main()
I don't have a dothat LCD display, so I can't test your code. But I think #Pigface333 was right, the i inside cancel is a local variable, thus the i in your if statement is not set to 1 after pressing Cancel. The following code demonstrates that:
from time import sleep
def main():
i = 0
k = 0
while True:
def cancel():
print "inside cancel"
i = 1
return
cancel()
if i == 1:
break
k = k+1
sleep(1)
exit(0)
main()
This will print inside cancel every 1 second, but won't exit, showing that the i inside cancel is a local variable. To fix it, you can create a class that stores the cancellation status:
from time import sleep
class Cancel(object):
def __init__(self):
self.is_cancelled = False
def cancel(self):
self.is_cancelled = True
def main():
canceller = Cancel()
while True:
canceller.cancel()
if canceller.is_cancelled:
break
sleep(1)
exit(0)
main()
The same method can be applied to your code:
import dothat.lcd as l
import dothat.touch as t
import dothat.backlight as b
from time import sleep
import signal
class Cancel(object):
def __init__(self,):
self.is_cancelled = False
#t.on(t.CANCEL)
def cancel(self, ch, evt):
self.is_cancelled = True
return
def main():
k = 0
cancel = Cancel()
while True:
l.clear() # Clear LCD screen
b.hue(1.5) # Set background color
l.set_cursor_position(0, 1) # Set cursor position on LCD
l.write("%s" % k) # Write variable "k" to LCD
if cancel.is_cancelled:
break
k = k+1
sleep(1)
l.clear() # Clear LCD screen
b.off() # Turn the LCD Backlight off
signal.pause()
exit(0)
main()
To help understanding why the original code didn't work and why using a class is a good idea, I suggest reading about Python's variable's scope and Object-Oriented Prograaming in Python.

call a function after program has been idle for a given time

My Tkinter-based program needs to periodically perform some "heavy" maintenance functions.
Since it is a program that is running continuously, I was thinking of launching those functions only after a given amount of idle time.
How do you do it in Tkinter? I found about about after_idle in http://etutorials.org/Programming/Python+tutorial/Part+III+Python+Library+and+Extension+Modules/Chapter+16.+Tkinter+GUIs/16.9+Tkinter+Events/, but that gets called just when the event loop is idle. I need it to run my functions, say, after 10 minutes of idle time instead.
~~~
Mr.Steak gave the answer I needed - I just modified it slightly as follows to be able to perform different tasks at different intervals, using the idletime variable :
import time
from Tkinter import *
root = Tk()
def resetidle(*ignore):
global idletime
for k in idletime: k['tlast']=None
def tick(*ignore):
global idletime
t=time.time() # the time in seconds since the epoch as a floating point number
for k in idletime:
if not k['tlast']:
k['tlast'] = t
else:
if t-k['tlast']>k['tmax']:
k['proc']()
k['tlast'] = None
root.after(5000, tick) # reset the checks every 5''
idletime=[{'tlast':None,'tmax':60,'proc':test1}, # every 1'
{'tlast':None,'tmax':3600,'proc':test2}] # every 1h
root.after(5000, tick)
root.bind('<Key>', reset)
root.bind('<Button-1>', reset)
root.mainloop()
In the following example, the tick function is called every second. After 5 seconds, a message is printed, unless a key or mouse button 1 were pressed.
import time
from Tkinter import *
root = Tk()
running = None
def reset(*ignore):
global running
running = None
def tick(*ignore):
global running
if not running:
running = time.time()
elif time.time() - running > 5:
print 'I waited 5 seconds...'
running = None
root.after(1000, tick)
root.after(1000, tick)
root.bind('<Key>', reset)
root.bind('<Button-1>', reset)
root.mainloop()

Categories

Resources