day09——QMenu 与右键菜单实战!

🌞 欢迎来到 Day09:QMenu 与右键菜单实战

今天我们将学习如何在 PyQt6 中创建菜单栏、右键菜单(上下文菜单),并让它与表格或控件交互。
这是你从「界面搭建」迈向「交互逻辑」的关键一步。


🧠 今日目标

掌握以下三个核心概念:

  1. QMenu(菜单):可添加菜单项(Action),并绑定事件。

  2. QAction(动作):菜单项的基本单元,可设置文字、图标、快捷键。

  3. 右键菜单(Context Menu):用户在控件上点击右键时弹出的菜单。


一、基础示例:创建菜单栏

我们先从简单的菜单栏开始:

# QMenu 与右键菜单实战
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QMenu, QMessageBox)
from PyQt6.QtGui import QAction

class MenuExample(QMainWindow):
    """
    主窗口类,包含一个右键菜单
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("day09-菜单示例")
        self.setGeometry(300, 200, 400, 300)
        self.init_menu()

    def init_menu(self):
        """初始化右键菜单"""
        menubar = self.menuBar()
        # 创建文件菜单
        file_menu = menubar.addMenu("文件(&F)")

        # 添加动作项
        open_action = QAction("打开(&O)", self)
        save_action = QAction("保存(&S)", self)
        exit_action = QAction("退出(&X)", self)

        # 为菜单绑定事件
        open_action.triggered.connect(lambda: self.show_message("打开文件"))
        save_action.triggered.connect(lambda: self.show_message("保存文件"))
        exit_action.triggered.connect(self.close)

        # 添加到菜单
        file_menu.addAction(open_action)
        file_menu.addAction(save_action)
        file_menu.addSeparator()
        file_menu.addAction(exit_action)
        # 帮助菜单
        help_menu = menubar.addMenu("帮助(&H)")
        about_action = QAction("关于(&A)", self)
        help_menu.addAction(about_action)
        about_action.triggered.connect(lambda: self.show_message("这是一个菜单示例"))

    def show_message(self, message=""):
        """显示消息框"""
        QMessageBox.information(self, "提示", message)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MenuExample()
    window.show()
    sys.exit(app.exec())

效果如下:

imageimage

  • 菜单栏包含「文件」和「帮助」;

  • 菜单项可触发弹窗;

  • & 用于设置快捷键,如 Alt+F 打开“文件”菜单。


⚙️ 二、右键菜单(Context Menu)

菜单栏是顶部全局的,而右键菜单是局部的交互方式
我们可以给任意控件(比如表格、文本框)添加右键菜单。

下面以文本编辑框为例👇

# 右键菜单示例
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QMenu, QMessageBox, QTextEdit)
from PyQt6.QtGui import QAction
from PyQt6.QtCore import Qt

class ContextMenuExample(QMainWindow):
    """
    主窗口类,包含一个右键菜单
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("day09-右键菜单示例")
        self.setGeometry(300, 200, 400, 300)
        
        self.text_edit = QTextEdit(self)
        self.setCentralWidget(self.text_edit)

        self.text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.text_edit.customContextMenuRequested.connect(self.show_context_menu)

    def show_context_menu(self, pos):
        """显示右键菜单"""
        context_menu = QMenu(self)
        # 创建动作
        copy_action = QAction("复制", self)
        paste_action = QAction("粘贴", self)
        clear_action = QAction("清除", self)
        # 添加菜单项
        context_menu.addAction(copy_action)
        context_menu.addAction(paste_action)
        context_menu.addAction(clear_action)
        # 绑定行为
        copy_action.triggered.connect(self.text_edit.copy)
        paste_action.triggered.connect(self.text_edit.paste)
        clear_action.triggered.connect(self.text_edit.clear)

        # 显示菜单,在光标位置
        context_menu.exec(self.text_edit.mapToGlobal(pos))
        

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ContextMenuExample()
    window.show()
    sys.exit(app.exec())

效果如下:

image

🎯 效果:

  • 鼠标右击文本框;

  • 弹出“复制 / 粘贴 / 清空”菜单;

  • 可直接操作文本框内容。


🧩 三、进阶:右键操作 QTableWidget(实战)

我们回到前几天做的学生成绩表项目,
今天我们让用户在表格中右键点击,可以:

  • 删除当前行;

  • 编辑选中行;

  • 查看详细信息。

# 学生成绩管理系统
import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QPushButton, QVBoxLayout, QLabel, QTableWidgetItem, QAbstractItemView,
                             QLineEdit, QTextEdit, QMessageBox, QMenu, QGridLayout,QHBoxLayout,QTableWidget)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction

class StudentTable(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("学生成绩管理系统")
        self.setGeometry(300, 200, 650, 400)

        self.initUI()

    def initUI(self):
        layout = QVBoxLayout(self)

        # 表格部分
        self.table = QTableWidget(0, 3)
        self.table.setHorizontalHeaderLabels(["姓名","年龄","成绩"])
        self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
        self.table.setAlternatingRowColors(True)
        layout.addWidget(self.table)

        # 设置右键菜单策略
        self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.table.customContextMenuRequested.connect(self.show_context_menu)

        # 输入行,按钮部分
        input_layout = QHBoxLayout()
        self.name_input = QLineEdit()
        self.age_input = QLineEdit()
        self.score_input = QLineEdit()
        
        input_layout.addWidget(QLabel("姓名"))
        input_layout.addWidget(self.name_input)
        input_layout.addWidget(QLabel("年龄"))
        input_layout.addWidget(self.age_input)
        input_layout.addWidget(QLabel("成绩"))
        input_layout.addWidget(self.score_input)

        layout.addLayout(input_layout)

        # 按钮部分
        button_layout = QHBoxLayout()
        add_button = QPushButton("添加")
        add_button.clicked.connect(self.add_student)
        delete_button = QPushButton("删除")
        delete_button.clicked.connect(self.delete_student)
        avg_button = QPushButton("平均分")
        avg_button.clicked.connect(self.calculate_average)
        button_layout.addWidget(avg_button)
        button_layout.addWidget(add_button)
        button_layout.addWidget(delete_button)
        layout.addLayout(button_layout)

    def show_context_menu(self, pos):
        """显示右键菜单"""
        context_menu = QMenu(self)
        # 创建动作
        edit_action = QAction("编辑", self)
        delete_action = QAction("删除", self)
        info_action = QAction("查看信息", self)
        # 绑定事件
        edit_action.triggered.connect(self.edit_student)
        delete_action.triggered.connect(self.delete_student)
        info_action.triggered.connect(self.show_student_info)
        # 添加到菜单
        context_menu.addAction(edit_action)
        context_menu.addAction(delete_action)
        context_menu.addSeparator()
        context_menu.addAction(info_action)
        # 显示菜单,在光标位置
        context_menu.exec(self.table.mapToGlobal(pos))

    def add_student(self):
        """添加学生数据"""
        name = self.name_input.text().strip()
        age = self.age_input.text().strip()
        score = self.score_input.text().strip()

        if not name or not age or not score:
            QMessageBox.warning(self, "警告", "请填写完整信息")
            return

        try:
            age = int(age)
            score = float(score)
        except ValueError:
            QMessageBox.warning(self, "警告", "年龄和成绩必须是数字")
            return
        # 插入新行
        row = self.table.rowCount()
        self.table.insertRow(row)
        self.table.setItem(row, 0, QTableWidgetItem(name))
        self.table.setItem(row, 1, QTableWidgetItem(str(age)))
        self.table.setItem(row, 2, QTableWidgetItem(str(score)))

        # 清空输入
        self.name_input.clear()
        self.age_input.clear()
        self.score_input.clear()

    # 删除选中行
    def delete_student(self):
        """删除选中行"""
        # 获取选中的行索引
        select_items = self.table.selectedItems()
        if not select_items:
            QMessageBox.warning(self, "警告", "请选择要删除的行")
            return

        selected_rows = set()
        for item in select_items:
            selected_rows.add(item.row())
        for row in sorted(selected_rows, reverse=True):
            self.table.removeRow(row)
        QMessageBox.information(self, "信息", "删除成功")

    def edit_student(self):
        """编辑选中行"""
        # 获取选中的行索引
        select_item = self.table.currentItem()
        if select_item:
            select_item.setFlags(select_item.flags() | Qt.ItemFlag.ItemIsEditable)
            self.table.editItem(select_item)

    def show_student_info(self):
        """查看选中行信息"""
        # 获取选中的行索引
        select_item = self.table.currentItem()
        if select_item:
            name = self.table.item(select_item.row(), 0).text()
            age = self.table.item(select_item.row(), 1).text()
            score = self.table.item(select_item.row(), 2).text()
            QMessageBox.information(self, "信息", f"姓名: {name}\n年龄: {age}\n成绩: {score}")

    def calculate_average(self):
        """计算平均分"""
        total_score = 0
        num_students = self.table.rowCount()
        if num_students == 0:
            QMessageBox.warning(self, "警告", "没有学生成绩")
            return
        for i in range(num_students):
            total_score += float(self.table.item(i, 2).text())

        average_score = total_score / num_students
        QMessageBox.information(self, "信息", f"平均分: {average_score:.2f}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = StudentTable()
    window.show()
    sys.exit(app.exec())

效果演示:

image


🔍 四、总结与要点

概念 功能 关键方法
QMenu 菜单容器 .addAction() .exec()
QAction 菜单项 .triggered.connect()
右键菜单策略 控制是否允许右键菜单 .setContextMenuPolicy()
获取全局坐标 把局部位置转为全局 .mapToGlobal(pos)
posted @ 2025-10-27 17:36  毛豆*_*  阅读(127)  评论(0)    收藏  举报