In a code below both Car() and Plane() inherit from Base class.
Every time QTableView is clicked OnClick() method receives a QModelIndex as its incoming argument.
Inside of scope of OnClick() method node=index.internalPointer() line returns an instance of either Car or Plane defined in model's self.items variable.
Model's self.items is not a list but a sub-classed hierarchical variable. Yet internalPointer() method does make it appear it indexes a list variable with the supplied to it as an argument the row number of QModelIndex that was clicked.
I would appreciate if you supply more details on how internalPointer() method works so I could design Plane, Car and Base classes accordingly (so they return what I want and not what internalPointer().
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
class Base(object):
def __init__(self, name, parentNode=None):
self._name = name
self._children=[]
self._parentNode=parentNode
if parentNode is not None:
parentNode.addChild(self)
def typeInfo(self):
return "BaseNode"
def addChild(self, child):
self._children.append(child)
def name(self):
return self._name
def setName(self, name):
self._name = name
def child(self, row):
return self._children[row]
def childCount(self):
return len(self._children)
def getParent(self):
return self._parentNode
def row(self):
if self._parentNode is not None:
return self._parentNode._children.index(self)
class Car(Base):
def __init__(self, name, parent=None):
super(Car, self).__init__(name, parent)
def typeInfo(self):
return "CarNode"
class Plane(Base):
def __init__(self, name, parent=None):
super(Plane, self).__init__(name, parent)
def typeInfo(self):
return "PlaneNode"
class DataModel(QtCore.QAbstractItemModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = Base("Base")
car0 = Car("Car0", self.items)
car1 = Car("Car1", car0)
car2 = Car("Car2", car1)
plane0 = Plane("Plane0", self.items)
plane1 = Plane("Plane1", plane0)
plane2 = Plane("Plane2", plane1)
def columnCount(self, index=QtCore.QModelIndex()):
return 3
def getNodeFromIndex(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
return self.items
def parent(self, index):
node = self.getNodeFromIndex(index)
parentNode = node.getParent()
if parentNode == self.items:
return QtCore.QModelIndex()
return self.createIndex(parentNode.row(), 0, parentNode)
def index(self, row, column, parentIndex):
parentNode = self.getNodeFromIndex(parentIndex)
childItem = parentNode.child(row)
if childItem:
newIndex=self.createIndex(row, column, childItem)
return newIndex
else:
return QtCore.QModelIndex()
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
parentNode = self.items
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
def data(self, index, role):
if not index.isValid(): return QtCore.QVariant()
row=index.row()
column=index.column()
node=index.internalPointer()
if role==QtCore.Qt.DisplayRole:
if column==0 and not self.columnCount():
return QtCore.QModelIndex()
else:
return QtCore.QModelIndex()
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)
self.dataModel=DataModel()
self.viewA=QtGui.QTableView()
self.viewA.setModel(self.dataModel)
self.viewA.clicked.connect(self.onClick)
mainLayout.addWidget(self.viewA)
self.show()
def onClick(self, index):
node=index.internalPointer()
print node.name(), node.getParent().name()
window=Window()
sys.exit(app.exec_())
It's not exactly clear what you mean by designing your classes accordingly, but internal pointer ist just something you create with help of createIndex() in QModelIndex.
See the documentation of createIndex.
So it's up to you to store something useful in it.
Related
I'm currently struggling with QTreeView. Even though I called header().setVisible(True) the header is still not visible. header().isVisible() returns False. headerHidden is already set to False in Qt Designer.
I'm using Python 3.10 and latest PySide2 5.15.2.1 on Windows 10.
Any ideas what is wrong?
An example for a performant implementation of a sortable QTreeView usage in Python would be very appreciated.
class Node(object):
def __init__(self, path, parent=None):
self._parent = parent
self._path = path
self._children = []
def children(self):
return self._children
def hasChildren(self):
return bool(self.children())
def columnCount(self):
return 3
def child_count(self):
return len(self._children)
def add_child(self, child) -> 'Node':
self._children.append(child)
child._parent = self
return self
def child(self, row):
if 0 <= row < self.child_count():
return self._children[row]
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
return -1
class MyModel(QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._rootNode: Node = Node("Root", None)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal:
if role == Qt.DisplayRole:
return ("File", "Line", "Text")[section]
def index(self, row, column, parent=QModelIndex()):
if not self.hasIndex(row, column, parent):
return QModelIndex()
node = parent.internalPointer() if parent.isValid() else self._rootNode
if node.children:
return self.createIndex(row, column, node.child(row))
return QModelIndex()
def parent(self, child):
if not child.isValid():
return QModelIndex()
node: Node = child.internalPointer()
if node.row() >= 0:
return self.createIndex(node.row(), 0, node.parent())
return QModelIndex()
def rowCount(self, parent=QModelIndex()):
node = parent.internalPointer() if parent.isValid() else self._rootNode
return node.child_count()
def columnCount(self, parent=QModelIndex()):
return 3
def hasChildren(self, parent=QModelIndex()):
node = parent.internalPointer() if parent.isValid() else self._rootNode
return node.hasChildren()
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if role == Qt.DisplayRole:
node: Node = index.internalPointer()
if index.column() == 0:
return node.path
return "column"
def setData(self, index, value, role=Qt.EditRole):
return False
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def appendRow(self, item, parent=None):
pos = self.rowCount(self.indexFromItem(parent))
self.beginInsertRows(self.indexFromItem(parent), pos, pos)
if parent is None:
parent = self._rootNode.add_child(item)
else:
parent.add_child(item)
self.endInsertRows()
return parent
class MyTreeView(QTreeView):
def __init__(self, _model, parent=None):
super().__init__(parent)
self._model = MyModel(self)
self.setModel(self._model)
parent = None
for i in range(1000):
parent = self._model.appendRow(Node(f"Path{i}", parent)
if i % 5 == 0:
parent = None
self.header().setVisible(True)
print(self.header().isVisible()) # <-- Outputs False
I'm trying to add custom widget (image below) as item delegate in QTreeview. I do see the size of the treeItem getting bigger but my widget does not show up. Not sure what am I missing here. Thanks in advance for all your suggestions.
Custom Widget
Here is the code
ItemNode (node for QAbstractTeeModel)
class BaseNode:
def __init__(self, data, parent=None):
self._data = data
self._children = list()
self._parent = None
if parent:
parent.addChild(self)
def addChild(self, node):
self._children.append(node)
node._parent = self
def insertChildren(self, position, children):
if position < 0:
return False
for child in children:
child._parent = self
self._children.insert(position, child)
return True
def removeChildren(self, position, count):
if position < 0 or position > len(self._children):
return False
for i in range(count):
child = self._children.pop(position)
child._parent = None
return True
def child(self, row):
if row < self.rowCount():
return self._children[row]
def rowCount(self):
return len(self._children)
def columnCount(self):
return len(self._data)
def parent(self):
return self._parent
def index(self):
if self._parent:
return self._parent._children.index(self)
return 0
def data(self, column):
return self._data[column]
QAbstractItemModel
class NodeModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.headers = ["Node"]
self.root_node = BaseNode(["root"])
def getNode(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
return self.root_node
def rowCount(self, parent=QtCore.QModelIndex()):
return self.root_node.rowCount()
def columnCount(self, parent=QtCore.QModelIndex()):
return self.root_node.columnCount()
def data(self, index, role):
if not index.isValid():
return
node = self.getNode(index)
if role == QtCore.Qt.EditRole or role == QtCore.Qt.EditRole:
return node
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.headers[section]
def flags(self, index):
flgs = 0
if index.isValid():
flgs = QtCore.Qt.ItemIsEditable
flgs |= QtCore.Qt.ItemIsEnabled
flgs |= QtCore.Qt.DisplayRole
return flgs
def parent(self, child):
node = self.getNode(child)
parent = node.parent()
if parent == self.root_node:
return QtCore.QModelIndex()
return self.createIndex(parent.index(), 0, parent)
def index(self, row, column, parent):
parent = self.getNode(parent)
child = parent.child(row)
if child:
return self.createIndex(row, column, child)
return QtCore.QModelIndex()
def insertRows(self, row, count, parent=QtCore.QModelIndex(), children=None):
parent_node = self.getNode(parent)
self.beginInsertRows(parent, row, row + count - 1)
result = parent_node.insertChildren(row, children)
self.endInsertRows()
return result
def removeRows(self, row, count, parent=QtCore.QModelIndex()):
parent_node = self.getNode(parent)
self.beginRemoveRows(parent, row, row + count - 1)
success = parent_node.removeChildren(row, count)
self.endRemoveRows()
return success
ItemEditor (QWidget for Item Delegate
class ItemEditor(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Form() # this is a UI containing
self.ui.setupUi(self)
self.setAutoFillBackground(True)
Item Delegate
class NodeDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def createEditor(self, parent, option, index):
if index.column() == 0:
editor = ItemEditor()
return editor
else:
return super().createEditor(self, parent, option, index)
def setEditorData(self, editor, index):
if index.column() == 0:
pass
else:
return super().setEditorData(editor, index)
def setModelData(self, editor, model, index):
if index.column() == 0:
pass
else:
super().setModelData(editor, model, index)
def sizeHint(self, option, index):
editor = ItemEditor()
return editor.sizeHint()
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
Implementation in TreeView
app = QtWidgets.QApplication([])
tw = QtWidgets.QTreeView()
model = NodeModel()
tw.setModel(model)
for i in "abcd":
print(i)
node = BaseNode(data=[i])
index = model.index(model.root_node.rowCount(), 0, QtCore.QModelIndex())
model.insertRows(1, 1, index, children=[node])
item_delegate = NodeDelegate()
tw.setItemDelegateForRow(0, item_delegate)
tw.show()
app.exec_()
I am working on a simple tree directory explorer based on a qtreeview with a model view/controller/implementation. I need to use some threads that recursively search the sub-folders and feed the model/datas of the qtreeview. All of this works fine.
But my issue is that the view doesnt refresh when the datas change ...
I have tried a few different things, but iam not happy with any of the solutions:
QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
Emitting data change from setData() of the model should update the view, but it doesnt work for me. Also i havnt found an elegant way of finding the qmodelIndex. Iam just recusively loop all the datas to find the right Index.
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import time
import traceback, sys, os
from glob import glob
from random import randrange
import traceback
DEPTH = 0
threadpool = QThreadPool()
##########################################
###### Example thread function #####
##########################################
def listFolders( parent ):
global DEPTH
time.sleep(2)
if DEPTH>4:
return {'fileList':[], 'parent':parent}
else:
DEPTH+=1
fileList = []
for item in range(randrange(1,5)):
fileList.append('item_'+str(item))
return {'fileList':fileList, 'parent':parent}
##########################################
###### simple threading #####
##########################################
class WorkerSignals(QObject):
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
##########################################
###### Model for qtreeview #####
##########################################
class SceneGraphModel(QtCore.QAbstractItemModel):
def __init__(self, root ,parent=None):
super(SceneGraphModel, self).__init__(parent)
self._rootNode = root
def rowCount(self, parent):
if not parent.isValid():
parentNode = self._rootNode
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
def columnCount(self, parent):
return 1
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
if index.column() == 0:
return node.name()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.EditRole:
node = index.internalPointer()
node.setName(value)
return True
return False
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return "Scenegraph"
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def parent(self, index):
node = self.getNode(index)
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
if parentNode == None:
row = 0
else:
row = parentNode.row()
return self.createIndex(row, 0, parentNode)
def index(self, row, column, parent):
parentNode = self.getNode(parent)
childItem = parentNode.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def getNode(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
return self._rootNode
##########################################
###### Node class that contain the qtreeview datas #####
##########################################
class Node(object):
def __init__(self, name, parent=None):
self._name = name
self._children = []
self._parent = parent
if parent is not None:
parent.addChild(self)
def typeInfo(self):
return "folder"
def addChild(self, child):
self._children.append(child)
def name(self):
return self._name
def child(self, row):
return self._children[row]
def childCount(self):
return len(self._children)
def parent(self):
return self._parent
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
def __repr__(self):
return 'NODE_'+self.name()
##########################################
###### qtreeview containing the threading #####
##########################################
class DirectoryTree(QTreeView):
def __init__(self):
super(DirectoryTree, self).__init__()
#create root node
self.rootNode = Node('root')
#add model to treeview
self._model = SceneGraphModel(self.rootNode)
self.setModel(self._model)
#recurive loop with thread to add more datas
self.loop( self.rootNode )
def thread(self, path):
return listFolders(path)
def threadResult(self, result ):
for item in result['fileList']:
newNode = Node(item,result['parent'])
self.loop(newNode)
def loop(self, parent ):
worker = Worker( self.thread, parent )
worker.signals.result.connect( self.threadResult )
threadpool.start(worker)
##########################################
###### window with countdown #####
##########################################
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.counter = 0
self.layout = QVBoxLayout()
self.l = QLabel("Start")
self.layout.addWidget(self.l)
w = QWidget()
w.setLayout(self.layout)
self.setCentralWidget(w)
self.treeView = DirectoryTree()
self.layout.addWidget(self.treeView)
self.show()
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.recurring_timer)
self.timer.start()
self.setGeometry(0, 0, 650, 550)
self.setWindowTitle("shot tree")
self.centerOnScreen()
def centerOnScreen (self):
resolution = QtGui.QDesktopWidget().screenGeometry()
self.move((resolution.width() / 2) - (self.frameSize().width() / 2),
(resolution.height() / 2) - (self.frameSize().height() / 2))
def recurring_timer(self):
self.counter +=1
self.l.setText("Counter: %d" % self.counter)
##### This is a hack to refresh the view
##### i want to remove this line
##### and properly emit the changes from the node class to refresh the qtreeview
self.treeView.expandAll()
app = QApplication([])
window = MainWindow()
app.exec_()
This is my code example. there is a count down in the main window that would execute : self.treeView.expandAll()
every second to force the view to update, i want to find a better solution ...
Related topics i found:
Refresh view when model data has not changed (Qt/PySide/PyQt)?
PyQt and MVC-pattern
The problem has nothing to do with threads. For the view to be notified the model must emit the signal layoutAboutToBeChanged before the change and layoutChanged after the change, but for this the node must access the model, so the model must be made as a Node attribute. With that change you no longer need a QTimer to update the view.
class SceneGraphModel(QtCore.QAbstractItemModel):
def __init__(self, root, parent=None):
super(SceneGraphModel, self).__init__(parent)
self._rootNode = root
self._rootNode._model = self
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
parentNode = self._rootNode
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
if index.column() == 0:
return node.name()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.EditRole:
node = index.internalPointer()
node.setName(value)
return True
return False
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return "Scenegraph"
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def parent(self, index):
node = self.getNode(index)
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
if parentNode is None:
row = 0
else:
row = parentNode.row()
return self.createIndex(row, 0, parentNode)
def index(self, row, column, parent=QtCore.QModelIndex()):
parentNode = self.getNode(parent)
childItem = parentNode.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def getNode(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
print("node", node)
return self._rootNode
class Node(object):
def __init__(self, name, parent=None):
self._name = name
self._children = []
self._parent = parent
self._model = None
if parent is not None:
parent.addChild(self)
def typeInfo(self):
return "folder"
def addChild(self, child):
self._model.layoutAboutToBeChanged.emit()
self._children.append(child)
child._model = self._model
self._model.layoutChanged.emit()
def name(self):
return self._name
def setName(self, name):
self._name = name
def child(self, row):
return self._children[row] if row < len(self._children) else None
def childCount(self):
return len(self._children)
def parent(self):
return self._parent
def row(self):
return 0 if self.parent() is None else self._parent._children.index(self)
def __repr__(self):
return 'NODE_' + self.name()
I'm trying to just delete the QTreeView item "Node 6 (delete me)" from within the model.
I can't get it to work, as nothing is deleted. What am I doing wrong?
See the MainWindow.init method for the removal code.
import sys
try:
from PySide2 import QtWidgets
except ImportError:
from PyQt5 import QtWidgets
try:
from PySide2 import QtCore
except ImportError:
from PyQt5 import QtCore
class Node(object):
def __init__(self, name, parent=None):
self._name = name
self._children = []
self._parent = parent
if parent is not None:
parent.addChild(self)
def addChild(self, child):
self._children.append(child)
child._parent = self
def name(self):
return self._name
def child(self, row):
return self._children[row]
def insertChild(self, position, child):
if position < 0 or position > len(self._children):
return False
self._children.insert(position, child)
child._parent = self
return True
def childCount(self):
return len(self._children)
def parent(self):
return self._parent
def setParent(self, new_parent):
self._parent._children.remove(self)
self._parent = new_parent
new_parent._children.append(self)
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
def removeChild(self, position):
if position < 0 or position > len(self._children):
return False
child = self._children.pop(position)
child._parent = None
return True
def __repr__(self):
return self._name
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, root, parent=None):
super(TreeModel, self).__init__(parent)
self._rootNode = root
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
parentNode = self._rootNode
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
def columnCount(self, parent):
return 1
def data(self, index, role):
"""Return whatever the view should display"""
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
if index.column() == 0:
return node.name()
def index(self, row, column, parent):
if not parent.isValid():
# parent is not valid when it is the root node, since the "parent"
# method returns an empty QModelIndex
parentNode = self._rootNode
else:
parentNode = parent.internalPointer() # the node
childItem = parentNode.child(row)
return self.createIndex(row, column, childItem)
def parent(self, index):
node = index.internalPointer()
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
return self.createIndex(parentNode.row(), 0, parentNode)
def flags(self, index):
# Original, inherited flags:
original_flags = super(TreeModel, self).flags(index)
return (original_flags | QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
| QtCore.Qt.ItemIsDropEnabled)
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return 'Node name'
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.tree = QtWidgets.QTreeView()
self.layout = QtWidgets.QGridLayout()
self.main_widget = QtWidgets.QWidget()
self.layout.addWidget(self.tree, 0, 0)
self.main_widget.setLayout(self.layout)
self.setCentralWidget(self.main_widget)
# Note how these are added in a non-sorted fashion
root_node = Node('Hidden root')
Node(name='Node 3', parent=root_node) # add using parent param
n2 = Node(name='Node 2', parent=root_node) # add using parent param
Node(name='Node 1', parent=root_node) # add using parent param
n2.addChild(Node(name='Node 5')) # add using addChild
n2.addChild(Node(name='Node 6 (delete me)')) # add using addChild
n2.addChild(Node(name='Node 4')) # add using addChild
model = TreeModel(root=root_node)
self.tree.setModel(model)
self.tree.expandAll()
# Attempt to remove a node from within the model
node2 = model.index(1, 0, QtCore.QModelIndex()) # "Node 2" index
index = model.index(1, 0, parent=node2)
index_node = index.internalPointer()
print('index represents node "%s" (its parent node is "%s")' %
(index_node.name(), index.parent().internalPointer().name()))
print('Removing %s on row %s' % (index_node.name(), index.row()))
model.beginRemoveRows(index.parent(), index.row(), index.row())
success = model.removeRow(index.row(), parent=index.parent())
print('Removal was a succes?:', success)
model.endRemoveRows()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
#Jaa-c if you like, take this and copy-paste it into an answer from yourself and I'll mark it as the accepted answer.
import sys
try:
from PySide2 import QtWidgets
except ImportError:
from PyQt5 import QtWidgets
try:
from PySide2 import QtCore
except ImportError:
from PyQt5 import QtCore
class Node(object):
def __init__(self, name, parent=None):
self._name = name
self._children = []
self._parent = parent
if parent is not None:
parent.addChild(self)
def addChild(self, child):
self._children.append(child)
child._parent = self
def name(self):
return self._name
def child(self, row):
return self._children[row]
def insertChild(self, position, child):
if position < 0 or position > len(self._children):
return False
self._children.insert(position, child)
child._parent = self
return True
def childCount(self):
return len(self._children)
def parent(self):
return self._parent
def setParent(self, new_parent):
self._parent._children.remove(self)
self._parent = new_parent
new_parent._children.append(self)
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
def removeChild(self, position):
if position < 0 or position > len(self._children):
return False
child = self._children.pop(position)
child._parent = None
return True
def __repr__(self):
return self._name
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, root, parent=None):
super(TreeModel, self).__init__(parent)
self._rootNode = root
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
parentNode = self._rootNode
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
def columnCount(self, parent):
return 1
def data(self, index, role):
"""Return whatever the view should display"""
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
if index.column() == 0:
return node.name()
def index(self, row, column, parent):
if not parent.isValid():
# parent is not valid when it is the root node, since the "parent"
# method returns an empty QModelIndex
parentNode = self._rootNode
else:
parentNode = parent.internalPointer() # the node
childItem = parentNode.child(row)
return self.createIndex(row, column, childItem)
def parent(self, index):
node = index.internalPointer()
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
return self.createIndex(parentNode.row(), 0, parentNode)
def flags(self, index):
# Original, inherited flags:
original_flags = super(TreeModel, self).flags(index)
return (original_flags | QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
| QtCore.Qt.ItemIsDropEnabled)
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return 'Node name'
def removeRow(self, row, parent):
if not parent.isValid():
# parent is not valid when it is the root node, since the "parent"
# method returns an empty QModelIndex
parentNode = self._rootNode
else:
parentNode = parent.internalPointer() # the node
parentNode.removeChild(row)
return True
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.tree = QtWidgets.QTreeView()
self.layout = QtWidgets.QGridLayout()
self.main_widget = QtWidgets.QWidget()
self.layout.addWidget(self.tree, 0, 0)
self.main_widget.setLayout(self.layout)
self.setCentralWidget(self.main_widget)
# Note how these are added in a non-sorted fashion
root_node = Node('Hidden root')
Node(name='Node 3', parent=root_node) # add using parent param
n2 = Node(name='Node 2', parent=root_node) # add using parent param
Node(name='Node 1', parent=root_node) # add using parent param
n2.addChild(Node(name='Node 5')) # add using addChild
n2.addChild(Node(name='Node 6 (delete me)')) # add using addChild
n2.addChild(Node(name='Node 4')) # add using addChild
model = TreeModel(root=root_node)
self.tree.setModel(model)
self.tree.expandAll()
# Attempt to remove a node from within the model
node2 = model.index(1, 0, QtCore.QModelIndex()) # "Node 2" index
index = model.index(1, 0, parent=node2)
index_node = index.internalPointer()
print('index represents node "%s" (its parent node is "%s")' %
(index_node.name(), index.parent().internalPointer().name()))
print('Removing %s on row %s' % (index_node.name(), index.row()))
model.beginRemoveRows(index.parent(), index.row(), index.row())
success = model.removeRow(index.row(), parent=index.parent())
print('Removal was a succes?:', success)
model.endRemoveRows()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
I attempt to filter self.items's objects by their self.category attribute all from inside of QAbstractTableModel's data() method by comparing this attribute agains a text currently displayed in the QComboBox. Yet, the code doesn't function properly.
Shouldn't be QAbstractTableModel's data() method used "as a substitute to proxy model's accepts row() method?
Is it be possible to achieve the filtering without using QSortFilterProxyModel? If we have to use proxy to filter the model items what would be most Pythonic way of doing this?
from PySide import QtGui, QtCore
class Item(object):
def __init__(self):
self.ID=None
self.name=None
self.category=None
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.items = []
self.filterCategory = None
def rowCount(self, parent=QtCore.QModelIndex()):
return len( [item for item in self.items if item.category==self.filterCategory] )
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid(): return
row=index.row()
item=self.items[row]
if item.category!=self.filterCategory:
return
if role == QtCore.Qt.DisplayRole:
return self.items[row].name
if role == QtCore.Qt.UserRole:
return self.items[row]
def insertRows(self, row, item, column=1, index=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), row, row+1)
self.items.append(item)
self.endInsertRows()
def setFilter(self, comboText):
self.filterCategory = comboText
self.layoutChanged.emit()
def filterAcceptsRow(self, row, proc):
index=self.sourceModel().index(row, 0, proc)
item=self.sourceModel().data(index, QtCore.Qt.UserRole)
if not item: return True
resourceType=item.category
if self.filters.get(category)==False:
return False
if self.searchText and len(self.searchText)>0 and item.searchString(self.searchText)==False:
return False
return True
class MyWindow(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
vLayout=QtGui.QVBoxLayout(self)
self.setLayout(vLayout)
self.tableModel = TableModel()
self.ViewA=QtGui.QTableView(self)
self.ViewA.clicked.connect(self.viewClicked)
vLayout.addWidget(self.ViewA)
for row in range(5):
item=Item()
item.ID=row
if item.ID%2: item.category='Pet'
else: item.category='Birds'
item.name='%s_%s'%(item.category, row)
self.tableModel.insertRows(row, item)
self.ViewA.setModel(self.tableModel)
self.combo=QtGui.QComboBox()
self.combo.addItems(['Pet','Birds'])
self.combo.activated.connect(self.comboActivated)
vLayout.addWidget(self.combo)
currentComboCategory=self.combo.currentText()
self.tableModel.setFilter(currentComboCategory)
def viewClicked(self, indexClicked):
print('indexClicked() row: %s column: %s'%(indexClicked.row(), indexClicked.column() ))
def comboActivated(self, arg=None):
comboText=self.combo.currentText()
self.tableModel.setFilter(comboText)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
"no-more-proxy" code below shows how to sort and filter from inside of QAbstractTableModel instead of proxy:
import sys, os
from PyQt import QtGui, QtCore
class Item(object):
def __init__(self,ID=None,name=None,category=None,area=None):
self.ID=ID
self.name=name
self.category=category
self.area='South'
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.currentItems=[]
self.items = []
self.filterCategory = None
self.searchField = None
self.mainColumn=0
self.order=QtCore.Qt.SortOrder.DescendingOrder
def rowCount(self, parent=QtCore.QModelIndex()):
return len( self.currentItems )
def columnCount(self, parent=QtCore.QModelIndex()):
return 5
def data(self, index, role):
if not index.isValid(): return
row=index.row()
column=index.column()
item=self.currentItems[row]
if role == QtCore.Qt.DisplayRole:
if column==0: return item.ID
elif column==1: return item.name
elif column==2: return item.category
elif column==4 or column==5: return item.area
if role == QtCore.Qt.UserRole:
return item
def insertRows(self, row, item, column=1, index=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), row, row+1)
self.items.append(item)
self.endInsertRows()
def setFilter(self, comboText=None, searchText=None, mainColumn=None, order=None):
if comboText: self.filterCategory=comboText
if searchText: self.searchText=searchText
if mainColumn!=None: self.mainColumn=mainColumn
self.order=order
self.currentItems=[item for item in self.items if item.category==self.filterCategory]
if searchText:
self.currentItems=[item for item in self.currentItems if searchText in '%s%s%s'%(item.ID, item.name, item.category)]
values=[]
if self.mainColumn==0: values=[[item.ID, item, False] for item in self.currentItems]
elif self.mainColumn==1: values=[[item.name, item, False] for item in self.currentItems]
elif self.mainColumn==2: values=[[item.category, item, False] for item in self.currentItems]
elif self.mainColumn==3 or self.mainColumn==4: values=[[item.area, item, False] for item in self.currentItems]
keys=sorted([value[0] for value in values if isinstance(value, list)])
if self.order==QtCore.Qt.AscendingOrder: keys=list(reversed(keys))
filtered=[]
for key in keys:
for each in values:
if each[0]!=key: continue
if each[2]==True: continue
item=each[1]
filtered.append(item)
each[2]=True
if filtered: self.currentItems=filtered
self.layoutChanged.emit()
class ItemDelegate(QtGui.QItemDelegate):
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def flags(self, index):
if (index.column() == 1):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
else:
return QtCore.Qt.ItemIsEnabled
def createEditor(self, parent, option, index):
tableView=parent.parent()
model=tableView.model()
item=model.data(index, QtCore.Qt.UserRole)
combo=QtGui.QComboBox(parent)
combo.addItems(['South','West','North','East'])
combo.currentIndexChanged.connect(self.comboIndexChanged)
if item.area:
comboIndex=combo.findText(item.area)
if comboIndex>=0:
combo.setCurrentIndex(comboIndex)
else: combo.setCurrentIndex(0)
return combo
def comboIndexChanged(self):
self.commitData.emit(self.sender())
def setModelData(self, combo, model, index):
item=model.data(index, QtCore.Qt.UserRole)
comboText=combo.currentText()
item.area=comboText
class MyWindow(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
vLayout=QtGui.QVBoxLayout(self)
self.setLayout(vLayout)
self.tableModel = TableModel()
self.searchLine=QtGui.QLineEdit()
vLayout.addWidget(self.searchLine)
self.searchLine.textEdited.connect(self.searchLineEditied)
self.searchLine.returnPressed.connect(self.searchLineEditied)
self.tableView=QtGui.QTableView(self)
self.tableView.setSortingEnabled(True)
self.tableView.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
self.tableView.setShowGrid(False)
self.tableView.setSelectionBehavior(QtGui.QTableView.SelectRows)
self.tableView.setAlternatingRowColors(True)
self.delegate=ItemDelegate(self.tableView)
self.tableView.setItemDelegate(self.delegate)
self.tableView.clicked.connect(self.viewClicked)
vLayout.addWidget(self.tableView)
for row in range(15):
if row%2: category='Pet'
else: category='Birds'
item=Item(category=category, ID=row, name='%s_%s'%(category,row))
self.tableModel.insertRows(row, item)
self.tableView.setModel(self.tableModel)
self.combo=QtGui.QComboBox()
self.combo.addItems(['Pet','Birds'])
self.combo.activated.connect(self.comboActivated)
vLayout.addWidget(self.combo)
currentComboCategory=self.combo.currentText()
self.tableModel.setFilter(currentComboCategory)
self.horizontalHeader=self.tableView.horizontalHeader()
self.horizontalHeader.sortIndicatorChanged.connect(self.headerTriggered)
self.addComboDelegates()
def headerTriggered(self, mainColumn=None, order=None):
self.tableModel.setFilter(mainColumn=mainColumn, order=order)
self.deleteComboDelegates()
self.addComboDelegates()
def comboActivated(self, comboIndex=None):
self.deleteComboDelegates()
comboText=self.combo.currentText()
self.tableModel.setFilter(comboText=comboText)
self.addComboDelegates()
self.tableModel.layoutChanged.emit()
def searchLineEditied(self, searchText=None):
self.tableModel.setFilter(searchText=searchText)
def viewClicked(self, indexClicked):
item=self.tableModel.data(indexClicked, QtCore.Qt.UserRole)
print 'ID: %s, name: %s, category: %s'%(item.ID,item.name,item.category)
def deleteComboDelegates(self):
for row in range(self.tableModel.rowCount()):
index=self.tableModel.index(row, 3, QtCore.QModelIndex())
self.tableView.closePersistentEditor(index)
def addComboDelegates(self):
for row in range(self.tableModel.rowCount()):
index=self.tableModel.index(row, 3, QtCore.QModelIndex())
self.tableView.openPersistentEditor(index)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())