canFetchMore() and fetchMore() are not working as expected - python

I have problems implementing Qt tree model with lazy loading using canFetchMore() and fetchMore(). I have this code:
from PySide.QtCore import Qt, QAbstractItemModel, QModelIndex
from PySide.QtWidgets import QTreeView, QApplication
BATCH_SIZE = 100
class ObjectItem(object):
def __init__(self, parent, name, data, row):
self.parent = parent
self.name = name
self.data = data
self.row = row
self.hasChildren = False
self.canFetchMore = False
# ints have no children
if isinstance(data, int):
return
self.childCount = 0
self.childItems = []
if len(data) > 0:
self.canFetchMore = True
self.hasChildren = True
self.childCount = len(self.childItems)
if self.childCount > 0:
self.hasChildren = True
def fetchMore(self):
loadedCount = len(self.childItems)
totalCount = len(self.data)
remainder = totalCount - loadedCount
fetchCount = min(BATCH_SIZE, remainder)
for _ in range(fetchCount):
name = "[{}]".format(loadedCount)
value = self.data[loadedCount]
self.childItems.append(ObjectItem(self, name, value, loadedCount))
loadedCount += 1
self.canFetchMore = loadedCount < totalCount
return fetchCount
class MyTreeModel(QAbstractItemModel):
def __init__(self, data, parent=None):
super(MyTreeModel, self).__init__(parent)
self.rootItem = ObjectItem(None, "root", data, 0)
self.headerLabels = ["Name", "Type", "Value"]
def hasChildren(self, parent=QModelIndex()):
return parent.internalPointer().hasChildren if parent.isValid() else True
def rowCount(self, parent=QModelIndex()):
return parent.internalPointer().childCount if parent.isValid() else 1
def columnCount(self, parent=QModelIndex()):
return 3
def index(self, row, column, parent=QModelIndex()):
if not parent.isValid():
return self.createIndex(row, column, self.rootItem)
parentItem = parent.internalPointer()
return self.createIndex(row, column, parentItem.childItems[row])
def parent(self, index):
item = index.internalPointer()
if item == self.rootItem:
return QModelIndex()
parentItem = item.parent
return self.createIndex(parentItem.row, 0, parentItem)
def data(self, index, role):
item = index.internalPointer()
if role == Qt.DisplayRole:
if index.column() == 0:
return item.name
elif index.column() == 1:
return item.data.__class__.__name__
elif index.column() == 2:
return repr(item.data)
return None
def headerData(self, index, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.headerLabels[index]
return None
def canFetchMore(self, parent=QModelIndex()):
if parent.isValid():
return parent.internalPointer().canFetchMore
else:
return False
def fetchMore(self, parent=QModelIndex()):
parentItem = parent.internalPointer()
firstIndex = parentItem.childCount
fetchedCount = parentItem.fetchMore()
lastIndex = firstIndex + fetchedCount - 1
self.beginInsertRows(parent, firstIndex, lastIndex)
parentItem.childCount = len(parentItem.childItems)
self.endInsertRows()
# test data
data = [list(range(1000)), list(range(1000))]
app = QApplication([])
view = QTreeView()
model = MyTreeModel(data)
view.setModel(model)
view.show()
app.exec_()
It is probably the shortest version (simplified, unoptimized, not general enough etc.) of the code which can replicate the problem. I want to display a tree of lists of ints. Note that in the example above I have two lists of ints, each containing 1000 ints. But for some unknown reason, only he first 300 ints are displayed when the node is expanded and after I scroll to the very bottom. I would expect that the items should be added as I am scrolling down. However they are only added when I collapse and expand again the node.
In other words, the nodes are being added only as a reaction to collapsing and expanding of the parent nodes, not as a reaction to scrolling to the bottom.
What is the problem? Have I overlooked anything?
UPDATE: I simplified the code even more, compared to the first version.
UPDATE2: To compare it with a model without hierarchy, which works as expected (fetching more data as user scrolls down), look at this code:
from PySide.QtCore import Qt, QAbstractItemModel, QModelIndex
from PySide.QtGui import QApplication, QTreeView
BATCH_SIZE = 100
class LazyModel(QAbstractItemModel):
def __init__(self, totalCount, parent=None):
super(LazyModel, self).__init__(parent)
self.items = []
self.totalCount = totalCount
self.loadedCount = 0
def hasChildren(self, parent=QModelIndex()):
if not parent.isValid():
return True
else:
return False
def rowCount(self, parent=QModelIndex()):
return self.loadedCount
def columnCount(self, parent=QModelIndex()):
return 2
def index(self, row, column, parent=QModelIndex()):
return self.createIndex(row, column, None)
def parent(self, index):
return QModelIndex()
def data(self, index, role):
if role == Qt.DisplayRole:
if index.column() == 0:
return str(index.row())
else:
return str(self.items[index.row()])
return None
def canFetchMore(self, parent=QModelIndex()):
return self.loadedCount < self.totalCount
def fetchMore(self, parent=QModelIndex()):
remainder = self.totalCount - self.loadedCount
fetchCount = min(BATCH_SIZE, remainder)
for i in range(fetchCount):
self.items.append(len(self.items))
self.beginInsertRows(parent, self.loadedCount,
self.loadedCount + fetchCount - 1)
self.loadedCount += fetchCount
self.endInsertRows()
app = QApplication([])
view = QTreeView()
model = LazyModel(10000)
view.setModel(model)
view.show()
app.exec_()
Does it mean I got something wrong with hierarchy?

Related

Custom dialogbox (QWidget) as item delegate in QTreeView

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_()

QAbstractItemModel header on child tables

I created my own QAbstractItemModel with a tree-like structure and several sub tables like this:
group1
- order1
- item1
- item2
- order2
- item3
group2
- order3
- item4
- item5
- item6
I my goal is it to have a tree and a table view. When I click on an order in the tree view I want to display a table with all items (and their attributes) in a table. So far I got it working.
Now I obviously need headers for the table view, this is where I am somewhat stuck. I tried implementing the solution from this question but it is not working properly. This is what I got so far:
import sys
from PyQt5 import QtCore, QtWidgets
class Node(object):
def __init__(self, parent=None, item_count=0):
self.parent = parent
self.children = []
self.item_count = item_count
if self.parent is not None:
self.parent.add_child(self)
def add_child(self, child):
self.children.append(child)
child.parent = self
#property
def row(self):
if self.parent is None:
return 0
return self.parent.children.index(self)
#property
def child_item_count(self):
if len(self.children):
return self.children[0].item_count
return 1
#property
def child_count(self):
return len(self.children)
def child(self, row):
try:
return self.children[row]
except IndexError:
return None
def header_data(self):
return []
class ItemNode(Node):
def __init__(self, parent=None, name='', description='', price=''):
super(ItemNode, self).__init__(parent=parent, item_count=3)
self.name = name
self.description = description
self.price = price
def data(self, column):
if column == 0: return self.name
if column == 1: return self.description
if column == 2: return self.price
return None
class OrderNode(Node):
def __init__(self, parent=None, order_id='', order_date=''):
super(OrderNode, self).__init__(parent=parent, item_count=2)
self.order_id = order_id
self.order_date = order_date
def data(self, column):
if column == 0: return self.order_id
if column == 1: return self.order_date
return None
def header_data(self):
return ['Name', 'Description', 'Price']
class GroupNode(Node):
def __init__(self, parent=None, name='', description=''):
super(GroupNode, self).__init__(parent=parent, item_count=2)
self.name = name
self.description = description
def data(self, column):
if column == 0: return self.name
if column == 1: return self.description
return None
class ItemModel(QtCore.QAbstractItemModel):
def __init__(self, root=None):
if root is None:
root = Node()
self.root = root
super(ItemModel, self).__init__()
def rowCount(self, parent=None, *args, **kwargs):
node = self.get_node(parent)
return node.child_count
def columnCount(self, parent=None, *args, **kwargs):
node = self.get_node(parent)
return node.child_item_count
def index(self, row, column, parent=None, *args, **kwargs):
node = self.get_node(parent)
return self.createIndex(row, column, node.child(row))
def parent(self, index=None):
node = self.get_node(index)
if node.parent is None:
return QtCore.QModelIndex()
return self.createIndex(node.parent.row, 0, node.parent)
def data(self, index, role=QtCore.Qt.DisplayRole):
node = self.get_node(index)
if role == QtCore.Qt.DisplayRole:
return node.data(index.column())
if role == QtCore.Qt.UserRole + 1: # horizontal header role
return node.header_data()
def get_node(self, index):
if index and index.isValid():
node = index.internalPointer()
if node:
return node
return self.root
def is_order(self, index):
node = self.get_node(index)
return isinstance(node, OrderNode)
class ProxyItemModel(QtCore.QSortFilterProxyModel):
horizontal_header_role = QtCore.Qt.UserRole + 1
def __init__(self):
self.root_index = QtCore.QModelIndex()
super(ProxyItemModel, self).__init__()
def set_root_index(self, index):
self.root_index = self.mapToSource(index)
if self.sourceModel() and self.root_index.internalPointer() is not None:
self.headerDataChanged.emit(
QtCore.Qt.Horizontal, 0, self.sourceModel().columnCount(self.root_index)
)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if self.sourceModel() and self.root_index.isValid():
if orientation == QtCore.Qt.Horizontal:
role = self.horizontal_header_role
header_list = self.sourceModel().data(self.root_index, role)
if header_list and 0 <= section < len(header_list):
return header_list[section]
return super(ProxyItemModel, self).headerData(section, orientation, role)
def is_order(self, index):
source_index = self.mapToSource(index)
return self.sourceModel().is_order(source_index)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.central_widget = QtWidgets.QWidget(self)
self.setCentralWidget(self.central_widget)
central_layout = QtWidgets.QVBoxLayout(self)
self.central_widget.setLayout(central_layout)
self.treeView = QtWidgets.QTreeView(self)
self.tableView = QtWidgets.QTableView(self)
central_layout.addWidget(self.treeView)
central_layout.addWidget(self.tableView)
self.data_model = ItemModel()
self.set_up_data_model()
self.proxy_model = ProxyItemModel()
self.proxy_model.setSourceModel(self.data_model)
self.treeView.setModel(self.proxy_model)
self.treeView.header().hide()
self.tableView.setModel(self.proxy_model)
self.treeView.selectionModel().currentChanged.connect(self.set_root_index)
self.treeView.selectionModel().currentChanged.connect(self.hide_show_table)
self.show()
def set_root_index(self, index):
self.tableView.setRootIndex(index)
self.proxy_model.set_root_index(index)
def hide_show_table(self, index):
if self.proxy_model.is_order(index):
self.tableView.show()
else:
self.tableView.hide()
def set_up_data_model(self):
root = self.data_model.root
group1 = GroupNode(root, name='G1', description='Order Group 1')
group2 = GroupNode(root, name='G2', description='Order Group 2')
order1 = OrderNode(group1, order_id='123', order_date='17.10.2018')
item1 = ItemNode(order1, name='Item 1', price='345')
item2 = ItemNode(order1, name='Item 2', description='item number 2', price='99')
order2 = OrderNode(group1, order_id='987')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ui = MainWindow()
sys.exit(app.exec_())
Now this code results in the horizontal header not being displayed at all. However, when I substitute 0 <= section < len(header_list) in the headerData method with 0 < section < len(header_list), the header is displayed correctly - except for the first column which is just empty. Something must be going on with the code when section = 0 but for the life of me I can't figure out what it is. I had a look with the debugger and saw that for section = 0 both role and header_list have the expected values.
Any help will be greatly appreciated. I tried to delete everything that is not needed to get a minimal working example, it still turned out quite lengthy though, sorry about that.
You have to verify the section is Qt::Horizontal and the role is Qt::DisplayRole, in your case you did not do the second verification so it was overwritten generating that error.
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if self.sourceModel() and self.root_index.isValid():
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
header_list = self.sourceModel().data(self.root_index, self.horizontal_header_role)
if header_list and 0 <= section < len(header_list):
return header_list[section]
return super(ProxyItemModel, self).headerData(section, orientation, role)

How to use QitemDelegate with multiple proxy models stacked?

This question is related to this previous question:
how to use QSortFilterProxyModel for filter a 2d array?
i have been trying to stack several proxy model to display a 2d array of data into a qtableview.
#eyllanesc provided a really cool solution to my question but it doesn't seem to be compatible with qitemdelegate. When i add it to his example the delegate doesnt display as expected. Without the proxy3 it does show correctly.
import random
import math
from PyQt4 import QtCore, QtGui
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, columns, parent=None):
super(TableModel, self).__init__(parent)
self._columns = columns
self._data = data[:]
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid() or self._columns == 0:
return 0
return math.ceil(len(self._data )*1.0/self._columns)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self._columns
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
if role == QtCore.Qt.DisplayRole:
try:
value = self._data[ index.row() * self._columns + index.column() ]
return value
except:
pass
class Table2ListProxyModel(QtGui.QIdentityProxyModel):
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self.sourceModel().rowCount() * self.sourceModel().columnCount()
def mapFromSource(self, sourceIndex):
if sourceIndex.isValid() and sourceIndex.column() == 0 \
and sourceIndex.row() < self.rowCount():
r = sourceIndex.row()
c = sourceIndex.column()
row = c * sourceIndex.model().columnCount() + r
return self.index(row, 0)
return QtCore.QModelIndex()
def mapToSource(self, proxyIndex):
r = proxyIndex.row() / self.sourceModel().columnCount()
c = proxyIndex.row() % self.sourceModel().columnCount()
return self.sourceModel().index(r, c)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column)
class ListFilterProxyModel(QtGui.QSortFilterProxyModel):
def setThreshold(self, value):
setattr(self, "threshold", value)
self.invalidateFilter()
def filterAcceptsRow(self, row, parent):
if hasattr(self, "threshold"):
ix = self.sourceModel().index(row, 0)
val = ix.data()
if val is None:
return False
return int(val.toString()) < getattr(self, "threshold")
return True
class List2TableProxyModel(QtGui.QIdentityProxyModel):
def __init__(self, columns=1, parent=None):
super(List2TableProxyModel, self).__init__(parent)
self._columns = columns
def columnCount(self, parent=QtCore.QModelIndex()):
return self._columns
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return math.ceil(self.sourceModel().rowCount()/self._columns)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column)
def data(self, index, role=QtCore.Qt.DisplayRole):
r = index.row()
c = index.column()
row = r * self.columnCount() + c
if row < self.sourceModel().rowCount():
return super(List2TableProxyModel, self).data(index, role)
def mapFromSource(self, sourceIndex):
r = math.ceil(sourceIndex.row() / self.columnCount())
c = sourceIndex.row() % self.columnCount()
return self.index(r, c)
def mapToSource(self, proxyIndex):
if proxyIndex.isValid():
r = proxyIndex.row()
c = proxyIndex.column()
row = r * self.columnCount() + c
return self.sourceModel().index(row, 0)
return QtCore.QModelIndex()
class Delegate(QtGui.QItemDelegate):
def __init__(self, parent = None):
QtGui.QItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
number = str(index.data(QtCore.Qt.DisplayRole).toString())
widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
widget.setLayout( layout )
title = QtGui.QLabel("<font color='red'>"+number+"</font>")
layout.addWidget( title )
if not self.parent().tvf.indexWidget(index):
self.parent().tvf.setIndexWidget(
index,
widget
)
QtGui.QItemDelegate.paint(self, painter, option, index)
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
data = [random.choice(range(10)) for i in range(20)]
l = QtGui.QHBoxLayout(self)
splitter = QtGui.QSplitter()
l.addWidget(splitter)
tv = QtGui.QTableView()
lv = QtGui.QListView()
lvf = QtGui.QListView()
self.tvf = QtGui.QTableView()
delegate = Delegate(self)
self.tvf.setItemDelegate(delegate)
model = TableModel(data, 3, self)
proxy1 = Table2ListProxyModel(self)
proxy1.setSourceModel(model)
proxy2 = ListFilterProxyModel(self)
proxy2.setSourceModel(proxy1)
proxy2.setThreshold(5)
proxy3 = List2TableProxyModel(3, self)
proxy3.setSourceModel(proxy2)
tv.setModel(model)
lv.setModel(proxy1)
lvf.setModel(proxy2)
self.tvf.setModel(proxy3)
splitter.addWidget(tv)
splitter.addWidget(lv)
splitter.addWidget(lvf)
splitter.addWidget(self.tvf )
if __name__=="__main__":
import sys
a=QtGui.QApplication(sys.argv)
w=Widget()
w.show()
sys.exit(a.exec_())
Here is the expected result I am looking for:
and this is what it look like when i bypass the proxy model (the numbers are red).
replacing :
self.tvf.setModel(proxy3)
to
self.tvf.setModel(model)
You are incorrectly using the delegate that works sometimes if and sometimes not. The concepts of delegate and IndexWidget are 2 alternatives, the first one is a low level painting but also low cost, the second instead a widget is embedded in the item task simple but expensive since a widget is much more than a simple painted , also has a logic.
In this case the solution is to use a QStyledItemDelegate and overwrite the initStyleOption() method by modifying the palette.
class Delegate(QtGui.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.palette.setBrush(QtGui.QPalette.Text, QtGui.QColor("red"))
Note: Note: it is not necessary to write
def __init__(self, parent = None):
QtGui.QItemDelegate.__init__(self, parent)
since it is not modified.

how to use QSortFilterProxyModel for filter a 2d array?

I have been struggling with this for a while, and i am not even sure i am using the right pyqt classes.
I have a QTableView that display a 2d array of integers and i wish to filter it to only show the integer under a value.
This is the example:
from PyQt4 import QtCore, QtGui
import random
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
super(TableModel, self).__init__(parent)
self._columns = 3
self._parent = parent
def rowCount(self, parent=QtCore.QModelIndex()):
return int( float( len( self._parent.data ) ) / float( self._columns ) )+1
def columnCount(self, parent=QtCore.QModelIndex()):
return self._columns
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if role == QtCore.Qt.DisplayRole:
try:
value = self._parent.data[ index.row() * self._columns + index.column() ]
except:
pass
else:
return value
return None
class tableProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(tableProxyModel, self).__init__(parent)
self._parent = parent
self.filterColumn = []
self.filterRow = []
self.maxFilter =0
def setFilter(self, value):
#
# this will find the 2d position of the datas that need to be kept visible
# we need a list of the row and column axis
#
self.maxFilter = value
self.filterColumn = []
self.filterRow = []
for i in self._parent.data:
if i < self.maxFilter:
positionInList = [j for j,x in enumerate( self._parent.data ) if x == i][0]
filterRow, filterColumn = self.getPosition( positionInList )
self.filterColumn.append(filterColumn)
self.filterRow.append(filterRow)
self.invalidateFilter()
def getPosition(self, value):
#
# convert data list to a 2d array
#
leftOver = value%self._parent._tm._columns
filterRow = (value-leftOver) / self._parent._tm._columns
filterColumn = leftOver
return filterRow, filterColumn
def filterAcceptsRow(self, row, parent):
model = self.sourceModel()
if row in self.filterRow:
return True
else:
return False
def filterAcceptsColumn(self, column, parent):
model = self.sourceModel()
if column in self.filterColumn:
return True
else:
return False
if __name__=="__main__":
from sys import argv, exit
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.data = [ d for d in range(10) ]
###################################################
## this line shuffle the list, if you quote this line and keep the list in order , then the example works as expected
random.shuffle(self.data)
print self.data
l=QtGui.QVBoxLayout( self )
self._tm=TableModel( self )
self._tv=QtGui.QTableView( )
self._tpm=tableProxyModel( self )
self._tpm.setSourceModel( self._tm )
self._tv.setModel( self._tpm )
l.addWidget( self._tv )
#################################################
## apply the filter, only show numbers under 5
self._tpm.setFilter(5)
a=QtGui.QApplication(argv)
w=Widget()
w.show()
w.raise_()
exit(a.exec_())
If you execute this example, you can see it doesn't only show the values under 5 ...
How can i filter a QTableView filled with random values ? I wish to use the model/view system here.
QSortFilterProxyModel only filters columns or rows, it does not filter elements, so the strategy for this case is to convert the table into a list, then filter the list since filtering a row is equivalent to filtering an element and finally converting the list into a table.
import random
import math
from PyQt4 import QtCore, QtGui
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, columns, parent=None):
super(TableModel, self).__init__(parent)
self._columns = columns
self._data = data[:]
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid() or self._columns == 0:
return 0
return math.ceil(len(self._data )*1.0/self._columns)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self._columns
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
if role == QtCore.Qt.DisplayRole:
try:
value = self._data[ index.row() * self._columns + index.column() ]
return value
except:
pass
class Table2ListProxyModel(QtGui.QIdentityProxyModel):
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self.sourceModel().rowCount() * self.sourceModel().columnCount()
def mapFromSource(self, sourceIndex):
if sourceIndex.isValid() and sourceIndex.column() == 0 \
and sourceIndex.row() < self.rowCount():
r = sourceIndex.row()
c = sourceIndex.column()
row = c * sourceIndex.model().columnCount() + r
return self.index(row, 0)
return QtCore.QModelIndex()
def mapToSource(self, proxyIndex):
r = proxyIndex.row() / self.sourceModel().columnCount()
c = proxyIndex.row() % self.sourceModel().columnCount()
return self.sourceModel().index(r, c)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column)
class ListFilterProxyModel(QtGui.QSortFilterProxyModel):
def setThreshold(self, value):
setattr(self, "threshold", value)
self.invalidateFilter()
def filterAcceptsRow(self, row, parent):
if hasattr(self, "threshold"):
ix = self.sourceModel().index(row, 0)
val = ix.data()
if val is None:
return False
return val < getattr(self, "threshold")
return True
class List2TableProxyModel(QtGui.QIdentityProxyModel):
def __init__(self, columns=1, parent=None):
super(List2TableProxyModel, self).__init__(parent)
self._columns = columns
def columnCount(self, parent=QtCore.QModelIndex()):
return self._columns
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return math.ceil(self.sourceModel().rowCount()/self._columns)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column)
def data(self, index, role=QtCore.Qt.DisplayRole):
r = index.row()
c = index.column()
row = r * self.columnCount() + c
if row < self.sourceModel().rowCount():
return super(List2TableProxyModel, self).data(index, role)
def mapFromSource(self, sourceIndex):
r = math.ceil(sourceIndex.row() / self.columnCount())
c = sourceIndex.row() % self.columnCount()
return self.index(r, c)
def mapToSource(self, proxyIndex):
if proxyIndex.isValid():
r = proxyIndex.row()
c = proxyIndex.column()
row = r * self.columnCount() + c
return self.sourceModel().index(row, 0)
return QtCore.QModelIndex()
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
data = [random.choice(range(10)) for i in range(20)]
l = QtGui.QHBoxLayout(self)
splitter = QtGui.QSplitter()
l.addWidget(splitter)
tv = QtGui.QTableView()
lv = QtGui.QListView()
lvf = QtGui.QListView()
tvf = QtGui.QTableView()
model = TableModel(data, 3, self)
proxy1 = Table2ListProxyModel(self)
proxy1.setSourceModel(model)
proxy2 = ListFilterProxyModel(self)
proxy2.setSourceModel(proxy1)
proxy2.setThreshold(5)
proxy3 = List2TableProxyModel(3, self)
proxy3.setSourceModel(proxy2)
tv.setModel(model)
lv.setModel(proxy1)
lvf.setModel(proxy2)
tvf.setModel(proxy3)
splitter.addWidget(tv)
splitter.addWidget(lv)
splitter.addWidget(lvf)
splitter.addWidget(tvf)
if __name__=="__main__":
import sys
a=QtGui.QApplication(sys.argv)
w=Widget()
w.show()
sys.exit(a.exec_())

Checkboxes in treeview update slowly

I implemented a cutom model for treeview for having checkboxes inside the treeview. If I check a parent node all child nodes should be chekced automatically. This basically works but der is a time lack between checking the parent node and updating the subnodes.
from PyQt4 import QtCore, QtGui
import sys
class Node(object):
def __init__(self, name, parent=None, checked=False):
self._name = name
self._children = []
self._parent = parent
self._checked = checked
if parent is not None:
parent.addChild(self)
def addChild(self, child):
self._children.append(child)
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 name(self):
return self._name
def checked(self):
return self._checked
def setChecked(self, state):
self._checked = state
for c in self._children:
c.setChecked(state)
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)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, root, parent=None):
super().__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:
if index.column() == 0:
return node.name()
if role == QtCore.Qt.CheckStateRole:
if node.checked():
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.CheckStateRole:
node = index.internalPointer()
node.setChecked(not node.checked())
return True
return False
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
return "Nodes"
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsUserCheckable
def parent(self, index):
node = self.getNode(index)
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
return self.createIndex(parentNode.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
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
parentNode = self.getNode(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
for row in range(rows):
success = parentNode.removeChild(position)
self.endRemoveRows()
return success
def main_simple():
app = QtGui.QApplication(sys.argv)
rootNode = Node("Root")
n1 = Node("Node1", rootNode)
n2 = Node("Node2", rootNode)
n3 = Node("Node3", rootNode)
n1_1 = Node("Node1 1", n1)
n1_2 = Node("Node1 2", n1)
n1_3 = Node("Node1 3", n1)
n2_1 = Node("Node2 1", n2)
n2_2 = Node("Node2 2", n2)
n2_3 = Node("Node2 3", n2)
n3_1 = Node("Node3 1", n3)
n3_2 = Node("Node3 2", n3)
n3_3 = Node("Node3 3", n3)
model = TreeModel(rootNode)
treeView = QtGui.QTreeView()
treeView.show()
treeView.setModel(model)
sys.exit(app.exec_())
if __name__ == '__main__':
main_simple()
How can I avoid this lack so that the userinterface is more smoothly?
The model should emit the dataChanged() signal for the children when they are toggled, so that the view can update their checkboxes instantly. According to the documentation, setData should also emit dataChanged for the item that was explicitly changed.
This should work:
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.CheckStateRole:
node = index.internalPointer()
node.setChecked(not node.checked())
self.dataChanged.emit(index, index)
self.emitDataChangedForChildren(index)
return True
return False
def emitDataChangedForChildren(self, index):
count = self.rowCount(index)
if count:
self.dataChanged.emit(index.child(0, 0), index.child(count-1, 0))
for child in range(count):
self.emitDataChangedForChildren(index.child(child, 0))
As per flonks answer above, a non-iterative solution that worked for me is as follows:
C++: emit dataChanged(QModelIndex(), QModelIndex());
Python: self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
This should update all items in the tree.

Categories