【问题标题】:Removing dynamically created Qmenu items删除动态创建的 Qmenu 项
【发布时间】:2018-12-22 08:17:20
【问题描述】:

我有一个 Qmenu,我通过使用 Qsettings 加载列表来创建它,并且我试图通过在 QListQWidget 中加载列表并删除所选项目来从菜单中删除项目。目前,我能够删除列表小部件中的菜单项,并且它也将它们从 qsettings 中删除,但我无法弄清楚如何在不重新启动的情况下从菜单中删除这些项目。我已经尝试了各种方法,例如 removeAction 等,但无法弄清楚。
这是我的代码:

import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
    QApplication, QAction, QMenu, QListWidgetItem, \
    QListWidget, QGridLayout

class MainWindow(QWidget):
    settings = QtCore.QSettings('test_org', 'my_app')
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.layout = QHBoxLayout()
        self.menu_btn = QPushButton()
        self.menu = QMenu()
        self.add_menu = self.menu.addMenu("Menu")
        self.menu_btn.setMenu(self.menu)
        self.open_list_btn = QPushButton('open list')

        self.load_items = self.settings.value('menu_items', [])
        for item in self.load_items:
            self.action = QAction(item[0], self)
            self.action.setData(item)
            self.add_menu.addAction(self.action)
            self.action.triggered.connect(functools.partial(self.menu_clicked, self.action))

        self.layout.addWidget(self.menu_btn)
        self.layout.addWidget(self.open_list_btn)
        self.setLayout(self.layout)
        self.open_list_btn.clicked.connect(self.open_window)

    def open_window(self):
        self.create_menu_item = List()
        self.create_menu_item.show()

    def menu_clicked(self, item):
        itmData = item.data()
        print(itmData)

class List(QWidget):
    settings = QtCore.QSettings('test_org', 'my_app')
    def __init__(self, parent=None):
        super(List, self).__init__(parent)
        self.menu_items = self.settings.value('menu_items', [])
        self.layout = QGridLayout()
        self.list = QListWidget()
        self.remove_btn = QPushButton('Remove')
        self.layout.addWidget(self.list, 1, 1, 1, 1)
        self.layout.addWidget(self.remove_btn, 2, 1, 1, 1)
        self.setLayout(self.layout)
        self.remove_btn.clicked.connect(self.remove_items)

        for item in self.menu_items:
            self.item = QListWidgetItem()
            self.item.setText(str(item[0]))
            self.list.addItem(self.item)

    def remove_items(self):
        self.menu_items = self.settings.value('menu_items', [])
        del self.menu_items[self.list.currentRow()]
        self.settings.setValue('menu_items', self.menu_items)
        listItems = self.list.selectedItems()
        if not listItems: return
        for item in listItems:
            self.list.takeItem(self.list.row(item))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec_()

有人有什么想法吗?

编辑:

这是 QSettings 中列表的结构。我用这个加载菜单,我用这个加载 QlistWidget。当我为 QListWidget 删除项目时,我试图让菜单也删除它们。

mylist = ['item_name',['itemdata1', 'itemdata2', 'itemdata3'], 
          'item_name2',['itemdata1', 'itemdata2', 'itemdata3'], 
          'item_name3',['itemdata1', 'itemdata2', 'itemdata3']]

【问题讨论】:

  • 你可以显示配置文件
  • 我目前只是使用 qsettings 手动将列​​表添加到注册表中。它们看起来像这样 mylist = ['item_name',['itemdata1', 'itemdata2', 'itemdata3']]
  • 几乎只是第一个项目的项目名称,然后是一个包含随机数据的列表。我已经用对象名弄明白了,但对我来说尝试这种方法仍然效果不佳。
  • 据我了解,您有一个带有 QAction 名称的 QListWidget,您希望如果您删除一些 QListWidgetItem,它将删除其相应的 QAction,而无需重新启动应用程序,对吗?跨度>
  • 是的,这是正确的。当我从列表中删除时,我希望更新菜单以反映删除情况。

标签: python pyqt pyqt5 qmenu qsettings


【解决方案1】:

我认为您使用的数据结构不正确,因为当我执行您的代码时,它会生成两倍的 QActions,我建议的结构是一个字典,其中键是 QAction 的名称和数据列表的值:

{
 'item0': ['itemdata00', 'itemdata01', 'itemdata02'],
 'item1': ['itemdata10', 'itemdata11', 'itemdata12'],
  ...
}

要构建初始配置,请使用以下脚本:

create_settings.py

from PyQt5 import QtCore

if __name__ == '__main__':
    settings = QtCore.QSettings('test_org', 'my_app')
    d = {}
    for i in range(5):
        key = "item{}".format(i)
        value = ["itemdata{}{}".format(i, j) for j in range(3)]
        d[key] = value
    settings.setValue('menu_items', d)
    print(d)
    settings.sync()

另一方面,我认为您要处理 QActions 销毁的小部件应该接管相应的 QMenu,如下所示:

import sys
from PyQt5 import QtCore, QtWidgets

class MainWindow(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        layout = QtWidgets.QHBoxLayout(self)

        menu_btn = QtWidgets.QPushButton()
        open_list_btn = QtWidgets.QPushButton('open list')
        layout.addWidget(menu_btn)
        layout.addWidget(open_list_btn)

        menu = QtWidgets.QMenu()
        menu_btn.setMenu(menu)

        self.menu_manager = MenuManager("menu_items", "Menu")
        menu.addMenu(self.menu_manager.menu)
        self.menu_manager.menu.triggered.connect(self.menu_clicked)
        open_list_btn.clicked.connect(self.menu_manager.show)

    def menu_clicked(self, action):
        itmData = action.data()
        print(itmData)


class MenuManager(QtWidgets.QWidget):
    def __init__(self, key, menuname, parent=None):
        super(MenuManager, self).__init__(parent)
        self.settings = QtCore.QSettings('test_org', 'my_app')
        self.key = key

        self.layout = QtWidgets.QVBoxLayout(self)
        self.listWidget = QtWidgets.QListWidget()
        self.remove_btn = QtWidgets.QPushButton('Remove')
        self.layout.addWidget(self.listWidget)
        self.layout.addWidget(self.remove_btn)
        self.remove_btn.clicked.connect(self.remove_items)

        self.menu = QtWidgets.QMenu(menuname)

        load_items = self.settings.value(self.key, [])
        for name, itemdata in load_items.items():
            action = QtWidgets.QAction(name, self.menu)
            action.setData(itemdata)
            self.menu.addAction(action)

            item = QtWidgets.QListWidgetItem(name)
            item.setData(QtCore.Qt.UserRole, action)
            self.listWidget.addItem(item)

    def remove_items(self):
        for item in self.listWidget.selectedItems():
            it = self.listWidget.takeItem(self.listWidget.row(item))
            action = it.data(QtCore.Qt.UserRole)
            self.menu.removeAction(action)
        self.sync_data()

    def sync_data(self):
        save_items = {}
        for i in range(self.listWidget.count()):
            it = self.listWidget.item(i)
            action = it.data(QtCore.Qt.UserRole)
            save_items[it.text()] = action.data()

        self.settings.setValue(self.key, save_items)
        self.settings.sync()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

  • 我刚刚发布了我的答案,但我会花几分钟时间来复习一下你的答案。
  • @Richard 如 cmets 所述,您的方法可能会失败。
  • 是的,我明白这一点,我不确定是否有人会回答并在您之后 9 秒发布我的答案。感谢您的帮助,我现在正在查看您的答案。
  • @Richard 等待 2 小时是很短的时间来认为没有人会回应,另一方面发布答案总是好的,但也最好指出局限性,所以想要的人使用您的解决方案将知道什么会失败。如果我的回答对您有帮助,请不要忘记将其标记为正确:)
  • 感谢您的帮助,比我的答案要好得多。
【解决方案2】:

我明白了。我不确定有没有更好的方法,但我使用了对象名称。
在 MainWindow 中,我使用 for 循环内列表列表中每个列表的第一项将 objectNames 设置为 self.action,如下所示:

self.action.setObjectName(item[0])

然后我在 MainWindow 类中创建了这个函数:

def remove_menu_item(self, value):
    self.add_menu.removeAction(self.findChild(QAction, value))

然后我添加了这个:

w.remove_menu_item(item.text())

到 List 类中的 remove 函数以获取列表列表中的相同第一项,现在是 QActions 的 objectName。

【讨论】:

  • 使用您的方法可能会失败,例如,当您的 QMenu 的 QAction 与属于另一个 QMenu 的另一个 QAction 具有相同的文本时。
猜你喜欢
  • 2020-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-22
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
相关资源
最近更新 更多