1

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.

CivilKen
  • 15
  • 5

1 Answers1

2

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

     # ...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • First of all, thanks for your detailed answer. But it seems I didn't explain my question well. I want to build a tray icon that have a button, and without pushing the button, the window wouldn't show up. Besides, I also want the window pop out automatically after the thread is finished, that's why I add join and show method at the end of thread.start. – CivilKen Mar 02 '20 at 02:22
  • @莊效丞 I understand that the "start_button" button must start executing a task that is executed using threading, at the same time the window must be hidden, and when the task finishes executing, the window should be displayed. Am I correct? If so, that is what I point out in my answer, to be clear I will provide a MWE – eyllanesc Mar 02 '20 at 02:29
  • eyllanesc, you are totally correct. There were some mistakes in my code, and after following your instruction, it finally worked. Thanks a lot. – CivilKen Mar 02 '20 at 03:38