导出颜色并将其设置为 newly-added children 的索引

Derive and set color to the index of newly-added children

我已经创建了 QTreeWidgetQTreeWidgetItem 的子类,我试图在其中突出显示新添加的项目(文本将被涂成红色)。

我采用的树状结构如下:

|-- parent
|--|-- child
|--|-- child

树形小部件最初是从字典中填充的。

为了获得差异,我通过将树小部件中的当前层次结构转换为字典并将其与填充它的初始字典进行比较来做到这一点。

但是,如果我将新 child 添加到现有 parent 中,其中新 child 的名称已经存在于另一个 parent 中,方法相同如上所述不起作用,因为它会将找到的第一个结果着色。

要复制:

获取新添加的 child(ren) 索引的最佳方法是什么?

感谢您的任何回复。

P.S:如果大家对parent高亮有更好的建议,欢迎大家补充。

class CustomTreeWidgetItem(QtGui.QTreeWidgetItem):
    def __init__(self, widget=None, text=None, is_tristate=False):
        super(CustomTreeWidgetItem, self).__init__(widget)

        self.setText(0, text)

        if is_tristate:
            # Solely for the Parent item
            self.setFlags(
                self.flags()
                | QtCore.Qt.ItemIsTristate
                | QtCore.Qt.ItemIsEditable
                | QtCore.Qt.ItemIsUserCheckable
            )
        else:
            self.setFlags(
                self.flags()
                | QtCore.Qt.ItemIsEditable
                | QtCore.Qt.ItemIsUserCheckable
            )
            self.setCheckState(0, QtCore.Qt.Unchecked)

    def setData(self, column, role, value):
        state = self.checkState(column)
        QtGui.QTreeWidgetItem.setData(self, column, role, value)
        if (role == QtCore.Qt.CheckStateRole and
            state != self.checkState(column)):
            tree_widget = CustomTreeWidget()
            if tree_widget is not None:
                tree_widget.itemToggled.emit(self, column)



class CustomTreeWidget(QtGui.QTreeWidget):
    def __init__(self, widget=None):
        super(CustomTreeWidget, self).__init__(widget)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_custom_menu)


    def show_custom_menu(self):
        base_node = self.selectedItems()[0]

        qmenu = QtGui.QMenu(self)
        remove_action = QtGui.QAction("Remove item", self)
        remove_action.triggered.connect(self.remove_selected_item)
        qmenu.addAction(remove_action)

        # The following options are only effected for top-level items
        # top-level items do not have `parent()`
        if not base_node.parent():
            add_new_child_action = QtGui.QAction("Add new sub item", self)
            add_new_child_action.triggered.connect(
                partial(self.adds_new_child_item, base_node)
            )
            # qmenu.addAction(add_new_child_action)
            qmenu.insertAction(remove_action, add_new_child_action)

        qmenu.exec_(QtGui.QCursor.pos())

    def add_item_dialog(self, title):
        text, ok = QtGui.QInputDialog.getText(
            self,
            "Add {0} Item".format(title),
            "Enter name for {0}-Item:".format(title)
        )
        if ok and text != "":
            return text

    def add_new_parent_item(self):
        input_text = self.add_item_dialog("Parent")
        if input_text:
            CustomTreeWidgetItem(self, input_text, is_tristate=True)

    def adds_new_child_item(self, base_node):

        input_text = self.add_item_dialog("Sub")
        if input_text:
            CustomTreeWidgetItem(base_node, input_text)
            self.setItemExpanded(base_node, True)

    def remove_selected_item(self):
        root = self.invisibleRootItem()
        for item in self.selectedItems():
            (item.parent() or root).removeChild(item)

    def derive_tree_items(self, mode="all"):
        all_items = defaultdict(list)

        root_item = self.invisibleRootItem()
        top_level_count = root_item.childCount()

        for i in range(top_level_count):
            top_level_item = root_item.child(i)
            top_level_item_name = str(top_level_item.text(0))
            child_num = top_level_item.childCount()

            all_items[top_level_item_name] = []

            for n in range(child_num):
                child_item = top_level_item.child(n)
                child_item_name = str(child_item.text(0)) or ""

                if mode == "all":
                    all_items[top_level_item_name].append(child_item_name)

                elif mode == "checked":
                    if child_item.checkState(0) == QtCore.Qt.Checked:
                        all_items[top_level_item_name].append(child_item_name)

                elif mode == "unchecked":
                    if child_item.checkState(0) == QtCore.Qt.Unchecked:
                        all_items[top_level_item_name].append(child_item_name)

        return all_items


class MainApp(QtGui.QWidget):
    def __init__(self):
        super(MainApp, self).__init__()

        # initial dictionary that is populated into the tree widget
        test_dict = {
            "menuA": ["a101", "a102"],
            "menuC": ["c101", "c102", "c103"],
            "menuB": ["b101"],
        }

        self._tree = CustomTreeWidget()
        self._tree.header().hide()

        for pk, pv in sorted(test_dict.items()):
            parent = CustomTreeWidgetItem(self._tree, pk, is_tristate=True)

            for c in pv:
                child = CustomTreeWidgetItem(parent, c)

        self.orig_dict = self._tree.derive_tree_items()

        # Expand the hierarchy by default
        self._tree.expandAll()

        tree_layout = QtGui.QVBoxLayout()
        self.btn1 = QtGui.QPushButton("Add new item")
        self.btn2 = QtGui.QPushButton("Highlight Diff.")
        tree_layout.addWidget(self._tree)
        tree_layout.addWidget(self.btn1)
        tree_layout.addWidget(self.btn2)

        main_layout = QtGui.QHBoxLayout()
        main_layout.addLayout(tree_layout)

        self.setLayout(main_layout)
        self.setup_connections()

    def setup_connections(self):
        self.btn1.clicked.connect(self.add_parent_item)
        self.btn2.clicked.connect(self.highlight_diff)


    def add_parent_item(self):
        # Get current selected in list widget
        # CustomTreeWidgetItem(self._tree, "test", is_tristate=True)
        self._tree.add_new_parent_item()


    def highlight_diff(self):
        self.current_dict = self._tree.derive_tree_items()

        if self.orig_dict != self.current_dict:
            # check for key difference
            diff = [k for k in self.current_dict if k not in self.orig_dict]


            if diff:
                # get the difference of top-level items
                for d in diff:
                    top_item = self._tree.findItems(d, QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive)
                    #print aaa[0].childCount()
                    top_item[0].setTextColor(0, QtGui.QColor(255, 0, 0))

                    if top_item[0].childCount():
                        for n in range(top_item[0].childCount()):
                            top_item[0].child(n).setTextColor(0, QtGui.QColor(255, 0, 0))

            # to highlight the child diff. of existing top-items
            # issue with this portion if the new added item name already existed
            for k, v in self.current_dict.items():
                if k in self.orig_dict:
                    diff = set(v).difference(self.orig_dict.get(k), [])

                    for d in diff:
                        child_item = self._tree.findItems(d, QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive)
                        child_item[0].setTextColor(0, QtGui.QColor(255, 0, 0))



if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = MainApp()
    w.show()
    sys.exit(app.exec_())

您可以在角色中保存一个标志,指示它是否是新项目,并使用委托更改颜色:

import sys
from functools import partial
from PyQt4 import QtCore, QtGui
from collections import defaultdict

IsNewItemRole = QtCore.Qt.UserRole + 1000


class CustomTreeDelegate(QtGui.QStyledItemDelegate):
    @property
    def text_color(self):
        if not hasattr(self, "_text_color"):
            self._text_color = QtGui.QColor()
        return self._text_color

    @text_color.setter
    def text_color(self, color):
        self._text_color = color

    def initStyleOption(self, option, index):
        super(CustomTreeDelegate, self).initStyleOption(option, index)
        if self.text_color.isValid() and index.data(IsNewItemRole):
            option.palette.setBrush(QtGui.QPalette.Text, self.text_color)


class CustomTreeWidgetItem(QtGui.QTreeWidgetItem):
    def __init__(self, parent=None, text="", is_tristate=False, is_new_item=False):
        super(CustomTreeWidgetItem, self).__init__(parent)
        self.setText(0, text)
        flags = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
        if is_tristate:
            flags |= QtCore.Qt.ItemIsTristate
        else:
            self.setCheckState(0, QtCore.Qt.Unchecked)
        self.setFlags(self.flags() | flags)
        self.setData(0, IsNewItemRole, is_new_item)

    def setData(self, column, role, value):
        state = self.checkState(column)
        QtGui.QTreeWidgetItem.setData(self, column, role, value)
        if role == QtCore.Qt.CheckStateRole and state != self.checkState(column):
            tree_widget = self.treeWidget()
            if isinstance(tree_widget, CustomTreeWidget):
                tree_widget.itemToggled.emit(self, column)


class CustomTreeWidget(QtGui.QTreeWidget):
    itemToggled = QtCore.pyqtSignal(QtGui.QTreeWidgetItem, int)

    def __init__(self, widget=None):
        super(CustomTreeWidget, self).__init__(widget)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_custom_menu)

    def show_custom_menu(self, pos):
        base_node = self.itemAt(pos)
        if base_node is None:
            return

        qmenu = QtGui.QMenu(self)
        remove_action = QtGui.QAction("Remove item", self)
        remove_action.triggered.connect(self.remove_selected_item)
        qmenu.addAction(remove_action)

        # The following options are only effected for top-level items
        # top-level items do not have `parent()`
        if base_node.parent() is None:
            add_new_child_action = QtGui.QAction("Add new sub item", self)
            add_new_child_action.triggered.connect(
                partial(self.adds_new_child_item, base_node)
            )
            # qmenu.addAction(add_new_child_action)
            qmenu.insertAction(remove_action, add_new_child_action)

        qmenu.exec_(self.mapToGlobal(pos))

    def add_item_dialog(self, title):
        text, ok = QtGui.QInputDialog.getText(
            self, "Add {0} Item".format(title), "Enter name for {0}-Item:".format(title)
        )
        if ok:
            return text

    def add_new_parent_item(self):
        input_text = self.add_item_dialog("Parent")
        if input_text:
            it = CustomTreeWidgetItem(
                self, input_text, is_tristate=True, is_new_item=True
            )

    def adds_new_child_item(self, base_node):
        input_text = self.add_item_dialog("Sub")
        if input_text:
            it = CustomTreeWidgetItem(base_node, input_text, is_new_item=True)
            self.setItemExpanded(base_node, True)
            it.setData(0, IsNewItemRole, True)

    def remove_selected_item(self):
        root = self.invisibleRootItem()
        for item in self.selectedItems():
            (item.parent() or root).removeChild(item)


class MainApp(QtGui.QWidget):
    def __init__(self, parent=None):
        super(MainApp, self).__init__(parent)

        # initial dictionary that is populated into the tree widget
        test_dict = {
            "menuA": ["a101", "a102"],
            "menuC": ["c101", "c102", "c103"],
            "menuB": ["b101"],
        }

        self._tree = CustomTreeWidget()
        self._tree.header().hide()

        self._tree_delegate = CustomTreeDelegate(self._tree)
        self._tree.setItemDelegate(self._tree_delegate)

        for pk, pv in sorted(test_dict.items()):
            parent = CustomTreeWidgetItem(self._tree, pk, is_tristate=True)

            for c in pv:
                child = CustomTreeWidgetItem(parent, c)

        # Expand the hierarchy by default
        self._tree.expandAll()

        tree_layout = QtGui.QVBoxLayout()
        self.btn1 = QtGui.QPushButton("Add new item")
        self.btn2 = QtGui.QPushButton("Highlight Diff.")
        tree_layout.addWidget(self._tree)
        tree_layout.addWidget(self.btn1)
        tree_layout.addWidget(self.btn2)

        main_layout = QtGui.QHBoxLayout(self)
        main_layout.addLayout(tree_layout)

        self.setup_connections()

    def setup_connections(self):
        self.btn1.clicked.connect(self.add_parent_item)
        self.btn2.clicked.connect(self.highlight_diff)

    def add_parent_item(self):
        # Get current selected in list widget
        # CustomTreeWidgetItem(self._tree, "test", is_tristate=True)
        self._tree.add_new_parent_item()

    def highlight_diff(self):
        self._tree_delegate.text_color = QtGui.QColor(255, 0, 0)
        self._tree.viewport().update()


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = MainApp()
    w.show()
    sys.exit(app.exec_())