Python kivy and multiprocessing bug - python-3.x

I am stuck on writing a very simple kivy gui with 2 buttons.
button_1 launches the countdown in multiprocessing. It works.
button_2 is supposed to end the countdown in multiprocessing. That does not work...
Can anyone please point out what I am doing wrong and why?
Thank you kindly in advance.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import multiprocessing
import time
class MyApp(App):
list_of_objects_in_class = []
def build(self):
layout = BoxLayout()
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
#staticmethod
def actual_countdown_function(*args):
print('I am launching the countdown!')
for count in range(5, 0, -1):
print(count)
time.sleep(1)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
MyApp.list_of_objects_in_class.append(multiprocessing.Process(target=MyApp.actual_countdown_function, args=()))
MyApp.list_of_objects_in_class[0].start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
try:
MyApp.list_of_objects_in_class[0].terminate()
MyApp.list_of_objects_in_class.clear()
except:
pass
if __name__ == "__main__":
MyApp().run()
I tested to see if .terminate() works on my system, it works. The script below terminates successfully but the script above does NOT...
import multiprocessing
import time
def print_one():
time.sleep(3)
print('This does NOT get printed because proc.terminate() works on my Linux Mint OS')
def print_two():
print('This gets printed')
process_to_terminate = multiprocessing.Process(target=print_one, args=())
process_to_terminate.start()
process_to_keep_and_NOT_terminate = multiprocessing.Process(target=print_two, args=())
process_to_keep_and_NOT_terminate.start()
process_to_terminate.terminate() # Works and kills print_one function

It's better to use threads indeed to run such a small tasks.
Anyway, I think, the original problem could be solved by setting SIGTERM handler in a child process, because it seems to be ignoring SIGTERM by default:
def actual_countdown_function(self, *args):
+ signal.signal(signal.SIGTERM, lambda *_, **__: sys.exit(0))
print('I am launching the countdown!')
But, TBH, if you will really need a way to manage child process, I strongly recommend to use Locks/Conditions/Queues instead. They have almost the same API like all the threading primitives, but built to interact over pipes/shared-memory/sockets/etc.

after many different attempts, I was able to solve my own problem by using threading instead of multiprocessing.
Here is the code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import threading
import time
class MyApp(App):
my_var = True
list_of_objects_in_class = []
def build(self):
layout = BoxLayout(size =(300, 300))
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown, height = "100", width = 140)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
def actual_countdown_function(self, *args):
print('I am launching the countdown!')
x = 5
while x != 0:
new_var = self.my_var
if new_var == False:
break
time.sleep(1)
print(x, new_var)
x -=1
del(new_var)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
t1 = threading.Thread(target=self.actual_countdown_function)
t1.start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
self.my_var = not self.my_var
print(self.my_var)
self.list_of_objects_in_class.clear()
if __name__ == "__main__":
MyApp().run()

Related

tkinter state of button for multi-clicks

I have written a simple code. The button is disabled when process is running and it is enabled after the process finishes. It works as expected for the first time, but when I re-click the button (do not close the window), the button could not be disabled while the process is ok. Can anyone gives some hints.
import time
import tkinter as tk
import threading
win = tk.Tk()
class test(threading.Thread):
def __init__(self):
super(test, self).__init__()
def run(self):
print("start !!!")
time.sleep(3)
print("end !!!")
def monitor_state(thread):
if not thread.is_alive():
btn.configure(state=tk.NORMAL)
win.after(50, monitor_state, thread)
def py1_go():
btn.configure(state=tk.DISABLED)
test_thread = test()
test_thread.setDaemon(True)
test_thread.start()
monitor_state(test_thread)
btn = tk.Button(text='Display Results', command = py1_go)
btn.pack()
win.mainloop()
A Tkinter Button has three states : active, normal, disabled.
Snippet:
def run(self):
print("start !!!")
btn["state"] = "disabled"
time.sleep(3)
print("end !!!")
btn["state"] = "normal"

How to implement a "processing request"-message in TkInter

I'm trying to implement a popup-window, which should display over the main window, while an operation is taking place in the background. Pressing the button from the code below results in freezing the GUI for 10s without displaying any message and eventually making the button green. The freezing is normal, but I would like to have the popup displayed during the 10 seconds.
Any help would be appreciated! Thanks in advance!
import tkinter as tk
import time
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.button1 = tk.Button(text="Start", command=self.make_green)
self.button1.pack()
def popup(self):
tl = tk.Toplevel(self)
tl.transient()
tk.Label(tl, text="Painting green").pack()
tl.grab_set()
return tl
def make_green(self):
wait_popup = self.popup()
time.sleep(10)
self.button1.config(bg="green")
wait_popup.destroy()
a = GUI()
a.mainloop()
You can use self.update() in your code to make the popup window appear.
import tkinter as tk
import time
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.button1 = tk.Button(text="Start", command=self.make_green)
self.button1.pack()
def popup(self):
tl = tk.Toplevel(self)
tl.transient()
tk.Label(tl, text="Painting green").pack()
self.update()
tl.grab_set()
return tl
def make_green(self):
wait_popup = self.popup()
time.sleep(10)
self.button1.config(bg="green")
wait_popup.destroy()
a = GUI()
a.mainloop()
Or you can use threading.
Start by importing Thread from threading.
from threading import Thread
Then make a new method
def thread_it(self):
return Thread(target=self.make_green, daemon=True).start()
and update your button's command
self.button1 = tk.Button(text="Start", command=self.thread_it)

How to enable threading with tkinter?

When I want to build a program like a clock on Python3,there is a problem about threading & tkinter.
my_code :
#!/usr/bin/python3
#-*-coding:utf-8-*-
import tkinter as tk,time,threading,queue
def update_time(in_q):
while True:
in_q.put(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
class App_gui:
def __init__(self,parent):
self.top_frame = tk.Frame(parent)
self.clock = tk.Label(self.top_frame)
self.clock.pack()
self.top_frame.pack()
self.begin_thread()
def begin_thread(self):
self.clock_q = queue.Queue()
self.clock_thread = threading.Thread(target=update_time(self.clock_q))
self.clock_thread.start()
self.listen()
def listen(self):
gate_time = self.clock_q.get()
self.clock.config(text=gate_time)
self.clock.after(200,self.listen)
if __name__ == '__main__':
root = tk.Tk()
my_app = App_gui(root)
root.mainloop()
when I run this code,there's nothing happen.
Well threading is not all that difficult however in this case threading is overkill for a simple time loop.
We can use after() to manage a label for time without having to use threading.
import tkinter as tk
from time import strftime
class AppGUI(tk.Tk):
def __init__(self):
super().__init__()
self.time_label = tk.Label(self)
self.time_label.pack()
self.track_time()
def track_time(self):
self.time_label.config(text="{}".format(strftime('%Y-%m-%d %H:%M:%S')))
self.time_label.after(1000, self.track_time)
if __name__ == '__main__':
AppGUI().mainloop()
solved
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import threading,time,tkinter as tk
def clock_task():
global clock_time
while True:
clock_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
time_label.config(text=clock_time)
time.sleep(1)
clock_t = threading.Thread(target=clock_task)
clock_t.setDaemon(True)
root = tk.Tk()
time_label = tk.Label(root)
time_label.pack()
# start thread before mainloop
clock_t.start()
root.mainloop()

Kivy countdown Label

I'm trying to perform a countdown with the Label.
By updating the label's text I believe it is possible.
My intention is to start the countdown after the user presses the start button
But it's not working!!!
Here is my code
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
import time
class WelcomeScreen(Widget):
def __init__(self):
super().__init__()
self.start = Button(text="Start Game", pos=(350, 250))
self.add_widget(self.start)
self.count = Label(text="", pos=(350, 250),font_size=90)
self.start.bind(on_press=self.start_game)
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
for i in range(1, 4):
num += 1
self.count.text = str(num)
time.sleep(1)
class TestApp(App):
def build(self):
return WelcomeScreen()
TestApp().run()
I know this is a stupid problem but I cant't really figure it out how to solve it.
Please Help
Short anwser: Use the Clock
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
def count_it(num):
if num == 4:
return
num += 1
self.count.text = str(num)
Clock.schedule_once(lambda dt: count_it(num), 1)
Clock.schedule_once(lambda dt: count_it(0), 0)
Longer:
When you are running python code the kivy event loop is waiting so nothing gets update in the screen, when you do time.sleep you stall the process...
The Clock schedule_* methods allow you to set callbacks while letting the event loop run when you are idle
You can also use a thread and call Clock.schedule_once just for the updates but for your use case the code above is simpler

Pause execution until button press

I have a QStackedWidget. In the logic (not the UI) I am trying to change pages and wait there until a button on that page is pressed (basically an OK/Cancel). I pass the UI to the function in the class.
Something like this:
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
while True:
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
do_something
window.stackedWidget.setCurrentIndex(3)
break
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)
break
for i in range(5):
#stuff
func1(window) #this is where I want to pause
#other stuff
Now I know that I can't break with the function like that or pass the window variable through connect, but I hope that makes my point clearly enough.
A simple way to do this is to process pending events inside the loop (so the UI remains responsive), and set/unset an internal flag to control starting and stopping of the loop.
The following demo script shows a basic implementation of this idea:
import time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
layout.addWidget(self.label)
self.buttonStart = QtGui.QPushButton('Start', self)
self.buttonStart.clicked.connect(self.handleStart)
layout.addWidget(self.buttonStart)
self.buttonStop = QtGui.QPushButton('Stop', self)
self.buttonStop.clicked.connect(self.handleStop)
layout.addWidget(self.buttonStop)
self._running = False
def handleStart(self):
self.buttonStart.setDisabled(True)
self._running = True
while self._running:
self.label.setText(str(time.clock()))
QtGui.qApp.processEvents()
time.sleep(0.05)
self.buttonStart.setDisabled(False)
def handleStop(self):
self._running = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 100)
window.show()
sys.exit(app.exec_())
Just remove while and break.
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
# do_something
window.stackedWidget.setCurrentIndex(3)
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)

Resources