动态更新 PYQT 菜单项

Dynamically Updating a PYQT Menu Item

我使用以下构造构造了一个 Qmenu 项,其中包含最近打开的文件列表。我想实时更新菜单栏,这样我就不必等到下一次应用 运行 才更新菜单栏。

import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtGui import QPixmap


class Application(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)

        self.settings = {}
        self.settings['recent files'] = ['filename1', 'filename2', 'filename3']

        QtWidgets.QMainWindow.__init__(self)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.menu_bar()

    def menu_bar(self):
        file_menu = QtWidgets.QMenu('&File', self)
        self.recent = file_menu.addMenu('&Open Recent')

        for filename in self.settings['recent files']:
            self.recent.addAction(f'{filename}', lambda source=filename: self.dialog_open_file(file_name=source))

        self.menuBar().addMenu(file_menu)

    def dialog_open_file(self):
        
        self.settings['recent files'] = ['filename 4', 'filename1', 'filename2', 'filename3']

        self.update_recent_files()

        # do lots more stuff

    def update_recent_files(self):

        # Update list of recent files in Open Recent menubar  <---- 

qApp = QtWidgets.QApplication(sys.argv)
application_window = Application()
application_window.setWindowTitle(f"My App")
application_window.show()
sys.exit(qApp.exec_())

我的实际代码运行良好(实时更新菜单除外)。它将最近文件列表写入磁盘上的持久 json(示例代码不包括该位)。当用户打开一个新文件时,文件名被成功插入到最近文件列表中。下一次应用 运行,“最近的文件”菜单显示更新后的列表。

但是,我希望最近文件的菜单列表是动态的。我如何最好地调用实时重建“最近的文件”菜单?

EDIT - 我试图澄清这个问题以关注菜单项的动态更新而不是持续维护 文件列表。

如果您想永久保存信息,那么您必须将该信息保存在硬盘驱动器上(在文件中),python 和 Qt 中有很多选择,例如 QSettings。逻辑是在创建window时加载信息,并在需要时保存(例如当window关闭时)。

import sys
from functools import cached_property
from PyQt5 import QtCore, QtWidgets, QtGui


class Application(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Application, self).__init__(parent)

        QtWidgets.QMainWindow.__init__(self)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.create_menu_file()

        self.load_settings()

        button = QtWidgets.QPushButton("Load new files")
        button.clicked.connect(self.another_task)
        self.setCentralWidget(button)

    def create_menu_file(self):
        file_menu = QtWidgets.QMenu("&File", self)
        self.recentfiles_menu = file_menu.addMenu("&Open Recent")
        self.recentfiles_menu.triggered.connect(self.handle_triggered_recentfile)
        self.menuBar().addMenu(file_menu)

    @cached_property
    def settings(self):
        return QtCore.QSettings()

    def load_settings(self):
        filenames = self.settings.value("recent_files", [])
        for filename in filenames:
            self.add_recent_filename(filename)

    def save_settings(self):
        recentfiles = []
        for action in self.recentfiles_menu.actions()[::-1]:
            recentfiles.append(action.text())
        self.settings.setValue("recent_files", recentfiles)

    @QtCore.pyqtSlot(QtWidgets.QAction)
    def handle_triggered_recentfile(self, action):
        self.process_filename(action.text())

    def add_recent_filename(self, filename):
        action = QtWidgets.QAction(filename, self)
        actions = self.recentfiles_menu.actions()
        before_action = actions[0] if actions else None
        self.recentfiles_menu.insertAction(before_action, action)

    def process_filename(self, filename):
        print(filename)

    def closeEvent(self, event):
        super(Application, self).closeEvent(event)
        self.save_settings()

    def another_task(self):
        # DEMO
        # load new filenames
        counter = len(self.recentfiles_menu.actions())
        filenames = [f"foo {counter}"]
        for filename in filenames:
            self.add_recent_filename(filename)


qApp = QtWidgets.QApplication(sys.argv)
application_window = Application()
application_window.setWindowTitle(f"My App")
application_window.show()

sys.exit(qApp.exec_())

当菜单的内容是动态的时,我通常更喜欢使用 QMenu 的 aboutToShow 信号,它在即将打开之前被触发,并连接到一个清除它并更新它的函数它的内容。

我也用QAction.setData存储文件名,这样可以显示自定义的文本(比如路径太长,我们可以省略,或者只显示文件名而不是完整路径)。

class Application(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)

        file_menu = self.menuBar().addMenu('&File')
        self.recent_menu = file_menu.addMenu('&Open recent')

        self.recent_menu.aboutToShow.connect(self.update_recent_menu)
        self.recent_menu.triggered.connect(self.open_file_from_recent)
        self.settings = {}

    def update_recent_menu(self):
        self.recent_menu.clear()
        for row, filename in enumerate(self.get_recent_files(), 1):
            recent_action = self.recent_menu.addAction('&{}. {}'.format(
                row, filename))
            recent_action.setData(filename)

    def get_recent_files(self):
        recent = self.settings.get('recent files')
        if not recent:
            # just for testing purposes
            recent = self.settings['recent files'] = ['filename 4', 'filename1', 'filename2', 'filename3']
        return recent

    def open_file_from_recent(self, action):
        self.open_file(action.data())

    def open_file(self, filename):
        recent = self.get_recent_files()
        if filename in recent:
            recent.remove(filename)
        recent.insert(0, filename)
        
        print(filename)