Context menu didn't work on tray icon when using pyqt5 - python

I'm developing an application with pyqt5, which will hide window automatically when it starts to work. And I hope to build a tray icon with context menu to show the window again. Below is my brief code,
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.set_sytem_tray_icon()
self.current_status = Stop
self.track_str = ''
self.start_button.clicked.connect(self.start_button_clicked)
self.stop_button.clicked.connect(self.stop_button_clicked)
self.show()
def set_sytem_tray_icon(self):
contextMenu = QMenu()
action = contextMenu.addAction("Show Window")
action.triggered.connect(self.show)
contextMenu.addAction(action)
self.tray_icon = QSystemTrayIcon()
self.tray_icon.setIcon(QtGui.QIcon("img.ico"))
self.tray_icon.setContextMenu(contextMenu)
self.tray_icon.show()
def get_specific_window(self, class_name, title):
def check_ULink_status(self):
try:
while True:
# Doing something
break
def start_button_clicked(self):
self.hide()
thread = threading.Thread(target=self.check_ULink_status)
thread.setDaemon(True)
thread.start()
thread.join()
self.show()
def stop_button_clicked(self):
reply = QMessageBox.information(self, "Warning", "Stop", QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
self.current_status = Stop
self.change_button_availability()
Here is my problem, when I clicked start button, the application started working but tray icon didn't response to any action. I believe there was some conflict with my main thread, but I still can't figure out what's going on. Does anyone have answer for this?
Update,
I would like to provide another solution using qthread.
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.set_sytem_tray_icon()
self.current_status = Stop
self.track_str = ''
self.start_button.clicked.connect(self.start_button_clicked)
self.stop_button.clicked.connect(self.stop_button_clicked)
self.show()
def set_sytem_tray_icon(self):
contextMenu = QMenu()
action = contextMenu.addAction("Show Window")
action.triggered.connect(self.show)
contextMenu.addAction(action)
self.tray_icon = QSystemTrayIcon()
self.tray_icon.setContextMenu(contextMenu)
self.tray_icon.show()
def set_sytem_tray_icon_with_qthread(self):
set_sytem_tray_icon_qthread = qthread_definition.MyQThread2()
set_sytem_tray_icon_qthread.signal.connect(self.set_sytem_tray_icon)
set_sytem_tray_icon_qthread.start()
set_sytem_tray_icon_qthread.wait()
def show_mainwindow_with_qthread(self):
show_mainwindow_qthread = qthread_definition.MyQThread2()
show_mainwindow_qthread.signal.connect(self.show)
show_mainwindow_qthread.start()
show_mainwindow_qthread.wait()
def get_specific_window(self, class_name, title):
# doing something
def check_ULink_status(self):
self.set_sytem_tray_icon_with_qthread() # add new qthread here
try:
while True:
# Doing something
break
self.show_mainwindow_with_qthread()
def start_button_clicked(self):
self.hide()
thread = threading.Thread(target=self.check_ULink_status)
thread.setDaemon(True)
thread.start()
def stop_button_clicked(self):
reply = QMessageBox.information(self, "Warning", "Stop", QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
self.current_status = Stop
self.change_button_availability()
where MyQThread2 class is shown below,
class MyQThread2(QtCore.QThread):
# this thread is to create the tray icon
signal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
def run(self):
self.signal.emit()
To show the main window in the thread, we need to create a qthread to finish this task, because showing the window is a kind of modification of qt object, which is not allowed outside main thread.

As the docs of the join() method points out:
join(timeout=None)
Wait until the thread terminates. This blocks the
calling thread until the thread whose join() method is called
terminates – either normally or through an unhandled exception – or
until the optional timeout occurs.
When the timeout argument is present and not None, it should be a
floating point number specifying a timeout for the operation in
seconds (or fractions thereof). As join() always returns None, you
must call is_alive() after join() to decide whether a timeout happened
– if the thread is still alive, the join() call timed out.
When the timeout argument is not present or None, the operation will
block until the thread terminates.
A thread can be join()ed many times.
join() raises a RuntimeError if an attempt is made to join the current
thread as that would cause a deadlock. It is also an error to join() a
thread before it has been started and attempts to do so raise the same
exception.
(emphasis mine)
This method locks until the thread finishes executing preventing the GUI event loop from executing, causing it not to respond to events since it is frozen.
The solution is to remove this method:
def start_button_clicked(self):
self.hide()
thread = threading.Thread(target=self.check_ULink_status)
thread.setDaemon(True)
thread.start()
# thread.join() # remove this line
self.show()
It seems that the OP use join() so that when the task is finished the window is displayed again, if so, the correct solution is to use a signal:
class MainWindow(QMainWindow, Ui_MainWindow):
finished = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.finished.connect(self.show)
# ...
# ...
def check_ULink_status(self):
# After finishing the task the signal must be emitted
self.finished.emit()
# ...
def start_button_clicked(self):
self.hide()
thread = threading.Thread(target=self.check_ULink_status)
thread.setDaemon(True)
thread.start()
# ...

Related

why am i getting QThread: Destroyed while thread is still running in this case? [duplicate]

I'm trying to find a way to quit my app properly. When I exit, I get an error saying QThread: Destroyed while thread is still running. I have a thread for feeding output to a QTextBrowser. What should be the proper way to exit? Here's what I've got:
class LogReceiver(QtCore.QObject):
mysignal = QtCore.Signal(str)
def __init__(self, queue, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.queue = queue
def run(self):
while True:
text = self.queue.get()
self.mysignal.emit(text)
if __name__ == '__main__':
queue = Queue()
thread = QtCore.QThread()
my_receiver = MyReceiver(queue)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
my_receiver.mysignal.connect(window.append_text)
my_receiver.moveToThread(thread)
thread.started.connect(my_receiver.run)
thread.start()
sys.exit(app.exec_())
Should thread somehow be terminated upon exit? Note that self.queue.get() blocks and waits for text.
Thanks
You need to re-structure the while loop so that it doesn't block uncondtionally.
You can do this with a simple flag and a timeout:
def run(self):
self.active = True
while self.active:
try:
text = self.queue.get(timeout=1.0)
self.mysignal.emit(text)
except Empty:
continue
So now the queue won't block indefinitely, and the flag will be checked once a second to see if the loop should be exited.
EDIT:
Here's a working example based on your code:
import sys
from queue import Queue, Empty
from PySide import QtCore, QtGui
class LogReceiver(QtCore.QObject):
mysignal = QtCore.Signal(str)
def __init__(self, queue, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.queue = queue
def run(self):
self.active = True
while self.active:
try:
text = self.queue.get(timeout=1.0)
self.mysignal.emit('text')
except Empty:
continue
print('finished')
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.queue = Queue()
self.thread = QtCore.QThread(self)
self.receiver = LogReceiver(self.queue)
self.receiver.moveToThread(self.thread)
self.thread.started.connect(self.receiver.run)
self.thread.start()
def closeEvent(self, event):
print('close')
self.receiver.active = False
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Try:
# previous code here
thread.start()
app.exec_()
thread.terminate()
thread.wait()
sys.exit(0)
Basically when exec_() finishes (QApplication is closed ex. by closing the window) you force the thread to terminate and wait() for it to cleanup. If your thread has an event loop you can call quit() instead of terminate(). terminate() is generally not a good idea see: here.
The more desirable approach would be to put a flag in run() method ex.
while !flag:
do stuff
and change main to:
app.exec_()
flag = True
thread.wait()
sys.exit(0)
Where flag is a global variable. QThread terminates itself when run() method finishes.

Qthread communication between threads (Python) [duplicate]

After read and searching I am trying to use the generate a QObject then use the movetoThread method to run an independent process and allow the QMainWindow to continue to respond. This has not worked when I have tried to implement the operation in a QThread.run() method. The following code is my attempt to make a simple example. While the code works in running thread independent of the MainWindow, it does not abort. The only way I can get a thread to stop is to set worker.end = True. Which I think should not be the way to do it.
"""
This is a program to test Threading with Objects in PyQt4.
"""
from time import sleep
import sys
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from PyQt4.QtGui import QMainWindow, QApplication, QProgressBar
from PyQt4.QtGui import QPushButton, QVBoxLayout, QWidget
class workerObject(QObject):
bar_signal = pyqtSignal(int)
res_signal = pyqtSignal(str)
term_signal = pyqtSignal()
def __init__(self, maxIters):
super(workerObject, self).__init__()
self.maxIters = maxIters
def run(self):
self.bar_signal.emit(self.maxIters)
sleep(1)
self.end = False
for step in range(self.maxIters):
if self.end:
self.maxIters = step
break
self.bar_signal.emit(step)
sleep(2)
self.res_signal.emit("Got to {}".format(self.maxIters))
self.term_signal.emit()
#pyqtSlot()
def mystop(self):
print "stop signalled?"
self.end = True
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
self.maxIters = 50
widget = QWidget()
layout = QVBoxLayout(widget)
self.go_btn = QPushButton()
self.go_btn.setText('Go')
layout.addWidget(self.go_btn)
self.abort_btn = QPushButton()
self.abort_btn.setText('Stop')
layout.addWidget(self.abort_btn)
self.simulation_bar = QProgressBar()
self.simulation_bar.setRange(0, self.maxIters)
self.simulation_bar.setFormat("%v")
layout.addWidget(self.simulation_bar)
self.setCentralWidget(widget)
self.go_btn.clicked.connect(self.run_mc)
# The button calls the windows method to stop --- it could
# be that is 'clicked' calls the worker.mystop
# self.abort_btn.clicked.connect(self.stop_mc)
# This allows for the abort button to do somethign in the MainWindow
# before the abort_signal is sent, this works
self.abort_btn.clicked.connect(self.stop_mc)
def run_mc(self):
self.thread = QThread()
self.worker = workerObject(self.maxIters)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
# This is the simple stop method, but does not work
# self.abort_btn.clicked.connect(self.worker.mystop)
# This uses the signal in the MCwindow - this connection does NOT works
self.abort_signal.connect(self.worker.mystop)
# This does NOT stop the thread
# and would not allow for any clean up in the worker.
# self.abort_signal.connect(self.thread.terminate)
# This is a 'bad' way to stop the woker ... It does, however, work
# self.abort_signal.connect(self.stopper)
self.worker.bar_signal.connect(self.setBar)
self.worker.res_signal.connect(self.setData)
self.worker.term_signal.connect(self.thread.terminate)
self.thread.start()
def stop_mc(self):
print "Stopping?!"
# This signal is NEVER seen by the Worker.
self.abort_signal.emit()
def stopper(self):
print "I should stop?!"
# Should use signals to tell the worker to stop - and not setting a attribute
self.worker.end=True
#pyqtSlot(int)
def setBar(self, val):
self.simulation_bar.setValue(val)
#pyqtSlot(str)
def setData(self, txt):
print "Got done Sig!", txt
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MCwindow()
window.show()
sys.exit(app.exec_())
The reason why the slot connected to abort_signal doesn't seem to get called, is because cross-thread signals are queued by default. This means the signal will be wrapped as an event and posted to the event queue of whichever thread the receiver is living in.
In your particular example, the receiver is a worker object which has been moved to a worker thread. Calling start() on the worker thread will start its event-loop, and that is where abort_signal will be queued. However, the run() method of the worker object starts a for loop, which will block the thread's event processing in exactly the same way it would if it was executed in the main gui thread!
You can more clearly see what's happening if you make a few adjustments to your example:
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
# use a sane default
self.maxIters = 5
...
# DO NOT use QThread.terminate
self.worker.term_signal.connect(self.thread.quit)
Now run the example, and then click the Go button, click the Stop button, and wait for the worker to complete normally. This should produce output like this:
Stopping?!
Got done Sig! Got to 5
stop signalled?
Note that "stop signalled" is output last - i.e. after run() exits and control has returned to the thread's event-loop. In order to process in-coming signals while the worker is running, you will need to force immediate processing of the thread's pending events. This can be done like this:
for step in range(self.maxIters):
QApplication.processEvents()
...
With that in place, you should then see output like this:
Stopping?!
stop signalled?
Got done Sig! Got to 2
Which is presumably what you intended.
Typically a thread will close when it exits the run method. The other way to get a regular python thread to close is by calling it's join method.
For PyQt the join method should either be the quit or terminate method. You should probably still set your end variable to True.

PyQt4 Wait in thread for user input from GUI

I have a thread class "MyThread" and my main application which is simply called "Gui". I want to create a few objects from the thread class but for this example I created only one object. The thread class does some work, then emits a signal to the Gui class, indicating that a user input is needed (this indication for now is simply changing the text of a button). Then the thread should wait for a user input (in this case a button click) and then continue doing what it is doing...
from PyQt4 import QtGui, QtCore
class MyTrhead(QtCore.QThread):
trigger = QtCore.pyqtSignal(str)
def run(self):
print(self.currentThreadId())
for i in range(0,10):
print("working ")
self.trigger.emit("3 + {} = ?".format(i))
#### WAIT FOR RESULT
time.sleep(1)
class Gui(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Gui, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.btn)
self.t1 = MyTrhead()
self.t1.trigger.connect(self.dispaly_message)
self.t1.start()
print("thread: {}".format(self.t1.isRunning()))
#QtCore.pyqtSlot(str)
def dispaly_message(self, mystr):
self.pushButton.setText(mystr)
def btn(self):
print("Return result to corresponding thread")
if "__main__" == __name__:
import sys
app = QtGui.QApplication(sys.argv)
m = Gui()
m.show()
sys.exit(app.exec_())
How can I wait in (multiple) threads for a user input?
By default, a QThread has an event loop that can process signals and slots. In your current implementation, you have unfortunately removed this behaviour by overriding QThread.run. If you restore it, you can get the behaviour you desire.
So if you can't override QThread.run(), how do you do threading in Qt? An alternative approach to threading is to put your code in a subclass of QObject and move that object to a standard QThread instance. You can then connect signals and slots together between the main thread and the QThread to communicate in both directions. This will allow you to implement your desired behaviour.
In the example below, I've started a worker thread which prints to the terminal, waits 2 seconds, prints again and then waits for user input. When the button is clicked, a second separate function in the worker thread runs, and prints to the terminal in the same pattern as the first time. Please note the order in which I use moveToThread() and connect the signals (as per this).
Code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class MyWorker(QObject):
wait_for_input = pyqtSignal()
done = pyqtSignal()
#pyqtSlot()
def firstWork(self):
print 'doing first work'
time.sleep(2)
print 'first work done'
self.wait_for_input.emit()
#pyqtSlot()
def secondWork(self):
print 'doing second work'
time.sleep(2)
print 'second work done'
self.done.emit()
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__()
self.initUi()
self.setupThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(False)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
#pyqtSlot()
def enableButton(self):
self.button.setEnabled(True)
#pyqtSlot()
def done(self):
self.button.setEnabled(False)
def setupThread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstWork)
self.button.clicked.connect(self.worker.secondWork)
self.worker.wait_for_input.connect(self.enableButton)
self.worker.done.connect(self.done)
# Start thread
self.thread.start()
if __name__ == "__main__":
app = QApplication([])
w = Window()
app.exec_()

QThread doesn't finish

I am building a GUI with a socket connection running in a backround thread in PyQt5. Everything is working pretty well except the Qthread never emits the finished signal. Maybe this isn't a problem and I could change my implementation around to work around it, but is the expected behavior that the Qthread would continue to run once the object that has been moved has stopped doing anything?
Should I write a function in the main class to stop the thread once I'm done with it, or can I just move new things to that thread with no consequences?
class MyClass(PyQt5.QtWidgets.QMainWindow)
def __init__(self, parent=None):
# Setup thread for the socket control
self.simulThread = PyQt5.QtCore.QThread()
self.socketController = SocketController()
self.socketController.moveToThread(self.simulThread)
self.simulThread.started.connect(self.socketController.controlSocket)
self.simulThread.finished.connect(self.deadThread)
# Bind controls to events
self.ui.buttonConnect.clicked.connect(self.simulThread.start)
self.ui.buttonDisconnect.clicked.connect(lambda: self.socketController.stop())
def deadThread(self, data):
print ("THREAD IS DEAD.")
class SocketController(PyQt5.QtCore.QObject):
finished = PyQt5.QtCore.pyqtSignal()
def __init__(self):
super(SocketController, self).__init__()
self.run = True;
def controlSocket(self):
#setup the socket
while self.run:
# do socket stuff
time.sleep(1)
#close the socket
self.finished.emit()
def stop(self):
self.run = False;
A QThread has it's own event loop to handle signals. The controlSocket method blocks this event loop until self.run == False. But this never happens because self.run is only set to False when control is returned to the event lop and it can process the signal that runs the stop method.
You probably want to rearchitect your thread so that instead of having a while loop which blocks the QThread event loop, you construct a QTimer in the thread which calls the #do socket stuff code every 1 second. This way, control is returned to the threads event loop and it can process the stop signal (which would be changed to stop the QTimer from triggering again)
The answer is actually a combination of what three_pineapples and OP posted in their answers:
As noted by three_pineapples, the main problem is that controlSocket(), which gets called by the thread event loop when started signal is emitted, doesn't return until the stop() method gets called. However, in the OP's design, the stop() method can only be called by Qt if the socket thread can process events (since this is how cross-thread signals are dispatched, via the event loops). This is not possible while controlSocket() is busy looping and sleeping.
In order for the thread to exit, its event loop must be stopped.
The first issue must be fixed by either allowing the thread to process events during the loop, or by using a timer instead of a loop. Processing events is shown in this piece of code, based on the OP code:
import time
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QApplication, QHBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal
class MyClass(QWidget):
def __init__(self, parent=None):
super().__init__()
self.resize(250, 150)
self.setWindowTitle('Simple')
# Setup thread for the socket control
self.socketController = SocketController()
self.simulThread = QThread()
self.socketController.moveToThread(self.simulThread)
self.simulThread.started.connect(self.socketController.controlSocket)
self.simulThread.finished.connect(self.deadThread)
# Bind controls to events
self.buttonConnect = QPushButton('connect')
self.buttonConnect.clicked.connect(self.simulThread.start)
self.buttonDisconnect = QPushButton('disconnect')
self.buttonDisconnect.clicked.connect(self.socketController.stop)
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.buttonConnect)
hbox.addWidget(self.buttonDisconnect)
self.setLayout(hbox)
def deadThread(self, data):
print("THREAD IS DEAD.")
class SocketController(QObject):
finished = pyqtSignal()
def __init__(self):
print('initialized')
super().__init__()
self.run = True
def controlSocket(self):
# setup the socket
print('control socket starting')
while self.run:
# do socket stuff
app.processEvents()
print('control socket iterating')
time.sleep(1)
# close the socket
self.finished.emit()
print('control socket done')
def stop(self):
print('stop pending')
self.run = False
app = QApplication([])
mw = MyClass()
mw.move(300, 300)
mw.show()
app.exec()
The QTimer is a little more complicated, but the idea is to have controlSocket() only do one iteration of the loop, and call it repeatedly via QTimer:
QTimer.singleShot(0, self.controlSocket)
and
def controlSocket(self):
# do socket stuff, then:
if self.run:
QTimer.singleShot(1000, self.controlSocket)
else:
self.finished.emit()
Either of the above approaches fixes the first problem, and allows the SocketController to stop doing its socket-related work.
The second problem is that even after the socket controller is done its job, the thread loop is still running (the two loops are independent). To make the event loop exit, it must be stopped via the thread's quit() method. The quit and the stop should be done together:
class MyClass(QWidget):
def __init__(self, parent=None):
...
self.buttonDisconnect = QPushButton('disconnect')
self.buttonDisconnect.clicked.connect(self.stop)
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.buttonConnect)
hbox.addWidget(self.buttonDisconnect)
self.setLayout(hbox)
def stop(self):
self.simulThread.quit()
self.socketController.stop()
Ok, so I found A solution, but I don't really know if its correct. Instead of mapping the disconnect button to the socket stop function, I mapped it to function defined in MyClass that calls a new function I wrote called controlDisconnect which also tells the thread explicitly to quit.
# The changed line
self.ui.buttonDisconnect.clicked.connect(self.controlDisconnect)
def controlDisconnect(self):
self.socketController.stop()
self.simulThread.quit()
While this is working (the thread prints out the message that indicates it died), I'm not sure its really good practice. There should probably at least be some code to make sure the socketController has actually stopped before telling the thread to quit.

Python/Pyside: How do i completely destroy thread objects after they are finished?

I have a button on my GUI that starts a thread. An additional button that stops the thread by sending a signal to the thread asking it to kill itself by raising an exception. However, even after the thread is dead, the button's signal seems to be connected to any new thread objects including the old ones. So if i've started a 5th thread, the button calls the method 5 times.
class Worker(QtCore.QObject):
end = QtCore.Signal()
def __init__(self, settings):
super(Worker, self).__init__()
def start_work(self):
self.end_flag = False
try:
print('working')
if(end_flag):
raise KillThread
print('working again')
if(end_flag):
raise KillThread
except KillThread:
print('user clicked stop')
class Gui(QtGui.QMainWindow):
def __init__(self):
super(Gui, self).__init__()
self.ui = Ui_mainWindow()
self.ui.setupUi(self)
# button that starts a new thread
self.ui.start_button.clicked.connect(self.start_work_thread)
def start_work_thread(self):
self.new_thread = QtCore.Qthread()
self.worker = Worker()
self.new_thread.started.connect(self.worker.start_work)
self.new_thread.finished.connect(self.new_thread.quit)
# connects the stop button
self.stop_button.clicked.connect(self.kill_thread)
# moves worker to a thread
self.worker.moveToThread(self.new_thread)
self.new_thread.start()
# invoked when stop button is clicked
def kill_thread(self):
print('killing thread')
self.new_thread.end_flag = True
if __name__ == '__main__':
try:
app = QtGui.QApplication(sys.argv)
main = Gui()
main.show()
sys.exit(app.exec_())
finally:
pass
#save settings

Categories