PySide:QAbstractItemModel - 连接 dataChanged()

PySide: QAbstractItemModel - connect dataChanged()

当模型以任何方式被修改时,我正在尝试施展魔法。对添加或删除的项目做出反应非常容易,但我很难找到一种方法来对正在修改(重命名)的项目做出反应

model.dataChanged.connect(do_some_magic)

遗憾的是,它的效果不如:

model.layoutChanged.connect(do_some_magic)

编辑:我有一个简化版本的代码,只关注 QTreeView

main.py:

import sys
from PySide import QtGui
from editable_tree import EditableTreeView


def main():
    pm_app = QtGui.QApplication(sys.argv)
    pm_app.setStyle('plastique')
    pm_form = EditableTreeView()

    pm_form.show()
    pm_app.exec_()


if __name__ == '__main__':
    main()

editabletree.py:

from PySide import QtGui
from PySide import QtCore
import os
import editable_tree_ui    

class TreeItem(object):
    def __init__(self, _name, _parent=None):
        """
        :param _name: str
        :param _parent: TreeItem
        """
        self._name = _name
        self._children = []
        self._parent = _parent

        # if _parent is not None:
        #     _parent.add_child(self)

    def name(self):
        """
        :return: str
        """
        return self._name

    def set_name(self, name):
        """
        :param name: str
        :return: str
        """
        self._name = name

    def child(self, row):
        """
        :param row: int
        :return: TreeItem
        """
        return self._children[row]

    def child_count(self):
        """
        :return: list
        """
        return len(self._children)

    def parent(self):
        """
        :return: TreeItem
        """
        return self._parent

    def row(self):
        """
        :return: QModelIndex
        """
        if self._parent is not None:
            return self._parent._children.index(self)

    def add_child(self, child):
        """
        :param child: TreeItem
        """
        self._children.append(child)

    def insert_child(self, position, child):
        """
        :param position: int
        :param child: TreeItem
        :return: bool
        """
        if position < 0 or position > len(self._children):
            return False

        self._children.insert(position, child)
        child._parent = self
        return True

    def remove_child(self, position):
        """
        :param position: int
        :return: bool
        """
        if position < 0 or position > len(self._children):
            return False

        child = self._children.pop(position)
        child._parent = None

        return True

    def log(self, tab_level=-1):
        """
        :param tab_level: int
        :return: str
        """
        output = ""
        tab_level += 1

        for i in range(tab_level):
            output += "\t"

        output += "|------" + self._name + "\n"

        for child in self._children:
            output += child.log(tab_level)

        tab_level -= 1
        output += "\n"

        return output

    def __repr__(self):
        return self.log()


class TreeModel(QtCore.QAbstractItemModel):
    def __init__(self, _data, _parent=None):
        """
        :param _data: dict
        :param _parent: TreeItem
        """
        super(TreeModel, self).__init__(_parent)

        self._data = _data
        self.root_item = TreeItem('ROOT')

        self.setup_model_data(self._data)

    def rowCount(self, parent):
        """
        :param parent: QModelIndex
        :return: int
        """
        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        return parent_item.child_count()

    def columnCount(self, parent):
        """
        :param parent: QModelIndex
        :return: int
        """
        return 1

    def data(self, index, role):
        """
        :param index: QModelIndex
        :param role: int
        :return: QString
        """
        if not index.isValid():
            return None

        item = index.internalPointer()

        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            if index.column() == 0:
                return item.name()

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        """
        :param index: QModelIndex
        :param value: QVariant
        :param role: int (flag)
        :return: bool
        """
        if index.isValid():
            if role == QtCore.Qt.EditRole:
                item = index.internalPointer()
                item.set_name(value)

                return True
        return False

    def headerData(self, section, orientation, role):
        """
        :param section: int
        :param orientation:  Qt.Orientation
        :param role: int
        :return: QString
        """
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return self.root_item.name()
            else:
                return "Typeinfo"

    def flags(self, index):
        """
        :param index: QModelIndex
        :return: int (flag)
        """
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable

    def parent(self, index):
        """
        Should return the parent of the item with the given QModelIndex

        :param index: QModelIndex
        :return: QModelIndex
        """
        item = self.get_item(index)
        parent_item = item.parent()

        if parent_item == self.root_item:
            return QtCore.QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def index(self, row, column, parent):
        """
        Should return a QModelIndex that corresponds to the given row, column and parent item

        :param row: int
        :param column: int
        :param parent: QModelIndex
        :return: QModelIndex
        """
        parent_item = self.get_item(parent)

        child_item = parent_item.child(row)

        if child_item:
            return self.createIndex(row, column, child_item)
        else:
            return QtCore.QModelIndex()

    def get_item(self, index):
        """
        :param index: QModelIndex
        :return: TreeItem
        """
        if index.isValid():
            item = index.internalPointer()
            if item:
                return item

        return self.root_item

    def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
        """
        :param position: int
        :param rows: int
        :param parent: QModelIndex
        :return: bool
        """
        parent_item = self.get_item(parent)
        success = False

        self.beginInsertRows(parent, position, position + rows - 1)

        for row in range(rows):
            child_count = parent_item.child_count()
            child_item = TreeItem("untitled" + str(child_count))
            success = parent_item.insert_child(position, child_item)

        self.endInsertRows()

        return success

    def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
        """
        :param position: int
        :param rows: int
        :param parent: QModelIndex
        :return: bool
        """
        parent_item = self.get_item(parent)
        success = False

        self.beginRemoveRows(parent, position, position + rows - 1)

        for row in range(rows):
            success = parent_item.remove_child(position)

        self.endRemoveRows()

        return success

    def setup_model_data(self, _data, _parent=None):
        """
        Setup TreeView structure extracted from _data

        :param _data: dict
        :param _parent: TreeItem
        """
        if _parent is None:
            _parent = self.root_item

        for key, value in sorted(_data.iteritems()):
            if isinstance(value, dict):
                _item = TreeItem(key, _parent)
                _parent.add_child(_item)
                self.setup_model_data(value, _item)


class EditableTreeView(QtGui.QMainWindow, editable_tree_ui.Ui_MainWindow):
    def __init__(self, parent=None):
        """
        :param parent: TreeItem
        """
        super(EditableTreeView, self).__init__(parent)

        self.setupUi(self)

        self.template_path = r'C:\Users\gwuest\Downloads\PROJECT_TEMPLATE'
        self.target_path = ''

        self.template = self.get_directory_structure(self.template_path)

        model = TreeModel(self.template)

        self.treeView.setModel(model)

        model.dataChanged.connect(self.test)

    def get_directory_structure(self, _path):
        """
        Creates a nested dictionary that represents the folder structure of _path

        :param _path: str
        :return: dict
        """
        structure = {}
        _path = _path.rstrip(os.sep)
        start = _path.rfind(os.sep) + 1

        for path, dirs, files in os.walk(_path):
            folders = path[start:].split(os.sep)
            subdir = dict.fromkeys(files)
            parent = reduce(dict.get, folders[:-1], structure)
            parent[folders[-1]] = subdir

        return structure


    def test(self):
        print('test')

editabletree_ui.py:

from PySide import QtCore, QtGui

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.treeView = QtGui.QTreeView(self.centralwidget)
        self.treeView.setObjectName("treeView")
        self.horizontalLayout.addWidget(self.treeView)
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.btn_open_template = QtGui.QPushButton(self.centralwidget)
        self.btn_open_template.setObjectName("btn_open_template")
        self.verticalLayout.addWidget(self.btn_open_template)
        self.btn_open_target = QtGui.QPushButton(self.centralwidget)
        self.btn_open_target.setObjectName("btn_open_target")
        self.verticalLayout.addWidget(self.btn_open_target)
        spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.horizontalLayout.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
        self.btn_open_template.setText(QtGui.QApplication.translate("MainWindow", "Template", None, QtGui.QApplication.UnicodeUTF8))
        self.btn_open_target.setText(QtGui.QApplication.translate("MainWindow", "Server", None, QtGui.QApplication.UnicodeUTF8))

在 EditableTree class 中,我将 model.dataChanged 连接到一个测试方法...如果我编辑一个项目,什么也不会发生:( 据我所知,我不需要重新实现 dataChanged() 来让它工作

每次修改某些数据时,您都必须发出 dataChanged 信号,在您的 setData() 方法中:

def setData(self, index, value, role=QtCore.Qt.EditRole):
    """
    :param index: QModelIndex
    :param value: QVariant
    :param role: int (flag)
    :return: bool
    """
    if index.isValid():
        if role == QtCore.Qt.EditRole:
            item = index.internalPointer()
            item.set_name(value)
            self.dataChanged.emit(index, index) # <---
            return True
    return False