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)
Related
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()
when I left-click my icon in the system tray it does not exit the application - I can see it does go through the function though when I set a breakpoint, if however I click Exit from the popup menu then the program exits as expected (runs the same function), why is this please?
It is the on-exit function that should close the application:
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_exit)
Thanks very much for any help you can provide.
import wx
import wx.adv
TRAY_TOOLTIP = 'ShutMeDown'
TRAY_ICON = icon.png
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(TaskBarIcon, self).__init__()
self.SetIcon(wx.IconFromBitmap(wx.Bitmap(TRAY_ICON)), TRAY_TOOLTIP)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_exit)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def on_exit(self, event):
wx.CallAfter(self.Destroy)
self.frame.Close()
class App(wx.App):
def OnInit(self):
frame = wx.Frame(None)
self.SetTopWindow(frame)
TaskBarIcon(frame)
return True
def main():
app = App(False)
app.MainLoop()
if __name__ == '__main__':
main()
What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()
Basically, when the Switch Button is pressed, which is connected to the reconfigure method, I want everything in the reconfigure method to run as a separate thread/process so the main GUI is still accessible and not being blocked. Below is a watered down version of my code.
import sys, time
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import *
#//Popup Class - Will appear when the Switch Button is pressed
class Popup(QWidget):
def __init__(self):
QWidget.__init__(self)
#//nothing here now, it will have a message telling user to wait while program is run
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
grid = QtGui.QGridLayout()
label_header = QtGui.QLabel("TEST RECONFIGURE")
font = label_header.font()
font.setPointSize(24)
label_header.setFont(font)
#//Creating Static Labels that will be placed in the GUI
label_1 = QtGui.QLabel("Menu 1:")
label_2 = QtGui.QLabel("Menu 2:")
label_spacer = QtGui.QLabel("")
label_cfg = QtGui.QLabel("Current Configuration: '/tmp/directory_here' ")
global comboBox1
comboBox1 = QtGui.QComboBox()
comboBox1.addItem("1")
comboBox1.addItem("2")
global comboBox2
comboBox2 = QtGui.QComboBox()
comboBox2.addItem("3")
comboBox2.addItem("4")
#//Switch Button!!!
global switchButton
switchButton = QPushButton("Switch")
switchButton.clicked.connect(self.reconfigure)
quitButton = QtGui.QPushButton('Quit')
quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
#//Configure the grid layout
grid.addWidget(label_spacer, 0,0,9,9)
grid.addWidget(label_header, 0,0,1,6)
grid.addWidget(label_1, 1,0,1,1)
grid.addWidget(comboBox1, 2,0,1,1)
grid.addWidget(label_2, 3,0,1,1)
grid.addWidget(comboBox2, 4,0,1,1)
grid.addWidget(switchButton, 5,0,1,2)
grid.addWidget(label_cfg, 6,0,1,9)
grid.addWidget(quitButton, 9,9,1,1)
self.setLayout(grid)
self.setGeometry(640,300,400,600)
self.show()
#//open up the popup window for switch button, and reconfigure
def reconfigure(self):
print "Opening New Window"
self.w = Popup()
self.w.setGeometry(QRect(self.x()+100,self.y()+100,400,200))
self.w.show()
txt1 = str(comboBox1.currentText())
txt2 = str(comboBox2.currentText())
print " reconfiguring to option %s and option %s" %(txt1, txt2)
#
# This is where most of the work is done, and takes about 1/2 an hour for everything to run
# Want to make this method a separate thread/process so the rest of the main GUI is still accessible
# while the program is running as the whole class will be a separate tab in a larger GUI
#
print "all done!"
#//runner
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I feel like I have to use threading and signals to accomplish this, but I am not having much luck. Any help would be greatly appreciated. Thank you!
import sys, time
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import *
class ConfigureThread(QtCore.QThread):
def run(self):
pass
#
# This is where most of the work is done, and takes about 1/2 an hour for everything to run
# Want to make this method a separate thread/process so the rest of the main GUI is still accessible
# while the program is running as the whole class will be a separate tab in a larger GUI
#
#//Popup Class - Will appear when the Switch Button is pressed
class Popup(QWidget):
def __init__(self):
QWidget.__init__(self)
#//nothing here now, it will have a message telling user to wait while program is run
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
grid = QtGui.QGridLayout()
label_header = QtGui.QLabel("TEST RECONFIGURE")
font = label_header.font()
font.setPointSize(24)
label_header.setFont(font)
#//Creating Static Labels that will be placed in the GUI
label_1 = QtGui.QLabel("Menu 1:")
label_2 = QtGui.QLabel("Menu 2:")
label_spacer = QtGui.QLabel("")
label_cfg = QtGui.QLabel("Current Configuration: '/tmp/directory_here' ")
global comboBox1
comboBox1 = QtGui.QComboBox()
comboBox1.addItem("1")
comboBox1.addItem("2")
global comboBox2
comboBox2 = QtGui.QComboBox()
comboBox2.addItem("3")
comboBox2.addItem("4")
#//Switch Button!!!
global switchButton
switchButton = QPushButton("Switch")
switchButton.clicked.connect(self.reconfigure)
quitButton = QtGui.QPushButton('Quit')
quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
#//Configure the grid layout
grid.addWidget(label_spacer, 0,0,9,9)
grid.addWidget(label_header, 0,0,1,6)
grid.addWidget(label_1, 1,0,1,1)
grid.addWidget(comboBox1, 2,0,1,1)
grid.addWidget(label_2, 3,0,1,1)
grid.addWidget(comboBox2, 4,0,1,1)
grid.addWidget(switchButton, 5,0,1,2)
grid.addWidget(label_cfg, 6,0,1,9)
grid.addWidget(quitButton, 9,9,1,1)
self.setLayout(grid)
self.setGeometry(640,300,400,600)
self.show()
#//open up the popup window for switch button, and reconfigure
def reconfigure(self):
print("Opening New Window")
self.w = Popup()
self.w.setGeometry(QRect(self.x()+100,self.y()+100,400,200))
self.w.show()
txt1 = str(comboBox1.currentText())
txt2 = str(comboBox2.currentText())
print(" reconfiguring to option %s and option %s" %(txt1, txt2))
self.th = ConfigureThread()
self.th.finished.connect(self.configuring_done)
self.th.start()
def configuring_done(self):
print("all done!")
#//runner
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm trying to learn the basics of threading with PySide, and so put together the below code. What I'm trying to do is launch a thread that will update a QPlainTextEdit widget using a list of string, with a delay between each string. Instead what I'm getting is a crash to desktop, and I can't understand why:
import sys
import time
from PySide import QtCore, QtGui
class Worker(QtCore.QThread):
to_log = QtCore.Signal(str)
def __init__(self, txt, parent=None):
super(Worker, self).__init__(parent)
self.txt = txt
def run(self):
for i in self.txt:
self.to_log.emit(i)
time.sleep(1)
class TestThreadsApp(QtGui.QWidget):
def __init__(self):
super(TestThreadsApp, self).__init__()
self.initUI()
def initUI(self):
self.log = QtGui.QPlainTextEdit()
self.pb = QtGui.QPushButton('Go')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.log)
hbox.addWidget(self.pb)
self.setLayout(hbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Test')
self.show()
self.pb.clicked.connect(self.get_worker)
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
def to_log(self, txt):
self.log.appendPlainText(txt)
def main():
app = QtGui.QApplication(sys.argv)
ex = TestThreadsApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If I update the get_worker() method to the below, it will run, but the QPlainTextEdit widget is updated with all the strings simultaneously, where as the behavior I'm wanting is for the widget to be updated by the threaded processes as each string is emitted - not altogether after both have been emitted:
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
while not worker.isFinished():
pass
You need to keep a reference to the thread, otherwise it will be garbage-collected as soon as get_worker returns.
So do something like this, instead:
def get_worker(self):
self.worker = Worker(['This is a test', 'to understand threading'])
self.worker.to_log.connect(self.to_log)
self.worker.start()