QThread workaround in a Basic GUI - pyqt

Here is the sample code
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", text
self.listwidget.addItems(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.connect(self.workThread, QtCore.SIGNAL("update(QString"), self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
for i in range(6):
time.sleep(0.3)
self.emit(QtCore.SIGNAL('update(QString'), "from work thread " + str(i))
return
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()
Here I have a basic GUI with a listwidget and a push button. When I press the push button, I program should wait a moment and display a string in that listwidget. The WorkThread class does the waiting stuff and after waiting it emits a signal. But when I run the program, only I can see the GUI and nothing is displayed in the listwidget.
Can somebody tell me what is the reason behind this and how to fix this ?

QListWidget.addItems expects a list of items but you're giving it a single QString. You should use .addItem.
There are also a few minor corrections. You don't need to implement __del__ in your thread. You can skip __init__ if you're not doing additional stuff. And you should use new style signals and connections.
Here is the result with all corrections:
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.testButton.clicked.connect(self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", type(text)
self.listwidget.addItem(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.workThread.update.connect(self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
update = QtCore.pyqtSignal(str)
def run(self):
for i in range(6):
time.sleep(0.3)
self.update.emit("from work thread " + str(i))
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
sys.exit(app.exec_())

The PyQtWiki contains a pretty elaborate example how to send signals from a background thread to the UI. Apparently, no special magic is necessary as long as you QThread to implement your thread.
But I noticed that you use the signal released() to connect the button with the method test(). Did you try clicked()?

Related

pyqt signal from a window not changing the main window widgets

I can pass values between two windows using signal,
and I want the plain text change, while I press a button from another window.
but the insertPlainText and even text.show() are not working
I've tried sending the signal to the init part of the Mainwindow,
tried update, repaint, but none of them works.
appreciate for any of your help, thanks!
the search_send method in class SearchWindow to MainWindow method test_
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.tag_box.hide()
def test_(self, i): # -------problem here------------------
print(i) // <- value here were right
self.ui.tag_box.insertPlainText(i) # -------Plain Text does not change-------
self.ui.tag_box.show()# -------Plain Text does not show either--------------
class SearchWindow(QtWidgets.QMainWindow):
signal=pyqtSignal(str)
def __init__(self, endpoint=None, user=None, password=None, points_link=None):
super(SearchWindow, self).__init__()
self.ui = Ui_Search()
self.ui.setupUi(self)
self.ui.pushButton_2.clicked.connect(self.search_send)
def search_send(self): # -------problem here------------------
tag_list = [tag1,tag2, tag3]
otherClass = MainWindow()
self.signal.connect(otherClass.test_)
for k in tag_list:
self.signal.emit(k)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
sys.exit(app.exec_())
As we're unclear what Ui_Dialog contains, I can't tell what's wrong exactly, but can show you how Signal should be used.
This is example of Signal that's transferring text to Slot. Name may differ in PyQt5 - change accordingly.
from PySide2.QtWidgets import QWidget, QApplication, QPlainTextEdit, QTextEdit, QVBoxLayout
from PySide2.QtCore import Signal, Slot
import sys
class MainWindow(QWidget):
sig = Signal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("Main")
self.layout = QVBoxLayout()
self.text = QTextEdit()
self.layout.addWidget(self.text)
self.setLayout(self.layout)
self.text.textChanged.connect(self.onChange)
def onChange(self):
self.sig.emit(self.text.toPlainText())
class SubWindow(QWidget):
def __init__(self, connect_target: MainWindow):
super(SubWindow, self).__init__()
self.setWindowTitle("Sub")
self.layout = QVBoxLayout()
self.text = QPlainTextEdit()
self.layout.addWidget(self.text)
self.setLayout(self.layout)
connect_target.sig.connect(self.onSignal)
#Slot(str)
def onSignal(self, text):
self.text.insertPlainText(text + '\r\n')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
window_sub = SubWindow(window)
window_sub.show()
sys.exit(app.exec_())
Anything inside Main will be inserted to QPlainTextEdit as you wanted.

How to make pulsing progress bar in wxpython when running command?

I'd like to indicate that my program is running and didn't freeze with a pulsing progress bar. The command that would run in the background is this:
os.system('apt install program etc...')
It'll start on button press and I'd like to show a popup progress dialog during the process.
Here's the code of the releated part:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import wx
import threading
import common as g
class myThread (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
my_thread()
print ("Exiting " + self.name)
class myThread2 (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
my_thread2()
print ("Exiting " + self.name)
def my_thread():
os.system('apt update')
def my_thread2():
dlg = wx.ProgressDialog('Test', 'Please wait..', style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT | wx.STAY_ON_TOP)
test = OtherFrame(title='progres')
counter = 1
while g.th.isAlive():
print('HEEYY')
wx.MilliSleep(300)
dlg.Pulse("Doing computation %d"%counter)
test.Show()
counter += 1
class OtherFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title, size=(700, 400))
self.Centre()
self.InitUI()
self.Show()
def InitUI(self):
gs = wx.GridSizer(1, 1, 7, 7)
update = wx.Button(self,label = 'Check for system Updates')
gs.Add(update,28,wx.EXPAND)
update.Bind(wx.EVT_BUTTON, self.OnUpdate)
self.SetSizer(gs)
def OnUpdate(self, e):
g.th = myThread(1, "Thread-1")
thread2 = myThread2(2, "Thread-2")
thread2.start()
g.th.start()
g.th.join()
thread2.join()
def main():
app = wx.App()
f1 = OtherFrame(title='frame')
f1.Show()
app.MainLoop()
if __name__ == '__main__':
main()
The text 'HEEYY' appears in the right time on the right place, but the dialog doesn't show up.
You appear to be trying to use 1 thread to determine if the other one is still running.
There is a simpler way, see below:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import wx
import threading
#import common as g
class myThread (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
os.system('apt update')
print ("Exiting " + self.name)
class OtherFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title, size=(700, 400))
self.Centre()
self.InitUI()
self.Show()
def InitUI(self):
gs = wx.GridSizer(1, 1, 7, 7)
update = wx.Button(self,label = 'Check for system Updates')
gs.Add(update,28,wx.EXPAND)
update.Bind(wx.EVT_BUTTON, self.OnUpdate)
self.SetSizer(gs)
def OnUpdate(self, e):
t1 = myThread(1, "Thread-1")
t1.start()
counter = 0
dlg = wx.ProgressDialog('Test', 'Please wait..', style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT | wx.STAY_ON_TOP)
while t1.isAlive():
print('HEEYY')
wx.MilliSleep(300)
dlg.Pulse("Doing computation %d"%counter)
wx.GetApp().Yield()
counter += 1
del dlg
t1.join()
def main():
app = wx.App()
f1 = OtherFrame(title='frame')
f1.Show()
app.MainLoop()
if __name__ == '__main__':
main()

Open another wx.python frame cause the main UI to be blocked

I am trying to open another wx frame from a main frame UI. I followed the example shown here: https://wiki.wxpython.org/CallAfter but my main UI is still blocked.
Here is the event listener on the main UI:
def testShowGUI(self):
# This process is a long one
# It uses the vtk to read point cloud file and reconstruct the surface
file = "cache/d9c5e8ef-7b7f-485e-8fc8-23098c32afcb.ply"
reader = vtk.vtkPLYReader()
reader.SetFileName(file)
reader.Update()
delaunay = vtk.vtkDelaunay2D()
delaunay.SetAlpha(0.1)
delaunay.SetTolerance(0.0001)
delaunay.SetOffset(1.25)
delaunay.BoundingTriangulationOff()
delaunay.SetInputData(reader.GetOutput())
delaunay.Update()
#Once finish reading and processing the point cloud, pass to the next function for rendering
wx.CallAfter(self.AfterProcess, delaunay)
def AfterProcess(self, data):
meshVisGui = MesVisGUI.MeshVisGui(data)
meshVisGui.Show()
def OnEnter(self, event):
#Event listener when user click on Enter button
my_thread = threading.Thread(target=self.testShowGUI)
my_thread.start()
The code for the separate frame is as below:
class MeshVisGui(wx.Frame):
SPACING = 4
def __init__(self, delaunay, parent=None):
self.delaunayData = delaunay
self.title = "Mesh Visualization"
wx.Frame.__init__(self, None, -1, self.title)
self.Initialize()
def Initialize(self):
self.panel = wx.Panel(self, -1, size=(600, 400), style=wx.BORDER_RAISED)
self.widget3d = wxVTKRenderWindowInteractor(self.panel, -1)
self.widget3d.Enable()
self.render = vtk.vtkRenderer()
self.render.SetBackground(params.BackgroundColor)
self.widget3d.GetRenderWindow().AddRenderer(self.render)
self.interactor = self.widget3d.GetRenderWindow().GetInteractor()
self.interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
self.axesWidget = utils.createAxes(self.interactor)
self.meshActor = utils.build_actor(self.delaunayData)
self.render.AddActor(self.meshActor)
self.render.ResetCamera()
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.widget3d, 1, wx.EXPAND, self.SPACING)
self.panel.SetSizer(box)
self.Layout()
However, on my main UI, it is still showing a spinning icon and block the UI while it is trying to process the point cloud data. Can someone help me spot what I have done wrong?
Wxpython version: 4.0.1
Python version: 3.6.5
Following on from Mike Driscoll's answer and comment, here is a sample of a threaded task running from another panel.
The thread reports back to the 2nd panel (it's parent) using an event. This allows a progress bar to be updated.
The 2nd panel includes a "Cancel" option for the threaded task, whilst the main frame has a button to test if it is not frozen.
The use of wx.GetApp().Yield() may be slightly old fashioned but I have always found it to be reliable.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(300,10), pos=(10,50), range=300)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 300:
time.sleep(1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
frame = ThreadFrame(title='Threaded Task', parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = self.txt.GetValue()
txt += "More text " + str(self.text_count)+"\n"
self.txt.SetValue(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
You do not need threads to open new frames / windows in your wxPython application. You just need to create a sub-class of wx.Frame to hold the code of your other frame. Then from your main application's frame, you can instantiate the other frame and show it. You use the same concept when you create a wx.Dialog or a wx.MessageDialog.
Here is a simple example:
import wx
class OtherFrame(wx.Frame):
"""
Class used for creating frames other than the main one
"""
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
def on_new_frame(self, event):
frame = OtherFrame(title='SubFrame',
parent=wx.GetTopLevelParent(self))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
In this example, I set the other frame's parent to the MainFrame instance by using wx.GetTopLevelParent(self). The benefit of setting a parent for the sub-frames is that if I close the main frame, it will cause the other frames to also be closed.

PySide: Threading causes GUI to crash

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()

pyqt4 event programming (signal and slots )

I am trying to change this event code to standard signal-slot format.
But it does not work.
Can I get help?
this is slide number changing to lcd display number.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.lcd = QtGui.QLCDNumber(self)
self.sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
#sld.valueChanged.connect(lcd.display)
QtCore.QObject.connect(self.sld, QtCore.SIGNAL(str1), self, QtCore.SLOT("SHOW()"))
#QtCore.pyqtSlot()
def SHOW(str1):
self.lcd.display()
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You were very close with your first attempt. All you need is:
self.sld.valueChanged.connect(self.lcd.display)
(The other lines up to self.setGeometry can be deleted).

Resources