PyQt QAbstractTableModel checkbox not checkable - python

I am using own table model with QAbstractTableModel, where I have first col with checkbox (checkable cause flags Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled). I have trouble when I am trying use checkboxes, cause they are not checkable (can not make check or uncheck in them) in showed table.
What am I doing wrong? I am using this methods in own table model class:
def data(self, index, role):
row = index.row()
col = index.column()
if role == Qt.DisplayRole:
return '{0}'.format(self.tableData[row][col])
if role == Qt.CheckStateRole:
if col == 0:
return Qt.Unchecked
else:
return None
def setData(self, index, value, role):
if not index.isValid():
return False
if (role == Qt.CheckStateRole):
if (index.data(Qt.CheckStateRole) == Qt.Checked):
return True
else:
return False
else:
return False

You have to store the states for it, we need to have something permanent as a reference, for this we use QPersistentModelIndex, in this case a dictionary where the key is the QPersistentModelIndex and the value is the state of QCheckBox.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class TableModel(QAbstractTableModel):
def __init__(self, parent=None):
super(TableModel, self).__init__(parent)
self.tableData = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
self.checks = {}
def columnCount(self, *args):
return 3
def rowCount(self, *args):
return 3
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return Qt.Unchecked
def data(self, index, role=Qt.DisplayRole):
row = index.row()
col = index.column()
if role == Qt.DisplayRole:
return '{0}'.format(self.tableData[row][col])
elif role == Qt.CheckStateRole and col == 0:
return self.checkState(QPersistentModelIndex(index))
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
return True
return False
def flags(self, index):
fl = QAbstractTableModel.flags(self, index)
if index.column() == 0:
fl |= Qt.ItemIsEditable | Qt.ItemIsUserCheckable
return fl
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
view = QTableView()
model = TableModel()
view.setModel(model)
view.show()
sys.exit(app.exec_())

Related

pyqt5 Marked line in a different color

I'm using PyQt5 and would like to mark the line with a single click, that is, make it appear in a different color, e.g. in red.
After another click on the same line, the marking should be canceled again.
I have tried different approaches (how to see in my code marked as comments), but didn't find a solution.
class TableViewModel(QtCore.QAbstractTableModel):
def __init__(self, records, parent=None):
super().__init__(parent)
self.datatable = records
self.color_enabled = False
self.color_back = QtCore.Qt.magenta
self.data_changed = pyqtSignal(QtCore.QModelIndex,QtCore.QModelIndex)
def rowCount(self, parent=QtCore.QModelIndex):
return len(self.datatable)
def columnCount(self, parent=QtCore.QModelIndex):
return len(self.datatable[0]) if self.datatable else 0
def data(self, index, role, **kwargs): #role=QtCore.Qt.DisplayRole?
#reason = kwargs.get('r', None)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return str(self.datatable[index.row()][index.column()])
if index.isValid() and role == QtCore.Qt.BackgroundRole and self.color_enabled:
return gui.QBrush(gui.QColor(255, 0, 43)) #red
def row_clicked(self, index, role = QtCore.Qt.BackgroundRole):
# numeric position of dataset
if index is not None:
row = index.row()
column = index.column()
if column == 0:
self.itemnumber = index.sibling(row, column)
self.itemname = index.sibling(row, column + 1)
self.itemsize = index.sibling(row, column + 2)
elif column == 1:
self.itemnumber = index.sibling(row, column - 1)
self.itemname = index.sibling(row, column)
self.itemsize = index.sibling(row, column + 1)
elif column == 2:
self.itemnumber = index.sibling(row, column - 2)
self.itemname = index.sibling(row, column - 1)
self.itemsize = index.sibling(row, column)
return (self.itemnumber, self.itemname, self.itemsize, index) #return objects!!! no real datas
#print("Ausgewählter Artikel: ",self.itemindex.data(), self.itemname.data(), self.itemsize.data())
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
return header_table_view[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
color = gui.QColor(value)
#data = value
#self.datatable[row][column] = data
#self.dataChanged.emit(index, index)
return True
def changedData(self):
self.data_changed.emit(pyqtSignal("DataChanged(QModelIndex,QModelIndex)"), self.createIndex(0, 0), self.createIndex(self.rowCount(0)), self.columnCount(0))
def sort(self, column, order):
#self.emit(pyqtSignal("layoutAboutToBeChanged()"))
#self.layoutAboutToBeChanged.emit()
self.sortCol = column
self.sortOrder = order
try:
self.datatable = sorted(self.datatable, key = operator.itemgetter(column))
self.data_changed.emit(pyqtSignal("layoutChanged()"))
except:
print("keine Sortierung möglich")
#self.datatable.sort(key=itemgetter(column), reverse=order == QtCore.Qt.DescendingOrder)
def insertRow(self, item, index=QtCore.QModelIndex()):
""" Insert a row into the model. """
#self.beginInsertRows(QtCore.QModelIndex(), position, position + rows - 1)
TableViewModel.layoutAboutToBeChanged()
self.append(item)
TableViewModel.layoutChanged()
reply = QtWidgets.QMessageBox.information(self, "Meldung zum Artikelliste", "Artikel erfolgreich hinzugefügt!")
return True
Because you don't provide an MRE I will provide an example created from scratch. I will also assume that when you say line you mean row.
The strategy is to use a role that stores the state (a boolean) of the row, and then change that value when the row is pressed. According to that state a custom delegate will be used to change the backgroundBrush that will be used for painting the items.
import random
from PyQt5 import QtCore, QtGui, QtWidgets
StateRole = QtCore.Qt.UserRole + 1000
class BackgroundColorDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
print(index.data(StateRole))
if index.data(StateRole):
option.backgroundBrush = QtGui.QColor("red")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel()
for i in range(5):
for j in range(4):
value = random.randint(0, 10)
it = QtGui.QStandardItem()
it.setData(value, QtCore.Qt.DisplayRole)
self.model.setItem(i, j, it)
self.tableview = QtWidgets.QTableView()
self.tableview.setModel(self.model)
self.tableview.clicked.connect(self.on_clicked)
delegate = BackgroundColorDelegate(self.tableview)
self.tableview.setItemDelegate(delegate)
self.setCentralWidget(self.tableview)
#QtCore.pyqtSlot(QtCore.QModelIndex)
def on_clicked(self, index):
state = False
for i in range(self.model.columnCount()):
ix = self.model.index(index.row(), i)
value = ix.data(StateRole)
if value is not None:
state = value
break
for i in range(self.model.columnCount()):
ix = self.model.index(index.row(), i)
self.model.setData(ix, not state, StateRole)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Why is my table content blank and couldn't set data?

I am to learn model/view and write a demo, but my table has no data and couldn't set data.
And checkIndex method of QAbstractItemMode couldn't work ???
The code:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtPrintSupport import *
from PyQt5.QtChart import *
import numpy as np
class TableModel(QAbstractTableModel):
def __init__(self, data: np.ndarray):
super().__init__()
self.dataArray = data
def rowCount(self, parent):
return self.dataArray.shape[0]
def columnCount(self, parent):
return self.dataArray.shape[1]
def data(self, index: QModelIndex, role=None):
# checkIndex method not working ???
# self.checkIndex(index, QAbstractItemModel::IndexIsValid)
if not index.isValid():
return None
if index.row() >= self.dataArray.shape[0] or index.column() >= self.dataArray.shape[1]:
return None
if role in [Qt.DisplayRole, Qt.EditRole]:
return self.dataArray[index.row()][index.column()]
else:
return None
def headerData(self, section, orientation, role=None):
if role != Qt.DisplayRole:
return None
if orientation == Qt.Horizontal:
return f'Column {section}'
else:
return f'Row {section}'
def flags(self, index: QModelIndex):
if not index.isValid():
return Qt.ItemIsEnabled
return super().flags(index) | Qt.ItemIsEditable
def setData(self, index: QModelIndex, value, role=None):
if index.isValid() and role == Qt.EditRole:
self.dataArray[index.row()][index.column()] = value
self.dataChanged.emit(index, index, [role])
return True
return False
class DemoA(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
data = np.random.randint(1, 100, (4, 6))
model = TableModel(data)
# delegate = ComboBoxDelegate()
table = QTableView()
table.setModel(model)
# table.setItemDelegate(delegate)
self.setCentralWidget(table)
app = QApplication([])
demo = DemoA()
demo.show()
app.exec()
The result:
PyQt5 does not handle numpy data types so it will not display them. In your case, the data stored in the numpy array is numpy.int64, so the solution is to convert it to an integer or float:
if role in [Qt.DisplayRole, Qt.EditRole]:
return int(self.dataArray[index.row()][index.column()])

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

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?

How to prevent QTableView item from getting cleared on double-click

With QTableView set as editable using QAbstractTableModel's flag() method:
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
double-clicking the QTableView's item puts this item into the editing mode. By default the pre-existing string disappears from the field and an entire item is blank. I wonder if this behavior can be avoided or overridden?
Here is the QTableView field before the user double-clicks it:
And here is how it looks on double-click:
EDITED WORKING CODE (Many thanks to M4rtini):
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items =[
['Row0_Column0','Row0_Column1','Row0_Column2'],
['Row1_Column0','Row1_Column1','Row1_Column2'],
['Row2_Column0','Row2_Column1','Row2_Column2']
]
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def rowCount(self, parent):
return len(self.items)
def columnCount(self, parent):
return 3
def data(self, index, role):
if not index.isValid(): return QVariant()
row=index.row()
column=index.column()
if row>len(self.items): return QVariant()
if column>len(self.items[row]): return QVariant()
if role == Qt.EditRole or role == Qt.DisplayRole:
return QVariant(self.items[row][column])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
row = index.row()
column=index.column()
if row>len(self.items) or column>len(self.items[row]):
return False
else:
self.items[row][column]=value
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
tablemodel=Model(self)
tableview=QTableView(self)
tableview.setModel(tablemodel)
layout=QVBoxLayout(self)
layout.addWidget(tableview)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
You need to sett the return value for data when called with EditRole:
def data(self, index, role):
if not index.isValid(): return false
row=index.row()
if row>len(self.items): return false
if role == Qt.DisplayRole or role == Qt.EditRole:
return self.items[row]

how to set default value of Qdatetimeedit used as qdelegate

I'm using QDateTimeEdit as a delegate on my QTableview to show start date and end date.
when I try to populate data I receive from database, QDateTimeEdit delegate does not display it.
Here is my code:
Class DateDelegate:
class DateDelegate(QtGui.QItemDelegate):
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
self.dateEdit = QtGui.QDateTimeEdit(parent)
self.dateEdit.setCalendarPopup(True)
self.dateEdit.setMinimumDate(QtCore.QDate(2014, 03, 01))
self.dateEdit.setDisplayFormat(_translate("Form", "dd/mm/yyyy", None))
return self.dateEdit
def setModelData(self, editor, model, index):
value = self.dateEdit.dateTime().toPyDateTime()
strDate = value.strftime('%d/%m/%Y')
model.setData(index, strDate, QtCore.Qt.EditRole)
Class AssetTableModel:
class AssetTableModel(QtCore.QAbstractTableModel):
def __init__(self, assets = [], headers = [], parent = None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__assets = assets
self.__headers = headers
def rowCount(self, parent):
return len(self.__assets)
def columnCount(self, parent):
return len(self.__assets[0])
def flags(self, index):
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
def data(self, index, role):
row = index.row()
column = index.column()
if role == QtCore.Qt.EditRole:
return self.__assets[row][column]
if role == QtCore.Qt.DisplayRole:
print self.__assets[row][column]
return self.__assets[row][column]
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.__assets[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
if section < len(self.__headers):
return self.__headers[section]
else:
return "not implimented"
else:
return "verticle not implimented"
def insertRows(self, position, rows, parent = QtCore.QModelIndex()):
self.beginInsertRows( parent, position, position + rows - 1 )
for i in range(rows):
defaultValues = [ "" for c in range( self.columnCount( None ) ) ]
self.__assets.insert( position, defaultValues )
self.endInsertRows()
return True
Class AssetWidget:
class AssetWidget(QtGui.QDialog):
def __init__(self, parent = None):
super(AssetWidget, self).__init__(parent)
uic.loadUi(uipath+'/AssetTable.ui', self)
# DB call here
self.loadAssetData()
# db call ends here
self.model = None
self.fillCombo(self.assetType)
self.cellDelegate = CellDelegate(self)
for i in range(10):
self.assetTV.setItemDelegateForColumn(i, self.cellDelegate)
self.sDateDelegate = DateDelegate(self)
self.assetTV.setItemDelegateForColumn(10, self.sDateDelegate )
self.assetTV.setItemDelegateForColumn(11, self.sDateDelegate)
self.connect(self.assettypeCB, QtCore.SIGNAL("currentIndexChanged(int)"), self.loadAssets )
self.connect(self.closeBTN , QtCore.SIGNAL("clicked()"), self.close )
self.connect(self.addRowBTN, QtCore.SIGNAL("clicked()"), self.addRow )
self.connect(self.assetTV, QtCore.SIGNAL("doubleClicked(QModelIndex)"), self.tableEdited )
self.show()
I think you are missing the setEditorData() method in your ItemDelegate.
From your attached sourcecode, I assume you are storing the date as a string? In my opinion, it is better to use a QDateTime object to store your date/time. If you do this, you do not need a ItemDelegate to provide an appropriate editor, because Qt knows which editor it needs to provide for this kind of datatype. (see Qt Documentation - Standard Editing Widgets.
However, if you still want to store your date as a string, see this sample program below on how to use delegtes.
from PyQt4 import QtCore
from PyQt4 import QtGui
import sys
class myModel(QtCore.QAbstractTableModel):
def __init__(self, parent):
QtCore.QAbstractTableModel.__init__(self, parent)
self.lst = []
#populate with a few dummy dates
#store dates as str values
dateTime = QtCore.QDateTime.currentDateTime()
for i in range(10):
strDate = dateTime.toString("dd/mm/yyyy")
self.lst.append([strDate])
dateTime = dateTime.addDays(1)
def rowCount(self, parent = QtCore.QModelIndex()):
return len(self.lst)
def columnCount(self, parent = QtCore.QModelIndex()):
return 1
def data(self, index, role = QtCore.Qt.DisplayRole):
row = index.row()
col = index.column()
if role == QtCore.Qt.DisplayRole:
return self.lst[row][col]
if role == QtCore.Qt.EditRole:
return self.lst[row][col]
def setData(self, index, value, role = QtCore.Qt.EditRole):
row = index.row()
col = index.column()
self.lst[row][col] = value
def flags(self, index):
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable)
class DateDelegate(QtGui.QItemDelegate):
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
dateTimeEdit = QtGui.QDateTimeEdit(parent) #create new editor
#set properties of editor
dateTimeEdit.setDisplayFormat("dd/mm/yyyy")
dateTimeEdit.setCalendarPopup(True)
return dateTimeEdit
def setModelData(self, editor, model, index):
value = editor.dateTime().toString("dd/mm/yyyy")
model.setData(index, value)
def setEditorData(self, editor, index):
value = index.model().data(index, QtCore.Qt.EditRole)
qdate = QtCore.QDateTime().fromString(value, "dd/mm/yyyy")
editor.setDateTime(qdate)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
table = QtGui.QTableView()
data = myModel(table)
table.setModel(data)
d = DateDelegate(table)
table.setItemDelegateForColumn(0, d)
table.resize(800, 600)
table.show()
sys.exit(app.exec_())

Categories