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