更改 QTreeWidgetItem 标志时防止递归

Prevent recursion when changing QTreeWidgetItem flags

我正在尝试制作一个非常基本的密码管理器,但在尝试编辑项目时遇到了问题。当按下“编辑密码”按钮时,它使当前选定的项目可编辑,我希望在用户完成修改后将其删除。尝试删除标志 ItemIsEditable 会导致它在行 item.setFlags(item.flags() & ~Qt.ItemIsEditable).

上进入无限递归
# app class -------------------------------------------------------------------------- #
class App(QApplication):
    # initialisation ----------------------------------------------------------------- #
    def __init__(self, argv):
        super().__init__(argv)

        self.__ready = False

        self.__setup__()
        self.__load_data__()

        self.__ready = True

    # private methods ---------------------------------------------------------------- #
    def __load_data__(self):
        self.data_tree.headerItem().setText(0, "Client")
        self.data_tree.headerItem().setText(1, "Workstation")
        self.data_tree.headerItem().setText(2, "Login")
        self.data_tree.headerItem().setText(3, "Password")

        for level_1, client in enumerate(self.data["clients"]):
            row_1 = QTreeWidgetItem(self.data_tree)

            self.data_tree.topLevelItem(level_1).setText(0, client["name"])

            for level_2, workstation in enumerate(client["workstations"]):
                row_2 = QTreeWidgetItem(row_1)

                self.data_tree.topLevelItem(level_1).child(level_2).setText(
                    1, workstation["name"]
                )

                for level_3, login in enumerate(workstation["logins"]):
                    row_3 = QTreeWidgetItem(row_2)

                    row_3.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

                    self.data_tree.topLevelItem(level_1).child(level_2).child(
                        level_3
                    ).setText(2, login["username"])
                    self.data_tree.topLevelItem(level_1).child(level_2).child(
                        level_3
                    ).setText(3, login["password"])

        self.data_tree.setSortingEnabled(True)

    def __setup__(self):
        self.data = pd.read_json("list.json")
        self.main_window = QMainWindow()
        self.central_widget = QWidget(self.main_window)
        self.data_tree = QTreeWidget(self.central_widget)
        self.edit_password_button = QPushButton(self.central_widget)

        self.central_widget.setGeometry(QRect(0, 0, 500, 500))
        self.central_widget.setObjectName("central_widget")

        self.data_tree.itemChanged.connect(self.save_password)
        self.data_tree.setGeometry(QRect(0, 0, 500, 450))
        self.data_tree.setObjectName("data_tree")
        self.data_tree.sortByColumn(0, Qt.SortOrder.AscendingOrder)

        self.main_window.setCentralWidget(self.central_widget)
        self.main_window.setGeometry(QRect(200, 200, 500, 500))
        self.main_window.setWindowTitle("Password Manager")
        self.main_window.show()

        self.edit_password_button.clicked.connect(self.edit_password)
        self.edit_password_button.setGeometry(QRect(345, 455, 150, 40))
        self.edit_password_button.setText("Edit Password")

    # events ------------------------------------------------------------------------- #
    @pyqtSlot()
    def edit_password(self):
        try:
            item = self.data_tree.currentItem()

            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.data_tree.scrollToItem(item)
            self.data_tree.editItem(item, 3)
        except Exception as e:
            print(e)

    @pyqtSlot(QTreeWidgetItem, int)
    def save_password(self, item, column):
        if not self.__ready:
            return

        for client in self.data["clients"]:
            if client["name"] == item.parent().parent().text(0):
                for workstation in client["workstations"]:
                    if workstation["name"] == item.parent().text(1):
                        for login in workstation["logins"]:
                            if login["username"] == item.text(2):
                                login["password"] = item.text(3)

        with open("list.json", "w") as file:
            json.dump(json.loads(self.data.to_json()), file, indent=4)

        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

    def simulate_password(self):
        keyboard.write(self.data_tree.currentItem().text(3))

    def test_func(self):
        print("test")


app = App(sys.argv)

keyboard.add_hotkey("ctrl+insert", app.simulate_password)

sys.exit(app.exec())

itemChanged signal 是针对对项目的 所有 更改发出的,而不仅仅是对其文本的更改。为避免递归,您可以在设置标志时暂时阻止信号,这样 save_password 槽就不会再次被触发:

@pyqtSlot()
def edit_password(self):
    item = self.data_tree.currentItem()

    blocked = item.treeWidget().blockSignals(True)
    item.setFlags(item.flags() | Qt.ItemIsEditable)
    item.treeWidget().blockSignals(blocked)

    self.data_tree.scrollToItem(item)
    self.data_tree.editItem(item, 3)

@pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
    ...
    blocked = item.treeWidget().blockSignals(True)
    item.setFlags(item.flags() & ~Qt.ItemIsEditable)
    item.treeWidget().blockSignals(blocked)