Изменить всплывающее меню в окне Windows PyQt5
У меня есть небольшой графический интерфейс на PyQt5. При нажатии на иконку главного окна в левом верхнем углу открывается меню.
Я так понимаю, это стандартное меню для всех окон в Windows.
Я хочу переопределить это меню - добавить один пункт - "О программе", при нажатии на который будет открываться диалоговое окно с некоторой информацией.
Как я могу это сделать?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# конфигурация главного окна
self.central_widget = QWidget(self)
self.setGeometry(50, 50, 1500, 800)
self.setWindowTitle('Чтение БКУ')
self.setCentralWidget(self.central_widget)
self.layout_main_window = QVBoxLayout()
self.central_widget.setLayout(self.layout_main_window)
# конфигурация верхней панели инструментов
self.layout_toolbar_buttons = QHBoxLayout()
self.layout_toolbar_buttons.addStretch(1)
self.btn_start_reading = QPushButton("Считать")
self.btn_download_from_file = QPushButton("Загрузить из файла")
self.btn_save_to_file = QPushButton("Экспорт")
self.layout_toolbar_buttons.addWidget(self.btn_start_reading)
self.layout_toolbar_buttons.addWidget(self.btn_download_from_file)
self.layout_toolbar_buttons.addWidget(self.btn_save_to_file)
self.layout_main_window.addLayout(self.layout_toolbar_buttons)
# конфигурация таблицы
self.layout_table = QHBoxLayout()
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setRowCount(13500)
self.table.setHorizontalHeaderLabels(['№', 'Дата и время', 'БКУ', 'КЛ', 'АУ', 'Канал', 'Код события', 'Доп. параметр', 'Описание'])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setVisible(True)
for col in range(8):
self.table.setColumnWidth(col, 130)
self.layout_table.addWidget(self.table)
self.layout_main_window.addLayout(self.layout_table)
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
UPDATE:
У меня есть программа на С#, которая делает аналогичное действие. К сожалению, я не знаю С# и не знаю, насколько это поможет, но прикладываю участок кода, который, как мне кажется, отвечает за добавление нового пункта в меню, всплывающее при нажатии на иконку.
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND)
{
switch (msg.WParam.ToInt32())
{
case MYMENU1:
AboutProgram aboutProgram=new AboutProgram();
aboutProgram.ShowDialog();
break;
default:
break;
}
}
base.WndProc(ref msg);
}
private void AddMainContext()
{
IntPtr MenuHandle = GetSystemMenu(this.Handle, false);
InsertMenu(MenuHandle, 5, MF_BYPOSITION, MYMENU1, "О программе");
}
Ответы (5 шт):
Можно переопределить системное меню при помощи setWindowFlag():
self.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
help_menu = self.menuBar().addMenu('Справка')
about_action = QAction('О программе', self)
about_action.triggered.connect(self.show_about_dialog)
help_menu.addAction(about_action)
Ну и соответственно окошко:
def show_about_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle('О программе')
layout = QVBoxLayout(dialog)
label = QLabel('Бла бла бла')
layout.addWidget(label)
button = QPushButton('OK')
button.clicked.connect(dialog.accept)
layout.addWidget(button)
dialog.exec_()
Update
Попробуйте добавить:
self.windowIconChanged.connect(self.handle_window_icon_changed)
и создать:
def handle_window_icon_changed(self):
if self.windowIcon():
self.setWindowFlag(Qt.WindowContextHelpButtonHint, True)
else:
self.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
Перевод оригинального ответа Stack Overflow для русскоязычного сообщества с небольшим комментарием от меня.
Это меню не определяется — оно действительно стандартно для всех окон и предполагает основные действия с расположением окна и его статусом. В вашем случае подойдёт меню сверху, реализуемое с помощью компонента QMenuBar.
.
Чтобы создать кнопку в панели меню, нужно задать ей название. Кроме того, кнопки и действия принадлежат не Qt, поэтому вам нужно задать родительский объект для них или сохранить их куда-то, чтобы они не были удалены сборщиком мусора. Вот пример:
class MainWindow(QMainWindow):
def __init__(self):
menubar = self.menuBar() # создаём меню
menu = QMenu('Справка', self) # заголовок и родительский объект
action = QAction('О программе', self) # title and parent
action.triggered.connect(self.do_something) # привязка к функции при нажатии на кнопку
menu.addAction(action)
menubar.addMenu(menu)
И есть и более простой способ добиться того же результата, добавляя меню и действия непосредственно через методы объектов, примерно так:
menubar = self.menuBar()
menu = menubar.addMenu('Справка')
action = menu.addAction("О программе")
action.triggered.connect(self.do_something)
Чтобы добавить пункт о программе в PyQt5, вы можно заюзать метод menuBar() объекта QMainWindow. Сначала задаем экземпляр класса QAction, который будет отвечать за пункт меню о программе, затем добавляем его к объекту QMenu, а затем добавим этот QMenu к объекту menuBar(). По коду примерно так, но надо будет уже доделать под себя
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# ... Ваш код ...
# добавляем меню
menubar = self.menuBar()
about_action = QAction("О программе", self)
about_action.triggered.connect(self.about_program)
file_menu = menubar.addMenu("&Файл")
file_menu.addAction(about_action)
def about_program(self):
# здесь может быть код для отображения диалогового окна с информацией о программе
pass
Для windows можно так реализовать:
...
import win32gui
import win32.lib.win32con as win32con
...
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.custom_menu_id = 6
hwnd = self.winId()
hmenu = win32gui.GetSystemMenu(hwnd, False)
win32gui.AppendMenu(hmenu, win32con.MF_SEPARATOR, 5, '')
win32gui.AppendMenu(hmenu, win32con.MF_STRING, self.custom_menu_id, 'Custom menu text')
...
def nativeEvent(self, eventType, message):
retval, result = super(MainWindow, self).nativeEvent(eventType, message)
# handle the click on the title bar
try:
msg = ctypes.wintypes.MSG.from_address(message.__int__())
except:
msg = None
if eventType == "windows_generic_MSG" and msg is not None:
if msg.message == WM_NCLBUTTONDOWN:
# bonus - here's how to handle the left click
pass
if msg.message == WM_SYSCOMMAND:
if msg.wParam == self.custom_menu_id:
# do me now!
return True, 0
return retval, result
Я покажу вам, один из вариантов, как создать пользовательскую панель заголовка. а выглядит это примерно так:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *
class Ui_SecondWindow(object):
def setupUi(self, second_window):
second_window.setObjectName("MainWindow")
second_window.setEnabled(True)
second_window.resize(320, 240)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(second_window.sizePolicy().hasHeightForWidth())
second_window.setSizePolicy(sizePolicy)
second_window.setMinimumSize(QtCore.QSize(320, 240))
second_window.setMaximumSize(QtCore.QSize(320, 240))
font = QtGui.QFont()
font.setFamily("Verdana")
second_window.setFont(font)
second_window.setStyleSheet("")
second_window.setTabShape(QtWidgets.QTabWidget.Triangular)
self.centralwidget = QtWidgets.QWidget(second_window)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.label.setTextFormat(QtCore.Qt.AutoText)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setWordWrap(True)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
second_window.setCentralWidget(self.centralwidget)
self.retranslateUi(second_window)
QtCore.QMetaObject.connectSlotsByName(second_window)
def retranslateUi(self, second_window):
_translate = QtCore.QCoreApplication.translate
second_window.setWindowTitle(_translate("MainWindow", "About"))
self.label.setText(_translate("MainWindow",
"""
<html><head/><body>
<p>
<span style=\" font-size:11pt; font-style:italic;
color:#000000;\">Версия приложения 0.1. Находится в тестировании,
может использоваться в качестве готовой версии приложения.
</span>
</p>
<p align=\"center\">
<img src=\"boy4.png\"/>
</p>
<p align=\"center\">
<span style=\" font-size:11pt; font-style:italic;
color:#000000;\">Copyright ©
</span>
<a href=\"https://ru.stackoverflow.com/users/539485/\">
<span style=\" font-size:11pt; font-weight:600;
text-decoration: underline; color:#0000ff;\">Игорь Ищенко
</span>
</a><br>
<a href=\"https://ru.stackoverflow.com/questions/1496443/\">
<span style=\" font-size:11pt; font-weight:600;
color:#ff0000;\">???
</span>
</a>
<span style=\" font-size:11pt; font-style:italic;
color:#000000;\">. ← Работу можно заказать по этому адресу.
</span>
</p>
</body></html>
"""))
class SecondWindow(QtWidgets.QMainWindow, Ui_SecondWindow):
def __init__(self, parent=None):
super(SecondWindow, self).__init__(parent)
self.setupUi(self)
class Label(QLabel):
clicked = pyqtSignal(object)
def __init__(self, parent=None):
super(Label, self).__init__(parent)
self._pos = QPoint(0, 0)
def mousePressEvent(self, event):
super(Label, self).mousePressEvent(event)
self.clicked.emit(self.mapToGlobal(event.pos()) + QPoint(15, 15))
class TitleBar(QWidget):
def __init__(self, parent):
super(TitleBar, self).__init__()
self.parent = parent
self.initUI()
def initUI(self):
self.frameTitle = QFrame()
self.frameTitle.setFrameStyle(QFrame.NoFrame)
self.frameTitle.setFixedHeight(30)
self.frameTitle.setAutoFillBackground(True)
colorFrame = self.palette()
colorFrame.setColor(QPalette.Background, Qt.yellow)
self.frameTitle.setPalette(colorFrame)
self.labelIcon = Label(self)
self.labelIcon.setPixmap(QPixmap("icono.png").scaled(
18, 18,
Qt.KeepAspectRatio, Qt.SmoothTransformation)
)
self.labelIcon.setAlignment(Qt.AlignCenter)
self.labelIcon.setToolTip("Icon")
self.labelIcon.setFixedWidth(32)
self.labelIcon.setFixedHeight(24)
self.labelTitle = QLabel("Чтение БКУ")
self.labelTitle.setAlignment(Qt.AlignCenter)
font_title = self.font()
font_title.setPointSize(14)
self.labelTitle.setFont(font_title)
self.labelTitle.setToolTip("Hазвание")
buttonMinimize = QToolButton()
buttonMinimize.setToolTip("Minimize")
buttonMinimize.setIconSize(QSize(38, 25))
buttonMinimize.setAutoRaise(True)
buttonMinimize.setIcon(
QApplication.style().standardIcon(QStyle.SP_TitleBarMinButton))
buttonMinimize.clicked.connect(self._minimize)
self.buttonMaxRes = QToolButton()
self.buttonMaxRes.setToolTip("Maximizar")
self.buttonMaxRes.setIconSize(QSize(38, 25))
self.buttonMaxRes.setAutoRaise(True)
self.buttonMaxRes.setIcon(
QApplication.style().standardIcon(QStyle.SP_TitleBarMaxButton))
self.buttonMaxRes.clicked.connect(self._maximize_restore)
buttonClose = QToolButton()
buttonClose.setToolTip("Закрыть")
buttonClose.setIconSize(QSize(38, 25))
buttonClose.setAutoRaise(True)
buttonClose.setIcon(
QApplication.style().standardIcon(QStyle.SP_TitleBarCloseButton))
buttonClose.clicked.connect(self._close)
layoutH = QHBoxLayout(self.frameTitle)
layoutH.setSpacing(0)
layoutH.addWidget(self.labelIcon)
layoutH.addWidget(self.labelTitle)
layoutH.addWidget(buttonMinimize)
layoutH.addWidget(self.buttonMaxRes)
layoutH.addWidget(buttonClose)
layoutH.setContentsMargins(0, 0, 0, 0)
main_layoutH = QHBoxLayout(self)
main_layoutH.addWidget(self.frameTitle)
main_layoutH.setContentsMargins(0, 0, 0, 0)
self.start = QPoint(0, 0)
self.pressing = False
def resizeEvent(self, event):
super(TitleBar, self).resizeEvent(event)
self.frameTitle.setFixedWidth(self.parent.width())
self.labelTitle.setFixedWidth(self.parent.width() - 62)
def mousePressEvent(self, event):
self.start = self.mapToGlobal(event.pos())
self.pressing = True
def mouseMoveEvent(self, event):
if self.pressing:
self.end = self.mapToGlobal(event.pos())
self.movement = self.end - self.start
self.parent.setGeometry(
self.mapToGlobal(self.movement).x(),
self.mapToGlobal(self.movement).y(),
self.parent.width(), self.parent.height()
)
self.start = self.end
def mouseReleaseEvent(self, QMouseEvent):
self.pressing = False
def mouseDoubleClickEvent(self, event):
if event.buttons() == Qt.LeftButton:
if self.parent.isMaximized():
self.parent.showNormal()
self.buttonMaxRes.setIcon(
QApplication.style().standardIcon(
QStyle.SP_TitleBarMaxButton)
)
self.buttonMaxRes.setToolTip("Maximizar")
else:
self.parent.showMaximized()
self.buttonMaxRes.setIcon(
QApplication.style().standardIcon(
QStyle.SP_TitleBarNormalButton)
)
self.buttonMaxRes.setToolTip("Restaurar")
def _minimize(self):
self.parent.showMinimized()
def _maximize_restore(self):
if self.parent.isMaximized():
self.parent.showNormal()
self.buttonMaxRes.setIcon(QApplication.style().standardIcon(QStyle.SP_TitleBarMaxButton))
self.buttonMaxRes.setToolTip("Увеличить")
else:
self.parent.showMaximized()
self.buttonMaxRes.setIcon(QApplication.style().standardIcon(QStyle.SP_TitleBarNormalButton))
self.buttonMaxRes.setToolTip("Восстановление")
def _close(self):
self.parent.close()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.central_widget = QWidget(self)
self.setCentralWidget(self.central_widget)
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowSystemMenuHint) # !!!
self.setMinimumSize(800, 400)
self.pressing = False
self.titleBar = TitleBar(self)
self.titleBar.labelIcon.clicked.connect(self.labelIcon_clicked)
self.layout_main_window = QVBoxLayout(self.central_widget)
self.layout_main_window.addWidget(self.titleBar)
self.layout_main_window.setContentsMargins(0, 0, 0, 0)
self.initUI()
self.table.setFocus()
self.context_menu = QMenu(self)
self.init_menu()
def init_menu(self):
self.context_menu.setAttribute(Qt.WA_TranslucentBackground)
self.context_menu.setWindowFlags(
self.context_menu.windowFlags() |
Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
action = self.context_menu.addAction('меню about', self.create_window)
action = self.context_menu.addAction(
QIcon("img/readMe.png"), '&About', self.about_qt)
self.context_menu.addSeparator()
action = self.context_menu.addAction(
QIcon("img/exit.png"), "&Quit", self.close)
def labelIcon_clicked(self, pos):
self.context_menu.exec_(pos)
def initUI(self):
# конфигурация верхней панели инструментов
self.layout_toolbar_buttons = QHBoxLayout()
self.layout_toolbar_buttons.addStretch(1)
self.btn_start_reading = QPushButton("Считать")
self.btn_download_from_file = QPushButton("Загрузить из файла")
self.btn_save_to_file = QPushButton("Экспорт")
self.layout_toolbar_buttons.addWidget(self.btn_start_reading)
self.layout_toolbar_buttons.addWidget(self.btn_download_from_file)
self.layout_toolbar_buttons.addWidget(self.btn_save_to_file)
self.layout_main_window.addLayout(self.layout_toolbar_buttons)
# конфигурация таблицы
self.layout_table = QHBoxLayout()
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setRowCount(13500)
self.table.setHorizontalHeaderLabels(
['№', 'Дата и время', 'БКУ', 'КЛ', 'АУ', 'Канал',
'Код события', 'Доп. параметр', 'Описание'])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setVisible(True)
for col in range(8):
self.table.setColumnWidth(col, 130)
self.layout_table.addWidget(self.table)
self.layout_main_window.addLayout(self.layout_table)
def about_qt(self):
# О Qt
QApplication.instance().aboutQt()
def create_window(self):
self.second_window = SecondWindow(self)
self.second_window.show()
Style = """
QMenu {
background-color: rgba(64, 224, 208, 220);
border: 1px;
border-radius: 4px;
}
QMenu::item {
border-radius: 4px;
padding: 8px 48px 8px 36px;
background-color: transparent;
}
QMenu::item:selected {
border-radius: 0px;
background-color: rgba(32, 178, 170, 232);
}
QMenu::item:disabled {
background-color: transparent;
}
QMenu::icon {
left: 15px;
}
QMenu::separator {
height: 2px;
background-color: rgb(232, 236, 255);
}
"""
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
app.setStyleSheet(Style)
w = MainWindow()
w.show()
sys.exit(app.exec())



