漫谈QWidget及其派生类(菜单、菜单栏)

本文详细解析了QMenuBar和QMenu在Qt框架中的功能和使用方法,包括如何添加操作项、处理鼠标事件以及展示上下文菜单。重点讨论了两者之间的不同之处以及在实际开发中的应用技巧。
  • QMenu和QMenuBar都是QWidget的派生类,按理说也没有什么特别的。只是真的想理清它,似乎还真的不是那么简单...

QMenuBar

漫谈QWidget及其派生类(三)中,我们已经知道:QMenuBar和QStatusBar都是QWidget的派生类,所以我们可以像添加一个QPushButton一样将它们任意放置到另一个QWidget中。

继续看个别的例子:

#include <QtGui>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QMenuBar menubar;
    menubar.addAction("Action1");
    menubar.addAction("Action1");
    menubar.show();

    return app.exec();
}

我们知道,QWidget本来就有一个成员函数:

void QWidget::addAction ( QAction * action )

而此处只不过是QMenuBar提供的重载版本:

QAction *QMenuBar::addAction(const QString &text)
{
    QAction *ret = new QAction(text, this);
    addAction(ret);
    return ret;
}

可是同样的东西,到了QMenuBar中,就显示出一种菜单栏的效果了呢?

不同的Widget,为什么不一样?我们都会自定义控件,所以都熟悉:

void QMenuBar::paintEvent(QPaintEvent *e)
{
...
    for (int i = 0; i < d->actions.count(); ++i) {
        QAction *action = d->actions.at(i);
        QRect adjustedActionRect = d->actionRect(action);
...

便利它自身的actions列表,然后依次将它们paint出来。

然后当触发快捷键,或者在某个区域点击鼠标时:

void QMenuBar::mousePressEvent(QMouseEvent *e)
{
...
    QAction *action = d->actionAt(e->pos());
....
    d->setCurrentAction(action, true);
...

addMenu()

在继续之前,我们需要简单了解一下addAction和addMenu

QMenu *QMenuBar::addMenu(const QString &title)
{
    QMenu *menu = new QMenu(title, this);
    addAction(menu->menuAction());
    return menu;
}

看,无非是创建一个QMenu,然后将该菜单对应的QAction添加进来了。而这个QMenu,稍候可以通过对应的QAction的成员函数

QMenu * QAction::menu () const

来获得

先看看QMenu,稍候再回来

QMenu

QMenu 也是一个QWidget的派生类

  • 一个规规矩矩的矩形窗口(窗口类别 Qt::Popup)
  • 通过setVisible(true)或show()使其显示出来

但是,为什么QMenu长成这个样子呢,一条一条的?

其实看了前面的QMenuBar,我们对QMenu为什么长这个样子,应该不会感到任何惊奇了。

先添加一些QAction,然后在paintEvent中遍历Action列表,并画出一个一个矩形条。

void QMenu::paintEvent(QPaintEvent *e)
{
    //draw the items that need updating..
    for (int i = 0; i < d->actions.count(); ++i) {
        QAction *action = d->actions.at(i);
...

当然,为了有点立体效果,或者说为了让QMenu和其他东西分开,还需要画边框。

上下文菜单

我们在一个Widget的某个位置上点击右键,或者通过键盘上某个键。会弹出上下文菜单。

这个菜单和我们在菜单栏看到的菜单其实没有区别,要说区别的话,就是需要我们自己:

  • 设置菜单的位置
  • 并让菜单显示出来

考虑:一般我们是如何使用上下文菜单的?

void Widget::contextMenuEvent(QContextMenuEvent *event)
{
  QMenu menu(this);
  menu.addAction("A1");
  menu.addAction("A2");
  
  filemenu->exec(this->mapToGlobal(event.pos())); 
  • 创建菜单
  • 添加action
  • 显示菜单

如何显示?

  • 记得刚接触这个东西时,在这个地方都是犯糊涂。这儿为什么不能直接用event中的位置,而是要map到全局坐标。

还是要从QMenu是QWidget的派生类说起。在前面我们提到了这样的内容:

  • QWidget分:窗口(Window)和部件(Widget)两类。前者的位置是全局的或者说相对屏幕的,后者坐标是相对父部件的

QMenu 是 Window,所以位置坐标是全局的。而event坐标是当前窗口部件的。

  • 显示一个QWidget,我们必须调用setVisible(true)/show()才行!!这儿用的确实exec(),我们类比QDialog的话,很容易才到答案。

其内部肯定调用了show(),而且还使用QEventLoop开启了局部的事件循环。

popup()

既然直接调用show()就可以让菜单显示出来,这个popup()又有何用?

void QMenu::popup(const QPoint &p, QAction *atAction)
{
...
}

它做的工作:

  • 确保样式和字体被正确设置
  • 调整几何尺寸(位置和大小)
  • 调用show() 显示

而我们刚提到的exec()就是在popup的基础上加了个事件循环

QAction *QMenu::exec(const QPoint &p, QAction *action)
{
    QEventLoop eventLoop;
    d->eventLoop = &eventLoop;
    popup(p, action);
...

回到菜单栏

了解了上下文菜单,我们可以回头看看菜单栏是如何控制菜单显示的。

一开始说了,

  • 菜单对应的action被加入到了菜单栏
  • 菜单栏在paintEvent绘制我们熟悉的那一栏东西
  • 当我们点击鼠标时,菜单栏mousePressEvent中判断位置对应哪个action
  • 这样就到了...

void QMenuBarPrivate::popupAction(QAction *action, bool activateFirst)
{
    if(!action || !action->menu() || losePopupMode)
        return;

    activeMenu = action->menu();
    ....
    activeMenu->popup(pos);

源码中,最长的一段就是在计算和调整菜单的位置(以确保菜单不会跑到屏幕外),即那个pos


<think>我们参考了提供的引用资料,特别是引用[2]和引用[3]中关于右键菜单的实现。在PyQt5中,为QWidget添加右键菜单通常有两种方法: 1. 重写contextMenuEvent方法(如引用[2]所示) 2. 使用setContextMenuPolicy和自定义菜单(如引用[3]中提到的方式) 下面我们将分别介绍这两种方法。 ### 方法一:重写contextMenuEvent事件处理器 我们可以通过重写QWidget的contextMenuEvent事件来创建右键菜单。当用户右键点击窗口部件时,该事件会被触发。 步骤: 1. 创建一个QMenu对象。 2. 向菜单中添加动作(QAction)。 3. 使用事件对象提供的全局坐标(globalPos)来显示菜单。 示例代码(基于引用[2]的示例修改): ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QMenu, QAction class MyWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle('右键菜单示例') self.resize(400, 300) def contextMenuEvent(self, event): # 创建右键菜单 context_menu = QMenu(self) # 添加菜单项 action1 = context_menu.addAction('动作1') action2 = context_menu.addAction('动作2') # 添加分割线 context_menu.addSeparator() action3 = context_menu.addAction('动作3') # 将动作与槽函数连接(这里用lambda演示,也可以单独定义槽函数) action1.triggered.connect(lambda: print('执行动作1')) action2.triggered.connect(lambda: print('执行动作2')) action3.triggered.connect(lambda: print('执行动作3')) # 在鼠标位置显示菜单 context_menu.exec_(event.globalPos()) if __name__ == '__main__': app = QApplication(sys.argv) window = MyWidget() window.show() sys.exit(app.exec_()) ``` ### 方法二:使用setContextMenuPolicy和自定义菜单 我们也可以使用setContextMenuPolicy方法将上下文菜单策略设置为Qt.CustomContextMenu,然后连接customContextMenuRequested信号。 步骤: 1. 设置上下文菜单策略:self.setContextMenuPolicy(Qt.CustomContextMenu) 2. 连接customContextMenuRequested信号到一个槽函数。 3. 在槽函数中创建菜单并显示。 示例代码: ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QMenu, QAction from PyQt5.QtCore import Qt class MyWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle('右键菜单示例') self.resize(400, 300) # 设置上下文菜单策略 self.setContextMenuPolicy(Qt.CustomContextMenu) # 连接信号 self.customContextMenuRequested.connect(self.show_context_menu) def show_context_menu(self, pos): # 创建菜单 context_menu = QMenu(self) action1 = context_menu.addAction('动作1') action2 = context_menu.addAction('动作2') context_menu.addSeparator() action3 = context_menu.addAction('动作3') action1.triggered.connect(lambda: print('执行动作1')) action2.triggered.connect(lambda: print('执行动作2')) action3.triggered.connect(lambda: print('执行动作3')) # 将鼠标点击的位置转换为全局坐标 global_pos = self.mapToGlobal(pos) context_menu.exec_(global_pos) if __name__ == '__main__': app = QApplication(sys.argv) window = MyWidget() window.show() sys.exit(app.exec_()) ``` ### 两种方法的比较 1. 重写contextMenuEvent方法更直接,适用于只需要在特定部件上添加右键菜单的情况。 2. 使用信号和槽的方式更灵活,特别是当需要为多个部件设置不同的右键菜单时,可以通过判断信号发送者来创建不同的菜单。 根据需求选择合适的方法即可。 §§相关问题§§ 1. PyQt5中如何为不同的子部件设置不同的右键菜单? 2. 如何禁用QWidget的默认右键菜单? 3. 在PyQt5中,如何让右键菜单中的动作执行特定的操作(如修改窗口状态)?</think>### PyQt5 实现 QWidget 右键菜单栏 在 PyQt5 中为 `QWidget` 添加右键菜单栏有两种常用方法,下面我将详细说明两种实现方式: #### 方法一:重写 contextMenuEvent 方法(推荐) 这是最直接的方式,通过重写控件的 `contextMenuEvent` 事件处理器: ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QMenu, QAction class MyWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("右键菜单示例") self.resize(400, 300) def contextMenuEvent(self, event): # 创建右键菜单 context_menu = QMenu(self) # 添加菜单项 action_copy = context_menu.addAction("复制") action_paste = context_menu.addAction("粘贴") context_menu.addSeparator() # 添加分隔线 action_settings = context_menu.addAction("设置") # 连接菜单项信号 action_copy.triggered.connect(self.copy_action) action_paste.triggered.connect(self.paste_action) action_settings.triggered.connect(self.settings_action) # 在鼠标位置显示菜单 context_menu.exec_(event.globalPos()) def copy_action(self): print("执行复制操作") def paste_action(self): print("执行粘贴操作") def settings_action(self): print("打开设置") if __name__ == "__main__": app = QApplication(sys.argv) window = MyWidget() window.show() sys.exit(app.exec_()) ``` #### 方法二:使用 setContextMenuPolicy 和信号槽 通过设置控件的上下文菜单策略并连接信号: ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QMenu, QAction from PyQt5.QtCore import Qt class MyWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("右键菜单示例") self.resize(400, 300) # 设置上下文菜单策略 self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) def show_context_menu(self, pos): # 创建右键菜单 context_menu = QMenu(self) # 添加菜单项 action_new = context_menu.addAction("新建") action_open = context_menu.addAction("打开") context_menu.addSeparator() # 添加子菜单 export_menu = context_menu.addMenu("导出") export_menu.addAction("导出为PDF") export_menu.addAction("导出为PNG") # 显示菜单 context_menu.exec_(self.mapToGlobal(pos)) if __name__ == "__main__": app = QApplication(sys.argv) window = MyWidget() window.show() sys.exit(app.exec_()) ``` #### 关键点说明: 1. **菜单位置**: - 使用 `event.globalPos()`(方法一)或 `self.mapToGlobal(pos)`(方法二)获取鼠标的全局坐标 - 这是确保菜单在正确位置显示的关键 2. **菜单项类型**: - 普通动作:`addAction("文本")` - 分隔线:`addSeparator()` - 子菜单:`addMenu("文本")` 3. **连接功能**: - 使用 `triggered` 信号连接槽函数 - 示例:`action.triggered.connect(self.your_function)` 4. **策略选择**: - 方法一更直接,适合简单场景 - 方法二更灵活,适合需要动态生成菜单的场景 #### 高级技巧: 1. **添加图标**: ```python action = QAction(QIcon("icon.png"), "带图标的动作", self) menu.addAction(action) ``` 2. **禁用菜单项**: ```python action_open.setEnabled(False) # 禁用菜单项 ``` 3. **动态菜单**: ```python def show_context_menu(self, pos): menu = QMenu() # 根据应用状态动态添加项目 if self.has_selection: menu.addAction("剪切") menu.addAction("复制") menu.exec_(self.mapToGlobal(pos)) ``` [^1]: 在 PyQt5 中,`contextMenuEvent` 是处理右键菜单的核心事件处理器 [^2]: 使用 `setContextMenuPolicy(Qt.CustomContextMenu)` 可以启用自定义上下文菜单策略 [^3]: 菜单项的动作通过 `QAction` 对象创建并添加到菜单
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值