5

I have QSystemTrayIcon with a QMenu. In order to fill the menu, I need to fetch some things from the network, so I want to do that in the background.

So I have a QThread with a slot that is connected to the activated signal of the tray icon. Then the thread fetches the resources and updates the menu using another signal.

However, these updates do not show until I close and reopen the menu.

This seems to be a Mac specific problem. I ran my code on Windows, and there it updated more or less correctly. Is there any workaround?

Below is an extracted version of the problem. When the menu is opened, it will sleep 1 second in a thread and then change the menu. This change is not seen.

import sys
import time
from PySide import QtCore, QtGui

class PeerMenu(QtGui.QMenu):

    def __init__(self):
        QtGui.QMenu.__init__(self)
        self.set_peers("prestine")

    @QtCore.Slot(object)
    def set_peers(self, label):
        self.clear()

        self.addAction(QtGui.QAction(label, self))
        self.addSeparator()
        self.addAction(QtGui.QAction("Hello", self))

class GUIListener(QtCore.QObject):

    files = QtCore.Signal(object)

    def __init__(self):
        QtCore.QObject.__init__(self)
        self.counter = 0

    @QtCore.Slot()
    def check(self):
        time.sleep(1)
        self.counter += 1
        self.files.emit(str(self.counter))

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    icon = QtGui.QSystemTrayIcon(QtGui.QIcon('images/glyphicons-206-electricity.png'), app)

    listener = GUIListener()
    t = QtCore.QThread()
    t.start()
    listener.moveToThread(t)

    menu = PeerMenu()
    icon.activated.connect(listener.check)
    listener.files.connect(menu.set_peers)

    icon.setContextMenu(menu)

    icon.show()

    app.exec_()
NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104
Pepijn
  • 4,145
  • 5
  • 36
  • 64
  • Why don't you do all the stuff in the slot connected to the `QMenu::aboutToShow()` signal of the context menu? – vahancho Jan 14 '15 at 12:14
  • That would still block the menu, right? In this example it would take 1 second to show the menu. In real life, the network might be broken and the menu will never show. – Pepijn Jan 14 '15 at 12:18
  • Well, but why do you use menu for showing something that is not yet available? User will need to open the menu and wait without clicking elsewhere to keep the menu opened until the data got fetched? – vahancho Jan 14 '15 at 12:24
  • Well yea. Usually the data is there within half a second. But maybe I should look into using a window like Dropbox does. – Pepijn Jan 14 '15 at 12:33
  • Yeah, IMO, using a window or dialog would make user experience better. – vahancho Jan 14 '15 at 12:35
  • If you know how to make a popup similar to Dropbox, I'll gladly accept that as an answer. https://d339l1rkauam1y.cloudfront.net/wp-content/uploads/2013/02/mac.png – Pepijn Jan 14 '15 at 12:37
  • And you can't force redrawing ? – Thomas Ayoub Jan 16 '15 at 12:18
  • I tried both update() and repaint() but no change. Note that it works on Windows. It seems a Mac menu is just fixed as soon as it's open, so another workaround is needed. Maybe a small dialog or QWidgetAction, but I'm unsuccessful so far. – Pepijn Jan 16 '15 at 12:35

1 Answers1

4

After a few hours of extensive googling I finally figured it out.

You can create a borderless window using QtGui.QMainWindow(parent=None, flags=QtCore.Qt.Popup) and then find the location of the icon with icon.geometry().center() and finally move the window there with window.move(icon_point).

There is some hackery involved in deciding how to place the window relative to the icon. Full code can be found at https://github.com/pepijndevos/gierzwaluw/blob/master/gui.py

Pepijn
  • 4,145
  • 5
  • 36
  • 64