PyQt: Show menu in a system tray application - python

First of all, I'm an experienced C programmer but new to python. I want to create a simple application in python using pyqt. Let's imagine this application it is as simple as when it is run it has to put an icon in the system tray and it has offer an option in its menu to exit the application.
This code works, it shows the menu (I don't connect the exit action and so on to keep it simple)
import sys
from PyQt4 import QtGui
def main():
app = QtGui.QApplication(sys.argv)
trayIcon = QtGui.QSystemTrayIcon(QtGui.QIcon("Bomb.xpm"), app)
menu = QtGui.QMenu()
exitAction = menu.addAction("Exit")
trayIcon.setContextMenu(menu)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
But this doesn't:
import sys
from PyQt4 import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu()
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main():
app = QtGui.QApplication(sys.argv)
trayIcon = SystemTrayIcon(QtGui.QIcon("Bomb.xpm"), app)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I probably miss something. There are no errors but in the second case when I click with the right button it doesn't show the menu.

Well, after some debugging I found the problem. The QMenu object it is destroyed after finish __init__ function because it doesn't have a parent. While the parent of a QSystemTrayIcon can be an object for the QMenu it has to be a Qwidget. This code works (see how QMenu gets the same parent as the QSystemTrayIcon which is an QWidget):
import sys
from PyQt4 import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("Bomb.xpm"), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

I think I would prefer the following as it doesn't seem to depend upon QT's internal garbage collection decisions.
import sys
from PyQt4 import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtGui.QMenu(parent)
exitAction = self.menu.addAction("Exit")
self.setContextMenu(self.menu)
def main():
app = QtGui.QApplication(sys.argv)
style = app.style()
icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_FileIcon))
trayIcon = SystemTrayIcon(icon)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Here is the code with Exit action implemented
import sys
from PyQt4 import QtGui, QtCore
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
QtCore.QObject.connect(exitAction,QtCore.SIGNAL('triggered()'), self.exit)
def exit(self):
QtCore.QCoreApplication.exit()
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("qtLogo.png"), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Here is the PyQt5 version (was able to implement the Exit action of demosthenes's answer).
Source for porting from PyQt4 to PyQt5
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
# code source: https://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5
#PyQt4 to PyQt5 version: https://stackoverflow.com/questions/20749819/pyqt5-failing-import-of-qtgui
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
menu.triggered.connect(self.exit)
def exit(self):
QtCore.QCoreApplication.exit()
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
on=r''# ADD PATH OF YOUR ICON HERE .png works
main(on)

I couldn't get any of the above answers to work in PyQt5 (the exit in the system tray menu, wouldn't actually exit), but i managed to combine them for a solution that does work. I'm still trying to determine if exitAction should be used further somehow.
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
menu.triggered.connect(self.exit)
def exit(self):
QtCore.QCoreApplication.exit()
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
on='icon.ico'
main(on)

With a pyqt5 connected event:
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
menu.triggered.connect(self.exit)
def exit(self):
QtCore.QCoreApplication.exit()

it can be easier
instead:
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
write:
super().__init__(self, icon, parent)

For PySide6:
in main.py:
import sys
try:
from PySide6 import QtWidgets
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import (QIcon)
from PySide6.QtCore import (QSize)
except ImportError as e:
print("no pyside6")
print("use python.exe -m pip install pyside6")
print(str(e))
exit(0)
from silnik.mytray import SystemTrayIcon
if __name__ == "__main__":
app = QApplication(sys.argv)
ico = QIcon()
ico.addFile(u":/img/brylant_64x64.png", QSize(64, 64))
ico.addFile(u":/img/brylant_16x16.png", QSize(16, 16))
ico.addFile(u":/img/brylant_32x32.png", QSize(32, 32))
ico.addFile(u":/img/brylant_48x48.png", QSize(48, 48))
ico.addFile(u":/img/brylant_128x128.png", QSize(128, 128))
app.setWindowIcon(ico)
trayIcon = silnik.mytray.SystemTrayIcon(ico)
app.tray = trayIcon
trayIcon.show()
in folder silnik
create mytray.py:
import sys
from PySide6.QtWidgets import (QSystemTrayIcon, QMenu)
from PySide6.QtCore import (QCoreApplication)
class SystemTrayIcon(QSystemTrayIcon):
def __init__(self, icon):
super().__init__()
self.setIcon(icon)
self.menu = QMenu()
self.exitAction = self.menu.addAction("Wyjście")
self.setContextMenu(self.menu)
self.exitAction.triggered.connect(self.exit)
def exit(self):
QCoreApplication.exit()

Related

Pyqt5 open child windows of class of another file *.py Pb [duplicate]

I am trying to display a loading gif after a button is pressed. This is the code I currently have
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self)#Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
def gif_display(self):
l = QMovieLabel('loading.gif')
l.show()
class QMovieLabel(QLabel):
def __init__(self, fileName):
QLabel.__init__(self)
m = QMovie(fileName)
m.start()
self.setMovie(m)
def setMovie(self, movie):
QLabel.setMovie(self, movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
I would like to display the gif called "loading.gif" after the button is pressed. Nothing appears after pressing the button and I am unsure of what to do to get the gif to properly appear. The gif is the same size as the screen that I created (240x320).
The problem is that QMovieLabel is a local variable within gif_display so it will be deleted when the function finishes running, so the solution is to avoid deleting it. There are 2 options: make it an attribute of the class or make it a child of the window , I will show the second method since I think it is the one you want:
import sys
from PyQt4 import QtCore, QtGui
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self) # Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
#QtCore.pyqtSlot()
def gif_display(self):
l = QMovieLabel('loading.gif', self)
l.adjustSize()
l.show()
class QMovieLabel(QtGui.QLabel):
def __init__(self, fileName, parent=None):
super(QMovieLabel, self).__init__(parent)
m = QtGui.QMovie(fileName)
self.setMovie(m)
m.start()
def setMovie(self, movie):
super(QMovieLabel, self).setMovie(movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
run()

shortcut doesn't work in PyQt5 in tray dropdown menu

I was trying to set a shortcut for dropdown menu. The code is below
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QAction
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
super(SystemTrayIcon, self).__init__(icon, parent)
menu = QtWidgets.QMenu(parent)
# add actions and shortcuts for them.
exitAction = QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(parent.close)
menu.addAction(exitAction)
restoreAction = QAction('Restore', self)
restoreAction.setShortcut("Ctrl+R")
restoreAction.triggered.connect(self.restore)
menu.addAction(restoreAction)
self.setContextMenu(menu)
def restore(self):
print 'restore'
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Icon')
self.tray_icon = SystemTrayIcon(QtGui.QIcon('/opt/WizNote/wiznote.png'), self)
self.tray_icon.show()
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
w = MainWindow()
sys.exit(app.exec_())
The shortcut doesn't work. Is there something wrong with my code?
Python2.7 and PyQt5 was installed in my laptop.
I had exactly the same problem as yours, and I solved it by respecting the following pattern for each action:
menu.addAction(exitAction)
parent.addAction(exitAction) # line to be added

PyQt using classes to show button

Getting very stuck here, I am trying to learn how to use classes, and so simply want to show a button on a window when the button is in a different class. Here is the code I am trying to use:
#!/usr/bin/python3
import sys
from PyQt4 import QtGui, QtCore
class Window(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(0, 0, 800, 600)
main_menu = Menu()
self.show()
class Menu(QtGui.QWidget):
def __init__(self):
btn = QtGui.QPushButton("Quit")
btn.resize(btn.sizeHint())
btn.move(100,100)
btn.show()
print("Hello I am a menu")
def main():
app = QtGui.QApplication(sys.argv)
main_window = Window()
sys.exit(app.exec())
if __name__ == "__main__":
main()
This works so far as I get a window, but no button on it, howerver the print message I put in works. What am I doing wrong please?
You must initialize the parent class in Menu, in addition to passing the parent to that class and the button.
import sys
from PyQt4 import QtGui, QtCore
class Window(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(0, 0, 800, 600)
main_menu = Menu(self)
self.show()
class Menu(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
btn = QtGui.QPushButton("Quit", self)
btn.resize(btn.sizeHint())
btn.move(100,100)
print("Hello I am a menu")
def main():
app = QtGui.QApplication(sys.argv)
main_window = Window()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Make a modal QDialog minimize when QMainWindow minimized (using PyQt 5)

Example code I am using:
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.open_about = False
self.openAction = QtWidgets.QAction('About', self)
self.openAction.triggered.connect(self.aboutDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.calendar = QtWidgets.QCalendarWidget(self)
self.setCentralWidget(self.calendar)
def about_state_upd(self, value):
self.open_about = value
def aboutDialog(self):
self._about = AboutDialog(self)
self._about.exec_()
def hideEvent(self, hideEvent):
if self.open_about == True:
self._about.setVisible(False)
def showEvent(self, showEvent):
if self.open_about == True:
if self._about.isHidden() == True:
self._about.setModal(True)
self._about.setVisible(True)
class AboutDialog(QtWidgets.QDialog):
def __init__(self, parent):
super(AboutDialog, self).__init__(parent)
self.setMinimumSize(400, 350)
self.parent().about_state_upd(True)
def closeEvent(self, closeEvent):
self.parent().about_state_upd(False)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())
This code basically works, but seems very complicated. Is there a simpler / cleaner way to make it so that when the modal QDialog is open, if the QMainWindow is minimized, the QDialog also gets minimized too (and reverse when QMainWindow is restored)?
Code is running on KDE Neon (Kubuntu-based distro).
May be you can use this: http://korbinin.blogspot.fr/search/label/minimize%20button
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainForm(QDialog):
def __init__(self, fn=None,parent=None):
super(MainForm, self).__init__(parent,\
flags=Qt.WindowMinimizeButtonHint|Qt.WindowMaximizeButtonHint)
Thanks to the people on the PyQt Mailing list, I managed to get a workaround for KDE. Instead of using exec_(), I am just using show() - then I use setDisabled() on QMainWindow to make dialog act in a modal fashion. Here is a (very quick and basic) example for anyone interested:
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QtWidgets.QAction('About', self)
self.openAction.triggered.connect(self.aboutDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.calendar = QtWidgets.QCalendarWidget(self)
self.setCentralWidget(self.calendar)
def aboutDialog(self):
self._about = AboutDialog(self)
self.setDisabled (True)
self._about.show()
def enableWidgets(self):
self.setDisabled(False)
class AboutDialog(QtWidgets.QDialog):
def __init__(self, parent):
super(AboutDialog, self).__init__(parent)
self.setMinimumSize(400, 350)
def closeEvent(self, parent):
self.parent().enableWidgets()
def changeEvent(self, event):
if event.type() == QtCore.QEvent.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
self.parent().showMinimized()
else:
self.parent().showMaximized()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())
Link to PyQt Mailing List posts.

How can I get the previous activated widget in PyQt?

I have a button and two tablewidgets.Pressing the button does two different things depending on which one of the tablewidgets was activated before the push of the button.How can I get the right widget?
You could for example use the focusInEvent to store the activated widget and return it when pressing the button, something like this:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyTableWidget(QtGui.QTableWidget):
focusIn = QtCore.pyqtSignal(QtCore.QObject)
def __init__(self, parent=None):
super(MyTableWidget, self).__init__(parent)
def focusInEvent(self, event):
self.focusIn.emit(self)
return super(MyTableWidget, self).focusInEvent(event)
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.lastFocusedTableWidget = None
self.tableWidgetFirst = MyTableWidget(self)
self.tableWidgetFirst.setObjectName("tableWidgetFirst")
self.tableWidgetFirst.focusIn.connect(self.on_tableWidget_focusIn)
self.tableWidgetSecond = MyTableWidget(self)
self.tableWidgetSecond.setObjectName("tableWidgetSecond")
self.tableWidgetSecond.focusIn.connect(self.on_tableWidget_focusIn)
self.pushButtonLastFocused = QtGui.QPushButton(self)
self.pushButtonLastFocused.setText("Print the last focused QTableWidget!")
self.pushButtonLastFocused.clicked.connect(self.on_pushButtonLastFocused_clicked)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.tableWidgetFirst)
self.layoutVertical.addWidget(self.tableWidgetSecond)
self.layoutVertical.addWidget(self.pushButtonLastFocused)
#QtCore.pyqtSlot(QtCore.QObject)
def on_tableWidget_focusIn(self, obj):
self.lastFocusedTableWidget = obj
#QtCore.pyqtSlot()
def on_pushButtonLastFocused_clicked(self):
print self.lastFocusedTableWidget.objectName()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.resize(333, 111)
main.show()
sys.exit(app.exec_())

Categories