Implement Popup mapping in QSystemTrayIcon - python

I want to implement behaviour of a popup-window exactly like default volume controller in windows 10. One click on icon, window opens or closes; if window is opened, clicking outside it will close the window. How can i implement this?
Before, i found that it is possible for the widget to override the methods for pressing and releasing the mouse keys, but here it was not found, or it was poorly searched. Help me please.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QGridLayout, \
QWidget, QSystemTrayIcon, QStyle, qApp
import PyQt5.QtCore
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(PyQt5.QtCore.QSize(600, 130))
self.setMaximumSize(PyQt5.QtCore.QSize(600, 130))
self.setWindowFlags(PyQt5.QtCore.Qt.Popup)
screen_geometry = QApplication.desktop().availableGeometry()
screen_size = (screen_geometry.width(), screen_geometry.height())
win_size = (self.frameSize().width(), self.frameSize().height())
x = screen_size[0] - win_size[0]
y = screen_size[1] - win_size[1]
self.move(x, y)
self.setWindowOpacity(0.85)
self.setWindowTitle("System Tray Application")
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
grid_layout = QGridLayout(central_widget)
grid_layout.addWidget(QLabel("Application, which can minimize to tray",
self), 0, 0)
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.
style().standardIcon(QStyle.SP_ComputerIcon))
self.tray_icon.activated.connect(self.trigger)
self.tray_icon.show()
self.setGeometry(500, 570, 600, 130)
self.fl = False
def trigger(self, reason):
if reason == QSystemTrayIcon.MiddleClick:
qApp.quit()
elif reason == QSystemTrayIcon.Trigger:
if not self.fl:
self.show()
else:
self.hide()
self.fl = not self.fl
elif reason == 1:
self.fl = False
# def mouseReleaseEvent(self, event):
# self.hide()
# self.fl = False
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec())

If you want to toggle visibility then you must use the setVisible() and isVisible() methods:
import sys
from PyQt5.QtWidgets import (
QApplication,
QGridLayout,
QLabel,
QMainWindow,
QStyle,
QSystemTrayIcon,
QWidget,
)
from PyQt5.QtCore import Qt, QSize
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setFixedSize(600, 130)
self.setWindowFlag(Qt.Popup)
self.setWindowOpacity(0.85)
self.setWindowTitle("System Tray Application")
central_widget = QWidget()
self.setCentralWidget(central_widget)
grid_layout = QGridLayout(central_widget)
grid_layout.addWidget(QLabel("Application, which can minimize to tray"), 0, 0)
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
self.tray_icon.activated.connect(self.trigger)
self.tray_icon.show()
self.setGeometry(
QStyle.alignedRect(
Qt.LeftToRight,
Qt.AlignBottom | Qt.AlignRight,
self.window().size(),
QApplication.desktop().availableGeometry(),
)
)
def trigger(self, reason):
if reason == QSystemTrayIcon.MiddleClick:
QApplication.quit()
elif reason == QSystemTrayIcon.Trigger:
self.setVisible(not self.isVisible())
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec())

Related

System tray application would detect hotkey with PyQt5 - QSystemTrayIcon

I want to create an application with a system tray icon. This app should detect the hotkey and react to it, even if the app is not focused (icon tray).
Example: I want to show the window, then I press Alt + X. The window is showing.
I tried using some global hotkey libraries but whenever the window is showing by hotkey, it freezes. The Show option in my QMenu still working.
I'm new to programming, thanks!
Here is my code:
from PyQt5.QtCore import QSize, Qt
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLineEdit, QVBoxLayout, QHBoxLayout, QWidget, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon, QKeySequence
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('GTyperP5')
self.setWindowIcon(QIcon('GTyperP5.ico'))
self.setFixedSize(QSize(192, 64))
self.setWindowFlag(Qt.WindowMinimizeButtonHint, False)
self.textInput = QLineEdit()
self.typeButton = QPushButton('Type')
self.typeButton.setFixedSize(QSize(50, 25))
self.typeButton.clicked.connect(self.cut_all)
self.textInput.returnPressed.connect(self.typeButton.click)
self.cancelButton = QPushButton('Cancel')
self.cancelButton.setFixedSize(QSize(50, 25))
self.cancelButton.clicked.connect(self.hide_window)
layout = QVBoxLayout()
layout.addWidget(self.textInput, alignment = Qt.AlignHCenter | Qt.AlignTop)
layout_ex = QHBoxLayout()
layout_ex.addWidget(self.cancelButton, alignment = Qt.AlignRight)
layout_ex.addWidget(self.typeButton, alignment = Qt.AlignLeft)
layout.addLayout(layout_ex)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
self.trayIcon = QSystemTrayIcon(QIcon('GTyperP5.ico'), parent = app)
self.trayIcon.setToolTip('GTyperP5')
menu = QMenu()
showAction = menu.addAction('Show')
showAction.triggered.connect(self.show_window)
exitAction = menu.addAction('Exit')
exitAction.triggered.connect(app.quit)
self.trayIcon.setContextMenu(menu)
self.trayIcon.show()
self.trayIcon.showMessage('GTyperP5 has started!', 'Thank you for using! :D', QIcon('GTyperP5.ico'), msecs = 10000)
self.trayIcon.activated.connect(self.doubleClick)
def closeEvent(self, event):
self.hide_window()
event.ignore()
def doubleClick(self, reason):
if reason == QSystemTrayIcon.DoubleClick:
self.show_window()
def cut_all(self):
clip = QApplication.clipboard()
clip.clear(mode = clip.Clipboard)
clip.setText(self.textInput.text(), mode = clip.Clipboard)
self.textInput.setText('')
self.hide_window()
def hide_window(self):
self.trayIcon.show()
self.hide()
def show_window(self):
self.trayIcon.hide()
self.show()
self.raise_()
self.textInput.setFocus()
app = QApplication([])
window = MainWindow()
window.show()
app.exec()

PyQt6 Isn't calling any events for hovering over a frame

My goal is to detect when a user hovers or stops hovering over a frame, but whenever I try to detect that with an eventFilter, there are just no events that get run that show that. The event IDs for hoverEnter, hoverLeave, and hoverMouseMove are 127, 128, and 129, but if you run the code, you'll see that they just don't come up. Here is the code that fails:
import sys
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Window")
self.resize(300, 200)
self.outerLayout = QHBoxLayout()
self.outerLayout.setContentsMargins(50, 50, 50, 50)
self.frame = QFrame()
self.frame.setStyleSheet("background-color: lightblue;")
self.innerLayout = QHBoxLayout(self.frame)
self.label = QLabel(self.frame)
self.label.setText("Example Frame")
self.innerLayout.addWidget(self.label)
self.outerLayout.addWidget(self.frame)
self.setLayout(self.outerLayout)
def eventFilter(self, obj, event):
if event.type() == 127:
print("hovered")
elif event.type() == 128:
print("no longer hovered")
elif event.type() == 129:
print("hover move event")
print(event.type())
return True
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainApp()
window.installEventFilter(window)
window.show()
sys.exit(app.exec())
My end goal here is to be able to detect when a QFrame is clicked. I was thinking I would try to do that by checking for mouse clicks, and if the mouse is hovering over the frame, trigger the function.
First of all it should be noted that clicked is not an event but a signal. The button clicked signal is emitted when the button receives the MouseButtonRelease event.
In this answer I will show at least the following methods to implement the clicked signal in the QFrame.
Override mouseReleaseEvent
import sys
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
class Frame(QFrame):
clicked = pyqtSignal()
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
self.clicked.emit()
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Window")
self.resize(300, 200)
self.outerLayout = QHBoxLayout(self)
self.outerLayout.setContentsMargins(50, 50, 50, 50)
self.frame = Frame()
self.frame.setStyleSheet("background-color: lightblue;")
self.label = QLabel(text="Example Frame")
self.innerLayout = QHBoxLayout(self.frame)
self.innerLayout.addWidget(self.label)
self.outerLayout.addWidget(self.frame)
self.frame.clicked.connect(self.handle_clicked)
#pyqtSlot()
def handle_clicked(self):
print("frame clicked")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec())
Use a eventFilter:
import sys
from PyQt6.QtCore import QEvent
from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Window")
self.resize(300, 200)
self.outerLayout = QHBoxLayout(self)
self.outerLayout.setContentsMargins(50, 50, 50, 50)
self.frame = QFrame()
self.frame.setStyleSheet("background-color: lightblue;")
self.label = QLabel(text="Example Frame")
self.innerLayout = QHBoxLayout(self.frame)
self.innerLayout.addWidget(self.label)
self.outerLayout.addWidget(self.frame)
self.frame.installEventFilter(self)
# for move mouse
# self.frame.setMouseTracking(True)
def eventFilter(self, obj, event):
if obj is self.frame:
if event.type() == QEvent.Type.MouseButtonPress:
print("press")
# for move mouse
# elif event.type() == QEvent.Type.MouseMove:
# print("move")
elif event.type() == QEvent.Type.MouseButtonRelease:
print("released")
return super().eventFilter(obj, event)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec())
Plus
A big part of the error of the O attempt is that by doing window.installEventFilter(window) it is only listening for events from the window itself and not from the QFrame. The solution is to send the QFrame events to the class window.frame.installEventFilter(window).
On the other hand, do not use numerical codes but the enumerations since they are more readable.
On the other hand, for the mouse event, the Qt::WA_Hover attribute must be enabled(Read the docs for more information)
import sys
from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Window")
self.resize(300, 200)
self.outerLayout = QHBoxLayout(self)
self.outerLayout.setContentsMargins(50, 50, 50, 50)
self.frame = QFrame()
self.frame.setStyleSheet("background-color: lightblue;")
self.label = QLabel(text="Example Frame")
self.innerLayout = QHBoxLayout(self.frame)
self.innerLayout.addWidget(self.label)
self.outerLayout.addWidget(self.frame)
self.frame.setAttribute(Qt.WidgetAttribute.WA_Hover)
self.frame.installEventFilter(self)
def eventFilter(self, obj, event):
if obj is self.frame:
if event.type() == QEvent.Type.HoverEnter:
print("enter")
elif event.type() == QEvent.Type.HoverMove:
print("move")
elif event.type() == QEvent.Type.HoverLeave:
print("leave")
return super().eventFilter(obj, event)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec())

PyQt: Multiple QGridLayout

How can I have multiple QGridLayouts on a single widget? I want to have one grid layout on the left and one on the right.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class FormWidget(QWidget):
def __init__(self):
super(FormWidget, self).__init__( )
self.grid = QGridLayout(self)
self.grid2 = QGridLayout(self)
self.grid.addWidget(self.grid2)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = FormWidget()
sys.exit(app.exec_())
If you want to place 2 layouts horizontally then you must use a QHBoxLayout:
import sys
from PyQt5.QtWidgets import QApplication, QGridLayout, QHBoxLayout, QWidget
class FormWidget(QWidget):
def __init__(self, parent=None):
super(FormWidget, self).__init__(parent)
left_grid_layout = QGridLayout()
right_grid_layout = QGridLayout()
lay = QHBoxLayout(self)
lay.addLayout(left_grid_layout)
lay.addLayout(right_grid_layout)
self.resize(640, 480)
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = FormWidget()
ex.show()
sys.exit(app.exec_())
Update:
If you want to set a weight you must set it in the stretch.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QGridLayout, QHBoxLayout, QTextEdit, QWidget
class FormWidget(QWidget):
def __init__(self, parent=None):
super(FormWidget, self).__init__(parent)
left_grid_layout = QGridLayout()
right_grid_layout = QGridLayout()
# for testing
left_grid_layout.addWidget(QTextEdit())
right_grid_layout.addWidget(QTextEdit())
lay = QHBoxLayout(self)
lay.addLayout(left_grid_layout, stretch=1)
lay.addLayout(right_grid_layout, stretch=2)
lay.setContentsMargins(
0, # left
100, # top
0, # right
100 # bottom
)
self.resize(640, 480)
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = FormWidget()
ex.show()
sys.exit(app.exec_())

Disable Carriage Return (Enter Key Press) in QPlainTextEdit()

This is what my example looks like:
The text area is a QPlainTextEdit() object because I want the text to wrap to the second line. I think this is the best widget choice.
The user will only enter a maximum number of 90 characters in this box so I don't need a large text area.
I want to disable the key press Enter (carriage return). I got it to work but it seems hacky and I don't think it would work cross-platform (ex: Mac).
Surely, there's got to be a better way to prevent a carriage return key event in a QPlainTextEdit object?
My Current Solution Explained
Below, you can see I'm checking if an IndexError occurs because the last_value throws an IndexError when there's nothing in the QPlainTextEdit box. Then, I'm getting the last character and asking if it's equal to a new line. If it is, I'm re-setting the text without that new line and moving the cursor to the end.
def some_event(self):
try:
last_value = self.field.toPlainText()[-1]
if last_value == '\n':
print('You Pressed Enter!', repr(last_value))
self.field.setPlainText(self.field.toPlainText()[:-1])
self.field.moveCursor(QTextCursor.End)
except IndexError:
print('Index Error occurred')
pass
Full Code Minimal Working Example:
from PyQt5.QtWidgets import (QWidget, QMainWindow, QGridLayout, QPushButton,
QApplication, QPlainTextEdit, QLabel)
from PyQt5.QtGui import QTextCursor
class BasicWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initWindow()
def initWindow(self):
self.setGeometry(400, 300, 400, 100)
self.grid = QGridLayout()
self.label = QLabel('Description Line 1')
self.grid.addWidget(self.label, 0, 0)
self.field = QPlainTextEdit()
self.field.setMaximumHeight(40)
self.field.textChanged.connect(self.some_event)
#TODO how to disable enter/return key events in this field?
self.grid.addWidget(self.field, 1, 0)
self.button = QPushButton('Some Button')
self.grid.addWidget(self.button)
self.centralWidget = QWidget()
self.centralWidget.setLayout(self.grid)
self.setCentralWidget(self.centralWidget)
def some_event(self):
try:
last_value = self.field.toPlainText()[-1]
if last_value == '\n':
print('You Pressed Enter!', repr(last_value))
self.field.setPlainText(self.field.toPlainText()[:-1])
self.field.moveCursor(QTextCursor.End)
except IndexError:
print('Index Error occurred')
pass
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = BasicWindow()
window.show()
sys.exit(app.exec_())
One option is to override the keyPressEvent method of the QPlainTextEdit:
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import (QWidget, QMainWindow, QGridLayout, QPushButton,
QApplication, QPlainTextEdit, QLabel)
class PlainTextEdit(QPlainTextEdit):
def keyPressEvent(self, event):
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
return
super().keyPressEvent(event)
class BasicWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initWindow()
def initWindow(self):
self.setGeometry(400, 300, 400, 100)
self.label = QLabel("Description Line 1")
self.field = PlainTextEdit()
self.field.setMaximumHeight(40)
self.button = QPushButton("Some Button")
self.centralWidget = QWidget()
grid = QGridLayout(self.centralWidget)
grid.addWidget(self.label, 0, 0)
grid.addWidget(self.field, 1, 0)
grid.addWidget(self.button)
self.setCentralWidget(self.centralWidget)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = BasicWindow()
window.show()
sys.exit(app.exec_())
Another option that implements the same logic is to use an eventFilter().
from PyQt5.QtCore import QEvent, Qt
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import (QWidget, QMainWindow, QGridLayout, QPushButton,
QApplication, QPlainTextEdit, QLabel)
class BasicWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initWindow()
def initWindow(self):
self.setGeometry(400, 300, 400, 100)
self.label = QLabel("Description Line 1")
self.field = QPlainTextEdit()
self.field.setMaximumHeight(40)
self.button = QPushButton("Some Button")
self.field.installEventFilter(self)
self.centralWidget = QWidget()
grid = QGridLayout(self.centralWidget)
grid.addWidget(self.label, 0, 0)
grid.addWidget(self.field, 1, 0)
grid.addWidget(self.button)
self.setCentralWidget(self.centralWidget)
def eventFilter(self, obj, event):
if obj is self.field and event.type() == QEvent.KeyPress:
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
return True
return super().eventFilter(obj, event)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = BasicWindow()
window.show()
sys.exit(app.exec_())

How to detect dialog's close event?

Hi everyone.
I am making a GUI application using python3.4, PyQt5 in windows 7.
Application is very sample. User clicks a main window's button, information dialog pops up. And when a user clicks information dialog's close button (window's X button), system shows confirm message. This is all.
Here's my code.
# coding: utf-8
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QLabel
class mainClass(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
openDlgBtn = QPushButton("openDlg", self)
openDlgBtn.clicked.connect(self.openChildDialog)
openDlgBtn.move(50, 50)
self.setGeometry(100, 100, 200, 200)
self.show()
def openChildDialog(self):
childDlg = QDialog(self)
childDlgLabel = QLabel("Child dialog", childDlg)
childDlg.resize(100, 100)
childDlg.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
mc = mainClass()
sys.exit(app.exec_())
Result screen shot is...
In this situation, I've added these code in mainClass class.
def closeEvent(self, event):
print("X is clicked")
This code works only when the main window is closed. But what I want is closeEvent function works when childDlg is to closed. Not main window.
What should I do?
You have added, the method closeEvent in the class mainClass.
So you have reimplemented the method closeEvent of your QMainwindow and not the method closeEvent of your childDlg. To do it, you have to subclass your chilDlg like this:
# coding: utf-8
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QLabel
class ChildDlg(QDialog):
def closeEvent(self, event):
print("X is clicked")
class mainClass(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
openDlgBtn = QPushButton("openDlg", self)
openDlgBtn.clicked.connect(self.openChildDialog)
openDlgBtn.move(50, 50)
self.setGeometry(100, 100, 200, 200)
self.show()
def openChildDialog(self):
childDlg = ChildDlg(self)
childDlgLabel = QLabel("Child dialog", childDlg)
childDlg.resize(100, 100)
childDlg.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
mc = mainClass()
sys.exit(app.exec_())
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QLabel
class mainClass(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
openDlgBtn = QPushButton("openDlg", self)
openDlgBtn.clicked.connect(self.openChildDialog)
openDlgBtn.move(50, 50)
self.setGeometry(100, 100, 200, 200)
self.show()
def openChildDialog(self):
childDlg = QDialog(self)
childDlgLabel = QLabel("Child dialog", childDlg)
childDlg.closeEvent = self.CloseEvent
childDlg.resize(100, 100)
childDlg.show()
def CloseEvent(self, event):
print("X is clicked")
if __name__ == "__main__":
app = QApplication(sys.argv)
mc = mainClass()
sys.exit(app.exec_())

Categories