PyQt Custom QAbstractTableModel for combobox - pyqt

In the following sample code I want to use a QDataWidgetMapper together with a Combobox to display data retrieved from a database.
Unfortunately I can't e.g. preselect the choice of the combobox and I can't retrieve the database id on selecting a value in the combobox. Therefore I think I am really missing something fundamental here.
I reduced my program to this sample code which I hope illustrates my problem.
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class ComboModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(ComboModel, self).__init__()
# The data would normally be read from a database (not using the Qt SQL classes)
self.dbIds = [ 13, 49, 81 ]
self.dbValues = [ 'Value 1', 'Value 2', 'Value 3' ]
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.dbIds)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return 2
def data(self, index, role=QtCore.Qt.DisplayRole):
print 'ComboModel data'
if not index.isValid() or ( role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole ):
print 'ComboModel return invalid QVariant'
return QtCore.QVariant()
if index.column() == 0:
return QtCore.QVariant(self.dbIds[index.row()])
if index.column() == 1:
return QtCore.QVariant(self.dbValues[index.row()])
print 'ComboModel return invalid QVariant'
return QtCore.QVariant()
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(TableModel, self).__init__()
self.editValue = 'Value to edit'
self.comboValue = 49 # This is the database id of the selected entry
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return 1
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return 2
def data(self, index, role=QtCore.Qt.DisplayRole):
print 'TableModel data'
if not index.isValid() or ( role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole ):
print 'TableModel return invalid QVariant'
return QtCore.QVariant()
if index.column() == 0:
return QtCore.QVariant(self.editValue)
if index.column() == 1:
return QtCore.QVariant(self.comboValue)
print 'TableModel return invalid QVariant'
return QtCore.QVariant()
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
print 'TableModel setData'
if not index.isValid():
return False
if index.column() == 0:
print 'new Value: ' + value.toString()
self.editValue = value.toString()
if index.column() == 1:
print 'new combobox value'
print 'new Value: ' + value.toString()
self.comboValue = value.toString() # how do I get the dbId?
return True
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.edit = QtGui.QLineEdit(self)
self.edit.setGeometry(10, 40, 80, 30)
self.combo = QtGui.QComboBox(self)
self.combo.setGeometry(10, 80, 80, 30)
self.combo_model = ComboModel()
self.combo.setModel(self.combo_model)
self.combo.setModelColumn(1)
self.model = TableModel()
self.mapper = QtGui.QDataWidgetMapper()
self.mapper.setModel(self.model)
self.mapper.addMapping(self.edit, 0)
self.mapper.addMapping(self.combo, 1)
self.mapper.toFirst();
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Related

In PyQt5 how do I properly move rows in a QTableView using Drag&Drop

I (simply) want to be able to use a QTableViews Drag&Drop mechanism to move existing rows. I found lots of sources (e.g. here, here or here) which describe some aspects of dragging, dropping, inserting etc. but I'm still struggling to make it work for my case.
Here is what the solution I'm looking for should be capable of:
work on a 'Qt-free' data structure, e.g. a list of tuples.
operate on the data structure. i.e. when the order of items gets
modified in the view it should be modified in the data structure
look and feel of standard drag&drop enabled lists:
select/move whole rows
show a drop indicator for the whole line
Further operations like deleting/editing of cells must still be possible
i.e. not be touched by the drag&drop approach
This tutorial shows a solution which is very close to what I need but it uses a QStandardItemModel rather than QAbstractTableModel which looks semi-optimal to me because I have to operate on a 'mirrored' data structure based on QStandardItem which is needed by QStandardItemModel (am I right?)
The code which represents my current progress is appended below.
Currently I see two possible approaches:
Approach 1: Implement against QAbstractTableModel and implement all needed events/slots to modify the underlying data structure:
* pro: most generic approach
* pro: no redundant data
* con: I don't know how to get informed about a finished drag&drop
operation and what index got moved where
In the code I've appended I trace all related methods I know of and print out all arguments. Here is what I get when I drag line 2 onto line 3
dropMimeData(data: ['application/x-qabstractitemmodeldatalist'], action: 2, row: -1, col: -1, parent: '(row: 2, column: 0, valid: True)')
insertRows(row=-1, count=1, parent=(row: 2, column: 0, valid: True))
setData(index=(row: 0, column: 0, valid: True), value='^line1', role=0)
setData(index=(row: 0, column: 1, valid: True), value=1, role=0)
removeRows(row=1, count=1, parent=(row: -1, column: -1, valid: False))
This output raises the following questions for me:
why do moveRow/moveRows not get called? when would they be called?
why are insertRow/removeRow not called but only insertRows/removeRows?
what does a row index of -1 mean?
what can I do with mime data provided in dropMimeData? Should I use it to copy data later?
Approach 2: Use QStandardItemModel and modify your data in parallel to the data managed by QStandardItemModel.
* pro: there's a working example
* contra: you manage a redundant data structure which has to be consistent
with another internally managed data structure.
* contra: didn't find out how to do that exactly neither
Here is my current approach using QAbstractTableModel:
from PyQt5 import QtWidgets, QtCore, QtGui
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data
def columnCount(self, parent):
return 2
def rowCount(self, parent):
return len(self._data)
def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
return (('Regex', 'Category')[column]
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
else None)
def data(self, index, role: QtCore.Qt.ItemDataRole):
if role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
return None
print("data(index=%s, role=%r)" % (self._index2str(index), self._role2str(role)))
return (self._data[index.row()][index.column()]
if index.isValid()
and role in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}
and index.row() < len(self._data)
else None)
def setData(self, index: QtCore.QModelIndex, value, role: QtCore.Qt.ItemDataRole):
print("setData(index=%s, value=%r, role=%r)" % (self._index2str(index), value, role))
return super().setData(index, value, role)
def flags(self, index):
return (
super().flags(index)
| QtCore.Qt.ItemIsDropEnabled
| (QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled)
if index.isValid() else QtCore.Qt.NoItemFlags)
def dropMimeData(self, data, action, row, col, parent: QtCore.QModelIndex):
"""Always move the entire row, and don't allow column 'shifting'"""
print("dropMimeData(data: %r, action: %r, row: %r, col: %r, parent: %r)" % (
data.formats(), action, row, col, self._index2str(parent)))
assert action == QtCore.Qt.MoveAction
return super().dropMimeData(data, action, row, 0, parent)
def supportedDragActions(self):
return QtCore.Qt.MoveAction
def supportedDropActions(self):
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def removeRow(self, row: int, parent=None):
print("removeRow(row=%r):" % (row))
return super().removeRow(row, parent)
def removeRows(self, row: int, count: int, parent=None):
print("removeRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().removeRows(row, count, parent)
def insertRow(self, index, parent=None):
print("insertRow(row=%r, count=%r):" % (row, count))
return super().insertRow(row, count, parent)
def insertRows(self, row: int, count: int, parent: QtCore.QModelIndex = None):
print("insertRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().insertRows(row, count, parent)
#staticmethod
def _index2str(index):
return "(row: %d, column: %d, valid: %r)" % (index.row(), index.column(), index.isValid())
#staticmethod
def _role2str(role: QtCore.Qt.ItemDataRole) -> str:
return "%s (%d)" % ({
QtCore.Qt.DisplayRole: "DisplayRole",
QtCore.Qt.DecorationRole: "DecorationRole",
QtCore.Qt.EditRole: "EditRole",
QtCore.Qt.ToolTipRole: "ToolTipRole",
QtCore.Qt.StatusTipRole: "StatusTipRole",
QtCore.Qt.WhatsThisRole: "WhatsThisRole",
QtCore.Qt.SizeHintRole: "SizeHintRole",
QtCore.Qt.FontRole: "FontRole",
QtCore.Qt.TextAlignmentRole: "TextAlignmentRole",
QtCore.Qt.BackgroundRole: "BackgroundRole",
#QtCore.Qt.BackgroundColorRole:
QtCore.Qt.ForegroundRole: "ForegroundRole",
#QtCore.Qt.TextColorRole
QtCore.Qt.CheckStateRole: "CheckStateRole",
QtCore.Qt.InitialSortOrderRole: "InitialSortOrderRole",
}[role], role)
class MyTableView(QtWidgets.QTableView):
class DropmarkerStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""Draw a line across the entire row rather than just the column we're hovering over.
This may not always work depending on global style - for instance I think it won't
work on OSX."""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
def __init__(self):
super().__init__()
self.setStyle(self.DropmarkerStyle())
# only allow rows to be selected
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# disallow multiple rows to be selected
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.setDragEnabled(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.setDropIndicatorShown(True) # default
self.setAcceptDrops(False) # ?
self.viewport().setAcceptDrops(True) # ?
self.setDragDropOverwriteMode(False)
class HelloWindow(QtWidgets.QMainWindow):
def __init__(self) -> None:
super().__init__()
model = MyModel([("^line0", 0),
("^line1", 1),
("^line2", 2),
("^line3", 3)])
table_view = MyTableView()
table_view.setModel(model)
table_view.verticalHeader().hide()
table_view.setShowGrid(False)
self.setCentralWidget(table_view)
def main():
app = QtWidgets.QApplication([])
window = HelloWindow()
window.show()
app.exec_()
if __name__ == "__main__":
main()
I have no clue yet how to make QAbstractTableModel or QAbstractItemModel work as described but I finally found a way to make the QTableView handle drag & drop and just makes the model move a row.
Here's the code:
from PyQt5 import QtWidgets, QtCore
class ReorderTableModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data
def columnCount(self, parent=None) -> int:
return 2
def rowCount(self, parent=None) -> int:
return len(self._data) + 1
def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
return (('Regex', 'Category')[column]
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
else None)
def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole):
if not index.isValid() or role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
return None
return (self._data[index.row()][index.column()] if index.row() < len(self._data) else
"edit me" if role == QtCore.Qt.DisplayRole else "")
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
# https://doc.qt.io/qt-5/qt.html#ItemFlag-enum
if not index.isValid():
return QtCore.Qt.ItemIsDropEnabled
if index.row() < len(self._data):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
def supportedDropActions(self) -> bool:
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def relocateRow(self, row_source, row_target) -> None:
row_a, row_b = max(row_source, row_target), min(row_source, row_target)
self.beginMoveRows(QtCore.QModelIndex(), row_a, row_a, QtCore.QModelIndex(), row_b)
self._data.insert(row_target, self._data.pop(row_source))
self.endMoveRows()
class ReorderTableView(QtWidgets.QTableView):
"""QTableView with the ability to make the model move a row with drag & drop"""
class DropmarkerStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""Draw a line across the entire row rather than just the column we're hovering over.
This may not always work depending on global style - for instance I think it won't
work on OSX."""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
self.setStyle(self.DropmarkerStyle())
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != QtCore.Qt.MoveAction and
self.dragDropMode() != QtWidgets.QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
self.model().relocateRow(from_index, to_index)
event.accept()
super().dropEvent(event)
class Testing(QtWidgets.QMainWindow):
"""Demonstrate ReorderTableView"""
def __init__(self):
super().__init__()
view = ReorderTableView(self)
view.setModel(ReorderTableModel([
("a", 1),
("b", 2),
("c", 3),
("d", 4),
]))
self.setCentralWidget(view)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
test = Testing()
raise SystemExit(app.exec_())
MyData class should be inherited from QStandardItemModel
revised your code to solve drag-drop and extension class function call issue.
from PyQt5 import (QtWidgets, QtCore)
from PyQt5.QtWidgets import (QApplication, QTableView)
from PyQt5.QtGui import (QStandardItem, QStandardItemModel)
class MyModel(QStandardItemModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data
for (index, data) in enumerate(data):
first = QStandardItem('Item {}'.format(index))
first.setDropEnabled(False)
first.setEditable(False)
second = QStandardItem(data[0])
second.setDropEnabled(False)
second.setEditable(False)
self.appendRow([first, second])
def columnCount(self, parent):
return 2
def rowCount(self, parent):
return len(self._data)
def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
return (('Regex', 'Category')[column]
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
else None)
def data(self, index, role: QtCore.Qt.ItemDataRole):
if role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
return None
print("data(index=%s, role=%r)" % (self._index2str(index), self._role2str(role)))
return (self._data[index.row()][index.column()]
if index.isValid() and role in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole} and index.row() < len(
self._data)
else None)
def setData(self, index: QtCore.QModelIndex, value, role: QtCore.Qt.ItemDataRole):
print("setData(index=%s, value=%r, role=%r)" % (self._index2str(index), value, role))
return super().setData(index, value, role)
def flags(self, index):
return (
super().flags(index)
| QtCore.Qt.ItemIsDropEnabled
| (QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled)
if index.isValid() else QtCore.Qt.NoItemFlags)
def dropMimeData(self, data, action, row, col, parent: QtCore.QModelIndex):
"""Always move the entire row, and don't allow column 'shifting'"""
print("dropMimeData(data: %r, action: %r, row: %r, col: %r, parent: %r)" % (
data.formats(), action, row, col, self._index2str(parent)))
assert action == QtCore.Qt.MoveAction
return super().dropMimeData(data, action, row, 0, parent)
def supportedDragActions(self):
return QtCore.Qt.MoveAction
def supportedDropActions(self):
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def removeRow(self, row: int, parent=None):
print("removeRow(row=%r):" % (row))
return super().removeRow(row, parent)
def removeRows(self, row: int, count: int, parent=None):
print("removeRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().removeRows(row, count, parent)
def insertRow(self, index, parent=None):
print("insertRow(row=%r, count=%r):" % (row, count))
return super().insertRow(row, count, parent)
def insertRows(self, row: int, count: int, parent: QtCore.QModelIndex = None):
print("insertRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
return super().insertRows(row, count, parent)
#staticmethod
def _index2str(index):
return "(row: %d, column: %d, valid: %r)" % (index.row(), index.column(), index.isValid())
#staticmethod
def _role2str(role: QtCore.Qt.ItemDataRole) -> str:
return "%s (%d)" % ({
QtCore.Qt.DisplayRole: "DisplayRole",
QtCore.Qt.DecorationRole: "DecorationRole",
QtCore.Qt.EditRole: "EditRole",
QtCore.Qt.ToolTipRole: "ToolTipRole",
QtCore.Qt.StatusTipRole: "StatusTipRole",
QtCore.Qt.WhatsThisRole: "WhatsThisRole",
QtCore.Qt.SizeHintRole: "SizeHintRole",
QtCore.Qt.FontRole: "FontRole",
QtCore.Qt.TextAlignmentRole: "TextAlignmentRole",
QtCore.Qt.BackgroundRole: "BackgroundRole",
# QtCore.Qt.BackgroundColorRole:
QtCore.Qt.ForegroundRole: "ForegroundRole",
# QtCore.Qt.TextColorRole
QtCore.Qt.CheckStateRole: "CheckStateRole",
QtCore.Qt.InitialSortOrderRole: "InitialSortOrderRole",
}[role], role)
class MyTableView(QTableView):
class DropMarkerStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""Draw a line across the entire row rather than just the column we're hovering over.
This may not always work depending on global style - for instance I think it won't
work on OSX."""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
def __init__(self):
super().__init__()
self.setStyle(self.DropMarkerStyle())
self.verticalHeader().hide()
self.setShowGrid(False)
# only allow rows to be selected
self.setSelectionBehavior(self.SelectRows)
# disallow multiple rows to be selected
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
class HelloWindow(QtWidgets.QMainWindow):
def __init__(self) -> None:
super().__init__()
model = MyModel([("^line0", 0),
("^line1", 1),
("^line2", 2),
("^line3", 3)])
table_view = MyTableView()
table_view.setModel(model)
self.setCentralWidget(table_view)
def main():
app = QApplication([])
window = HelloWindow()
window.show()
app.exec_()
if __name__ == "__main__":
main()

PyQt5, Can't use mouse wheel to scroll within a QTableView displaying a pandas dataframe

I use a QtableView similiar to this one. Everything works fine.
My problem is that, that I can't scroll with the mousewheel. Also scrolling with pressing left mouse button and move the scrollbar don't work.
I can only use the arrows to scroll up and down.
Here is the code:
class PandasModel(QAbstractTableModel):
def __init__(self, data, parent=None):
"""
:param data: a pandas dataframe
:param parent:
"""
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
current_column = index.column()
current_row = index.row()
gold_color = QColor(235, 201, 52)
lightred_color = QColor('#FF5858')
knallrot = QColor('#FF0000')
shine_yellow = QColor('#FFFF00')
greeny = QColor(92, 235, 52)
white_color = QColor(QtCore.Qt.white)
black_color = QColor(QtCore.Qt.black)
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
if role == Qt.BackgroundColorRole: # The role for backgrounbd color of a cell
if current_column == 7:
it = self._data.iloc[index.row(), current_column] # Finds the specific data (second column) to test and assigns it to the variable "it"
if 20 > it > 10:
return QBrush(gold_color)
if it >= 20:
return QBrush(shine_yellow)
if role == Qt.BackgroundColorRole: # The role for backgrounbd color of a cell
if current_column == 5:
it = self._data.iloc[index.row(), current_column] # Finds the specific data (second column) to test and assigns it to the variable "it"
if it > 100000:
return QBrush(greeny)
if 10000 > it >= 1000:
return QBrush(lightred_color)
if it < 1000:
return QBrush(knallrot)
if current_column == 2:
it = self._data.iloc[index.row(), current_column] # Finds the specific data (second column) to test and assigns it to the variable "it"
if it > 100000:
return QBrush(greeny)
if 10000 > it >= 1000:
return QBrush(lightred_color)
if it < 1000:
return QBrush(knallrot)
if role == Qt.TextColorRole:
return QColor(39, 68, 209)
if role == Qt.BackgroundColorRole and index == 7:
return QColor(235, 201, 52)
if role == Qt.FontRole and index == 7:
return QFont("Helvetica", 12, QFont.Bold, );
return None
def headerData(self, rowcol, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[rowcol]
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
return self._data.index[rowcol]
if role == Qt.BackgroundColorRole and rowcol == 7:
return QColor(235, 201, 52) #gold
elif role == Qt.BackgroundColorRole and rowcol == 6:
return QColor(235, 67, 52) #red
elif role == Qt.BackgroundColorRole and rowcol == 5:
return QColor(235, 67, 52) #red
elif role == Qt.BackgroundColorRole and rowcol == 4:
return QColor(235, 67, 52) #red
elif role == Qt.BackgroundColorRole and rowcol == 3:
return QColor(92, 235, 52) #green
elif role == Qt.BackgroundColorRole and rowcol == 2:
return QColor(92, 235, 52) #green
elif role == Qt.BackgroundColorRole and rowcol == 1:
return QColor(92, 235, 52) #green
return None
def flags(self, index):
flags = super(self.__class__, self).flags(index)
#flags |= QtCore.Qt.ItemIsEditable
flags |= QtCore.Qt.ItemIsSelectable
flags |= QtCore.Qt.ItemIsEnabled
flags |= QtCore.Qt.ItemIsDragEnabled
flags |= QtCore.Qt.ItemIsDropEnabled
return flags
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
try:
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(self._data.columns[Ncol], ascending=not order)
self.layoutChanged.emit()
except Exception as e:
print(e)
From the main.py it's called like this:
self.tabCrawledresult = QTabWidget()
self.tabCrawledresult.layout = QGridLayout()
self.tabCrawledresult.setLayout(self.tabCrawledresult.layout)
self.tabs.addTab(self.tabCrawledresult, "Results")
self.view_minmax = QTableView(self.tabCrawledresult)
self.modelminmax = gui_pandasModel_sort.PandasModel(df)
self.view_minmax.setModel(self.modelminmax)
self.view_minmax.resize(1000,500) # is it possible to fit the size to width of headers?
self.view_minmax.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.view_minmax.setSortingEnabled(True)
self.view_minmax.sortByColumn(7, Qt.DescendingOrder)
self.view_minmax.setAlternatingRowColors(True)
self.view_minmax.setStyleSheet("alternate-background-color: rgb(209, 209, 209)"
"; background-color: rgb(244, 244, 244);")
self.view_minmax.show()
And how is it possible to fit the size of the QTableView to the width of headers?
First. The Class PandasModel is correct and make no troubles. What I have done in the main.py was wrong. I display the TableView in a QTabWidget and want to have an export button in the bottom of it. So my ugly solution is, to put a "layer" of empty QLabelWidgets on the Tab put then the TableView over it.
Here is my complete example with some Data.
import sys
from PyQt5 import QtCore
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QFileDialog,
QGridLayout, QPushButton, QTableView, QLabel, QHeaderView
import numpy as np
import pandas as pd
from functools import partial
def createDF():
df = pd.DataFrame(np.random.randint(0,11,size=(50, 5)), columns=list(['A','B','C',
'D','Tree']))
print(df)
return df
trees = { 1: 'Arborvitae (Thuja occidentalis)',
2: 'Black Ash (Fraxinus nigra)',
3: 'White Ash (Fraxinus americana)',
4: 'Bigtooth Aspen (Populus grandidentata)',
5: 'Quaking Aspen (Populus tremuloides)',
6: 'Basswood (Tilia americana)',
7: 'American Beech (Fagus grandifolia)',
8: 'Black Birch (Betula lenta)',
9: 'Gray Birch (Betula populifolia)',
10: 'Paper Birch (Betula papyrifera)'}
class PandasModel(QAbstractTableModel):
def __init__(self, data, parent=None):
"""
:param data: a pandas dataframe
:param parent:
"""
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, rowcol, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[rowcol]
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
return self._data.index[rowcol]
def flags(self, index):
flags = super(self.__class__, self).flags(index)
#flags |= QtCore.Qt.ItemIsEditable
flags |= QtCore.Qt.ItemIsSelectable
flags |= QtCore.Qt.ItemIsEnabled
flags |= QtCore.Qt.ItemIsDragEnabled
flags |= QtCore.Qt.ItemIsDropEnabled
return flags
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
try:
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(self._data.columns[Ncol],
ascending=not order)
self.layoutChanged.emit()
except Exception as e:
print(e)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.resize(400, 600)
self.tabs = QTabWidget()
self.tabs.layout = QGridLayout()
self.tabs.setLayout(self.tabs.layout)
self.grid = QGridLayout()
self.grid.addWidget(self.tabs)
self.setLayout(self.grid)
self.setCentralWidget(self.tabs)
self.df = createDF()
self.displayDF()
def displayDF(self):
self.result = QTabWidget()
self.result.layout = QGridLayout()
self.result.setLayout(self.result.layout)
self.tabs.addTab(self.result, 'Dataframe')
#empty space to put the export button in the bottom of the TableView
positions = [(i,j) for i in range(20) for j in range(10)]
space = ' '
i = 0
for position, leer in zip(positions, space):
emptyLabels = QLabel(leer)
self.result.layout.addWidget(emptyLabels, *position)
#export button
self.buttonExport = QPushButton("Export", self)
self.buttonExport.clicked.connect(partial(self.writeToCSV, self.df))
self.result.layout.addWidget(self.buttonExport, 21, 0)
# QTableView
self.view_minmax = QTableView(self.result)
self.modelminmax = PandasModel(self.df)
self.view_minmax.setModel(self.modelminmax)
self.view_minmax.resize(360,500)
self.view_minmax.clicked.connect(self.onClickedRow)
self.view_minmax.sortByColumn(4, Qt.DescendingOrder)
self.view_minmax.show()
def onClickedRow(self, index=None):
print("Click !")
print(index.data())
def writeToCSV(self, df):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileName, _ = QFileDialog.getSaveFileName(self,"Export results to a\
csv","","CSV (*.csv);;Text Files (*.txt)", options=options)
if fileName:
print(fileName)
df.to_csv (fileName, index = False, header=True)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('Fusion')
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
So when I create the #empty space QLabelWidget and export Button after the #QTableView section, like this, it comes to the strange behavior with the scrollbar because signals of mouse send to QLabelWidgets instead of the QTabelView
# QTableView
self.view_minmax = QTableView(self.result)
self.modelminmax = PandasModel(self.df)
self.view_minmax.setModel(self.modelminmax)
self.view_minmax.resize(360,500)
self.view_minmax.clicked.connect(self.onClickedRow)
self.view_minmax.sortByColumn(4, Qt.DescendingOrder)
self.view_minmax.show()
#empty space to put the export button in the bottom of the TableView
positions = [(i,j) for i in range(20) for j in range(10)]
space = ' '
i = 0
for position, leer in zip(positions, space):
emptyLabels = QLabel(leer)
self.result.layout.addWidget(emptyLabels, *position)
#export button
self.buttonExport = QPushButton("Export", self)
self.buttonExport.clicked.connect(partial(self.writeToCSV, self.df))
self.result.layout.addWidget(self.buttonExport, 21, 0)
Maybe it will help someone.

How to remove data from excel file displayed in PyQt5 and refresh it

My database application uses the Pandas library. I can display the excel file into my tableView but anytime I remove data from the mainframe and try to refresh the tableView. It gives me a keyError.
I'm trying to get it to display the refreshed table. I am attempting to drop the row that a user asks for. It works when it drops because I outputted the information but the tableView itself won't refresh and gives an error.
df = pd.read_excel("filename")
model = PandasModel(df)
self.tableView.setModel(model)
self.tableView.resizeColumnsToContents()
def DeletePlayer(self):
global df
choose = self.removePlayerEdit.text()
if(choose == '0'):
df = df.drop([0])
print("Player deleted")
print(df)
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
return QtCore.QVariant(str(self._df.ix[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
When implementing a model, you should not access the element that stores the data (dataframe) directly, because if you modify it, the model will not know what is going to generate problems, instead you should create methods that modify the internal data but use the methods as beginRemoveRows and endRemoveColumns that will notify the model of the change.
def removeColumn(self, col):
if 0 <= col < self.columnCount():
self.beginRemoveRows(QtCore.QModelIndex(), col, col)
self._df.drop(
self._df.columns[[col]], axis=1, inplace=True
)
self._df.reset_index(inplace=True, drop=True)
self.endRemoveColumns()
I have improved my initial model to the following:
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
import numpy as np
class FloatDelegate(QtWidgets.QStyledItemDelegate):
#property
def decimals(self):
if not hasattr(self, "_decimals"):
self._decimals = 2
return self._decimals
#decimals.setter
def decimals(self, decimals):
self._decimals = decimals
def createEditor(self, parent, option, index):
DBL_MAX = 1.7976931348623157e308
editor = QtWidgets.QDoubleSpinBox(
parent, minimum=-DBL_MAX, maximum=DBL_MAX, decimals=self.decimals
)
return editor
def setEditorData(self, editor, index):
editor.setValue(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.value(), QtCore.Qt.DisplayRole)
def displayText(self, value, locale):
return "{}".format(value)
class DataFrameModel(QtCore.QAbstractTableModel):
DtypeRole = QtCore.Qt.UserRole + 1000
ValueRole = QtCore.Qt.UserRole + 1001
def __init__(self, df=pd.DataFrame(), parent=None):
super(DataFrameModel, self).__init__(parent)
self._dataframe = df
def setDataFrame(self, dataframe):
self.beginResetModel()
self._dataframe = dataframe.copy()
self.endResetModel()
def dataFrame(self):
return self._dataframe
dataFrame = QtCore.pyqtProperty(
pd.DataFrame, fget=dataFrame, fset=setDataFrame
)
#QtCore.pyqtSlot(int, QtCore.Qt.Orientation, result=str)
def headerData(
self,
section: int,
orientation: QtCore.Qt.Orientation,
role: int = QtCore.Qt.DisplayRole,
):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._dataframe.columns[section]
else:
return str(self._dataframe.index[section])
return QtCore.QVariant()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._dataframe.index)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self._dataframe.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or not (
0 <= index.row() < self.rowCount()
and 0 <= index.column() < self.columnCount()
):
return QtCore.QVariant()
row = self._dataframe.index[index.row()]
col = self._dataframe.columns[index.column()]
dt = self._dataframe[col].dtype
val = self._dataframe.iloc[row][col]
if role == QtCore.Qt.DisplayRole:
return val
elif role == DataFrameModel.ValueRole:
return val
if role == DataFrameModel.DtypeRole:
return dt
return QtCore.QVariant()
def setData(self, index, value, role):
row = self._dataframe.index[index.row()]
col = self._dataframe.columns[index.column()]
if hasattr(value, "toPyObject"):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._dataframe[col].dtype
if dtype != object:
value = None if value == "" else dtype.type(value)
self._dataframe.at[row, col] = value
return True
def flags(self, index):
flags = (
QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsDragEnabled
| QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsEnabled
)
return flags
def roleNames(self):
roles = {
QtCore.Qt.DisplayRole: b"display",
DataFrameModel.DtypeRole: b"dtype",
DataFrameModel.ValueRole: b"value",
}
return roles
def removeRow(self, row):
if 0 <= row < self.rowCount():
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
self._dataframe.drop([row], inplace=True)
self._dataframe.reset_index(inplace=True, drop=True)
self.endRemoveRows()
def removeColumn(self, col):
if 0 <= col < self.columnCount():
self.beginRemoveRows(QtCore.QModelIndex(), col, col)
self._dataframe.drop(
self._dataframe.columns[[col]], axis=1, inplace=True
)
self._dataframe.reset_index(inplace=True, drop=True)
self.endRemoveColumns()
def sort(self, column, order):
colname = self._dataframe.columns[column]
self.layoutAboutToBeChanged.emit()
self._dataframe.sort_values(
colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True
)
self._dataframe.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
tableview = QtWidgets.QTableView()
tableview.setSortingEnabled(True)
delegate = FloatDelegate(tableview)
tableview.setItemDelegate(delegate)
delegate.decimals = 4
self.spinbox_row = QtWidgets.QSpinBox()
self.button_row = QtWidgets.QPushButton(
"Delete Row", clicked=self.remove_row
)
self.spinbox_col = QtWidgets.QSpinBox()
self.button_col = QtWidgets.QPushButton(
"Delete Column", clicked=self.remove_col
)
df = pd.DataFrame(
np.random.uniform(0, 100, size=(100, 4)), columns=list("ABCD")
)
self._model = DataFrameModel(df)
tableview.setModel(self._model)
grid = QtWidgets.QGridLayout(self)
grid.addWidget(tableview, 0, 0, 1, 4)
grid.addWidget(self.spinbox_row, 1, 0)
grid.addWidget(self.button_row, 1, 1)
grid.addWidget(self.spinbox_col, 1, 2)
grid.addWidget(self.button_col, 1, 3)
self.on_rowChanged()
self.on_columnChanged()
self._model.rowsInserted.connect(self.on_rowChanged)
self._model.rowsRemoved.connect(self.on_rowChanged)
self._model.columnsInserted.connect(self.on_columnChanged)
self._model.columnsRemoved.connect(self.on_columnChanged)
#QtCore.pyqtSlot()
def on_rowChanged(self):
self.spinbox_row.setMaximum(self._model.rowCount() - 1)
#QtCore.pyqtSlot()
def on_columnChanged(self):
self.spinbox_col.setMaximum(self._model.columnCount() - 1)
#QtCore.pyqtSlot()
def remove_row(self):
row = self.spinbox_row.value()
self._model.removeRow(row)
#QtCore.pyqtSlot()
def remove_col(self):
col = self.spinbox_col.value()
self._model.removeColumn(col)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

PyQT: adding to QAbstractItemModel using a button

I'm trying to implement a TreeView using QAbstractItemModel. I want to set up an empty model and then add an item to the model using a button.
This appears to be a lot more complex than I'd realised! I've modified a custom instance of the QAbstractItemModel example found in the Lib\site-packages\PyQt4\examples\itemviews\simpletreemodel\simpletreemodel.pyw.
Rather than setting up the model all at once, I want o build it incrementally.
When I click the button, I get the message showing that the process method is executing, but the model isn't updated.
Any pointers as to why it's not working would be greatly appreciated!
from PyQt4 import QtGui, QtCore
import sys
class myWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.resize(500, 400)
#Tree Widget
self.treeView = QtGui.QTreeView(self)
#Model
self.model = TreeModel()
self.treeView.setModel(self.model)
# Button
self.button = QtGui.QPushButton(self)
self.button.setText('Add Item')
self.button.clicked.connect(self.process)
# Add to Layout
self.gridLayout = QtGui.QGridLayout(self)
self.gridLayout.addWidget(self.button,0,0)
self.gridLayout.addWidget(self.treeView,1,0)
def process(self):
print "here"
newItem = TreeItem(["bob","bob","bob"],self.model.rootItem)
self.model.rootItem.appendChild(newItem)
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column):
try:
return self.itemData[column]
except IndexError:
return None
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.rootItem = TreeItem(("Model", "Status","Location"))
def columnCount(self, parent):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
item = index.internalPointer()
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
if __name__ == "__main__":
app = QtGui.QApplication.instance() # checks if QApplication already exists
if not app: # create QApplication if it doesnt exist
app = QtGui.QApplication(sys.argv)
# Window
window = myWindow()
window.show()
sys.exit(app.exec_())
Using a QStandardItemModel has worked a treat!
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(500, 400)
#Tree Widget
self.treeView = QTreeView()
#Model
self.model = QStandardItemModel()
self.treeView.setModel(self.model)
# Button
self.button = QPushButton()
self.button.setText('Add Item')
self.button.clicked.connect(self.addChildClick)
# Add to Layout
self.gridLayout = QGridLayout(self)
self.gridLayout.addWidget(self.button,0,0)
self.gridLayout.addWidget(self.treeView,1,0)
def addChildClick(self):
selection = self.treeView.selectedIndexes()
text = "bob"
item = QStandardItem(text)
# if nothing selected parent is model
if selection == []:
parent = self.model
else: # Otherwise parent is what is selected
s = selection[0] # Handling multiple selectons
parent = self.model.itemFromIndex(s)
parent.appendRow(item)
#cleanup
self.treeView.expandAll()
self.treeView.clearSelection()
if __name__ == "__main__":
app = QApplication.instance() # checks if QApplication already exists
if not app: # create QApplication if it doesnt exist
app = QApplication(sys.argv)
# Window
window = Window()
window.show()
app.exec_()

qtreeview root colored

I have a QTreeView with a colored background but I need to have also the root colored..
I'm not able to do so.
Someone can help me?
This is the code..
Thanks
from PyQt4 import QtGui, QtCore
HORIZONTAL_HEADERS = ("Surname", "Given Name")
class person_class(object):
def init(self, sname, fname, isMale):
self.sname = sname
self.fname = fname
self.isMale = isMale
class TreeItem(object):
def init(self, person, header, parentItem):
self.person = person
self.parentItem = parentItem
self.header = header
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return 2
def data(self, column):
if self.person == None:
if column == 0:
return QtCore.QVariant(self.header)
if column == 1:
return QtCore.QVariant("")
else:
if column == 0:
return QtCore.QVariant(self.person.sname)
if column == 1:
return QtCore.QVariant(self.person.fname)
return QtCore.QVariant()
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class treeModel(QtCore.QAbstractItemModel):
def init(self, parent=None):
super(treeModel, self).init(parent)
self.people = []
for fname, sname, isMale in (("John","Brown", 1),
("Fred", "Bloggs", 1), ("Sue", "Smith", 0)):
person = person_class(sname, fname, isMale)
self.people.append(person)
self.rootItem = TreeItem(None, "ALL", None)
self.parents = {0 : self.rootItem}
self.setupModelData()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().columnCount()
else:
return len(HORIZONTAL_HEADERS)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.person
if role == QtCore.Qt.BackgroundRole :
return QtGui.QColor('pink')
return QtCore.QVariant()
#def headerData(self, column, orientation, role):
#if (orientation == QtCore.Qt.Horizontal and
#role == QtCore.Qt.DisplayRole):
#try:
#return QtCore.QVariant(HORIZONTAL_HEADERS[column])
#except IndexError:
#pass
#return QtCore.QVariant()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.rootItem
else:
p_Item = parent.internalPointer()
return p_Item.childCount()
def setupModelData(self):
for person in self.people:
if person.isMale:
sex = "MALES"
else:
sex = "FEMALES"
if not self.parents.has_key(sex):
newparent = TreeItem(None, sex, self.rootItem)
self.rootItem.appendChild(newparent)
self.parents[sex] = newparent
parentItem = self.parents[sex]
newItem = TreeItem(person, "", parentItem)
parentItem.appendChild(newItem)
if name == "main":
app = QtGui.QApplication([])
model = treeModel()
dialog = QtGui.QDialog()
dialog.setMinimumSize(300,150)
layout = QtGui.QVBoxLayout(dialog)
tv = QtGui.QTreeView(dialog)
tv.setModel(model)
layout.addWidget(tv)
dialog.exec_()
app.closeAllWindows()

Resources