I have a QTableView that has a column with True/False values. To change value in this column I use a QDataWidgetMapper checkBox.
I wish to change a value immediately after clicking a checkbox. Usually a change is made after checkbox looses focus (in other words you need to make extra click somewhere else in application).
I have tried to do it like this: checkBox.stateChanged.connect(lambda: data_mapper.submit()), but I don't like that using this method too many signals are emitted: first it emits signals that all mapped items were changed, and after checkbox looses focus - a signal that only one item was changed.
So the goal is to update model immediately after clicking a checkbox and have only one signal emitted.
Code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.widget_1 = Widget_1(self)
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.table)
self.layout_1.addWidget(self.widget_1)
self.setLayout(self.layout_1)
headers = ['State', 'Prorerty_1', 'Prorerty_2']
data = [
[True, '1', '2'],
[False, '3', '4']
]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.clicked.connect(self.set_data_mapper)
self.model.dataChanged.connect(lambda value: print(value.row(), value.column(), value.data()))
def set_data_mapper(self):
position = self.table.selectionModel().selectedIndexes()[0].row()
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setCurrentIndex(position)
self.widget_1.checkBox.stateChanged.connect(lambda: self.widget_1.data_mapper.submit())
class Widget_1(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.layout_1 = QtWidgets.QGridLayout()
self.state = QtWidgets.QLabel('State')
self.checkBox = QtWidgets.QCheckBox()
self.property = QtWidgets.QLabel('Prop.1')
self.lineEdit = QtWidgets.QLineEdit()
self.layout_1.addWidget(self.state, 0, 0)
self.layout_1.addWidget(self.checkBox, 0, 1)
self.layout_1.addWidget(self.property, 1, 0)
self.layout_1.addWidget(self.lineEdit, 1, 1)
self.setLayout(self.layout_1)
self.data_mapper = QtWidgets.QDataWidgetMapper()
class ListModel(QtCore.QAbstractTableModel):
def __init__(self, data_list = [[]], headers = [], parent = None):
super(ListModel, self).__init__()
self.data_list = data_list
self.headers = headers
def rowCount(self, parent):
return len(self.data_list)
def columnCount(self, parent):
return len(self.headers)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.data_list[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:
return self.headers[section]
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
By default the QDataWidgetMapper policy is "AutoSubmit" which updates the model if any widget loses focus, so if you want it not to be emitted when it loses focus then you must use the "ManualSubmit" policy. On the other hand, the connection must be done only once since if you do it n times then n signals will be emitted and in your case "set_data_mapper is invoked every time the button is pressed.
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView(
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows
)
self.widget_1 = Widget_1(self)
lay = QtWidgets.QHBoxLayout(self)
lay.addWidget(self.table)
lay.addWidget(self.widget_1)
headers = ["State", "Prorerty_1", "Prorerty_2"]
data = [[True, "1", "2"], [False, "3", "4"]]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setSubmitPolicy(
QtWidgets.QDataWidgetMapper.ManualSubmit
)
self.widget_1.checkBox.stateChanged.connect(self.widget_1.data_mapper.submit)
self.widget_1.lineEdit.textChanged.connect(self.widget_1.data_mapper.submit)
self.table.clicked.connect(self.update_current_index)
self.model.dataChanged.connect(
lambda value: print(value.row(), value.column(), value.data())
)
def update_current_index(self, index):
position = index.row()
self.widget_1.data_mapper.setCurrentIndex(position)
I have figured out this problem: now only one signal is emitted immediately after change.
First, I used "ManualSubmit" policy for QDataWidgetMapper , as #eyllanesc told.
Second, I wrote my own custom function for both lineEdit and a checkBox instead data_mapper.submit().
Manual connection between mapped widgets and model is done once in 'for position in range(len(self.model.data_list))' loop.
Also, I want to pay attention that inside 'data_mapper_settings' function I used 'clicked' instead 'stateChanged' for checBox and 'textEdited' instead 'textChanged' for lineEdit to avoid emitting extra signals when other row is chosen.
Still I wonder if there's more elegant way to solve this problem
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.widget_1 = Widget_1(self)
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.table)
self.layout_1.addWidget(self.widget_1)
self.setLayout(self.layout_1)
headers = ['State', 'Prorerty_1', 'Prorerty_2']
data = [
[True, '1', '2'],
[False, '3', '4']
]
self.model = ListModel(data, headers)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.clicked.connect(lambda index: self.set_data_mapper(index))
for position in range(len(self.model.data_list)):
self.data_mapper_settings(position)
self.model.dataChanged.connect(lambda index: print(index.row(), index.column(), index.data()))
def set_data_mapper(self, index):
position = index.row()
self.widget_1.data_mapper.setModel(self.model)
self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
self.widget_1.data_mapper.setCurrentIndex(position)
def data_mapper_settings(self, position):
self.widget_1.checkBox.clicked.connect(lambda value: self.submit_checkbox(value, position))
self.widget_1.lineEdit.textEdited.connect(lambda value: self.submit_lineEdit(value, position))
def submit_checkbox(self, value, position):
if position == self.table.selectionModel().selectedIndexes()[0].row():
self.model.setData(self.model.index(position, 0), value)
def submit_lineEdit(self, value, position):
if position == self.table.selectionModel().selectedIndexes()[0].row():
self.model.setData(self.model.index(position, 1), value)
class Widget_1(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.layout_1 = QtWidgets.QGridLayout()
self.state = QtWidgets.QLabel('State')
self.checkBox = QtWidgets.QCheckBox()
self.property = QtWidgets.QLabel('Prop.1')
self.lineEdit = QtWidgets.QLineEdit()
self.layout_1.addWidget(self.state, 0, 0)
self.layout_1.addWidget(self.checkBox, 0, 1)
self.layout_1.addWidget(self.property, 1, 0)
self.layout_1.addWidget(self.lineEdit, 1, 1)
self.setLayout(self.layout_1)
self.data_mapper = QtWidgets.QDataWidgetMapper()
self.data_mapper.setSubmitPolicy(QtWidgets.QDataWidgetMapper.ManualSubmit)
class ListModel(QtCore.QAbstractTableModel):
def __init__(self, data_list = [[]], headers = [], parent = None):
super(ListModel, self).__init__()
self.data_list = data_list
self.headers = headers
def rowCount(self, parent):
return len(self.data_list)
def columnCount(self, parent):
return len(self.headers)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.data_list[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.data_list[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:
return self.headers[section]
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsUserCheckable
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
Related
I am new to Qt. Currently I am trying to learn how to update a table model from a different thread and then how to get an immediate display update for it. I read the documentation and found the dataChanged() and layoutChanged() signals. While dataChanged() works fine, any attempt to emit layoutChanged() fails with:
'QObject::connect: Cannot queue arguments of type 'QList<QPersistentModelIndex>' (Make sure 'QList<QPersistentModelIndex>' is registered using qRegisterMetaType().)
Searching for this particular error didn't give me anything that I could turn into working code. I am not using any QList or QPersistentModelIndex explicitly, but of course that can be implicitly used due to the constructs that I chose.
What am I doing wrong?
class TimedModel(QtCore.QAbstractTableModel):
def __init__(self, table, view):
super(TimedModel, self).__init__()
self.table = table
self.view = view
self.setHeaderData(0, Qt.Horizontal, Qt.AlignLeft, Qt.TextAlignmentRole)
self.rows = 6
self.columns = 4
self.step = 5
self.timer = Thread(
name = "Timer",
target = self.tableTimer,
daemon = True)
self.timer.start()
self.random = Random()
self.updated = set()
#staticmethod
def encode(row, column):
return row << 32 | column
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:
return f'Data-{index.row()}-{index.column()}'
if role == Qt.ForegroundRole:
encoded = TimedModel.encode(index.row(), index.column())
return QBrush(Qt.red if encoded in self.updated else Qt.black)
return None
def rowCount(self, index):
return self.rows
def columnCount(self, index):
return self.columns
def headerData(self, col, orientation, role):
if orientation == Qt.Vertical:
# Vertical
return super().headerData(col, orientation, role)
# Horizontal
if not 0 <= col < self.columns:
return None
if role == Qt.DisplayRole:
return f'Data-{col}'
if role == Qt.TextAlignmentRole:
return int(Qt.AlignLeft | Qt.AlignVCenter)
return super().headerData(col, orientation, role)
def tableTimer(self):
while True:
time.sleep(5.0)
randomRow = self.random.randint(0, self.rows)
randomColumn = self.random.randint(0, self.columns)
encodedRandom = TimedModel.encode(randomRow, randomColumn)
if encodedRandom in self.updated:
self.updated.remove(encodedRandom)
else:
self.updated.add(encodedRandom)
updatedIndex = self.createIndex(randomRow, randomColumn)
self.dataChanged.emit(updatedIndex, updatedIndex)
'''this here does not work:'''
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.timedTable = QTableView()
self.model = TimedModel(self.timedTable, self)
self.timedTable.setModel(self.model)
headerView = self.timedTable.horizontalHeader()
headerView.setStretchLastSection(True)
self.setCentralWidget(self.timedTable)
self.setGeometry(300, 300, 1000, 600)
self.setWindowTitle('Timed Table')
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.name = "Timed Table Application"
window = MainWindow()
window.show()
app.exec_()
The following code:
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
create new model elements that have QPersistentModelIndex associated that are not thread-safe and that Qt monitors its creation to warn its misuse as in this case since modifying that element is unsafe since it implies modifying the GUI from another thread (Read here for more information).
So you see that message warning that what you are trying to do is unsafe.
Instead dataChanged only emits a signal, does not create any element belonging to Qt, and you have been lucky that the modification of "self.updated" has not generated bottlenecks since you modify a property that belongs to the main thread from a secondary thread without use guards as mutexes.
Qt points out that the GUI and the elements that the GUI uses should only be updated in the GUI thread, and if you want to modify the GUI with information from another thread, then you must send that information, for example, using the signals that are thread- safe:
import random
import sys
import threading
import time
from PySide2 import QtCore, QtGui, QtWidgets
class TimedModel(QtCore.QAbstractTableModel):
random_signal = QtCore.Signal(object)
def __init__(self, table, view):
super(TimedModel, self).__init__()
self.table = table
self.view = view
self.setHeaderData(
0, QtCore.Qt.Horizontal, QtCore.Qt.AlignLeft, QtCore.Qt.TextAlignmentRole
)
self.rows = 6
self.columns = 4
self.step = 5
self.updated = set()
self.random_signal.connect(self.random_slot)
self.timer = threading.Thread(name="Timer", target=self.tableTimer, daemon=True)
self.timer.start()
#staticmethod
def encode(row, column):
return row << 32 | column
def data(self, index, role):
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
return f"Data-{index.row()}-{index.column()}"
if role == QtCore.Qt.ForegroundRole:
encoded = TimedModel.encode(index.row(), index.column())
return QtGui.QBrush(
QtCore.Qt.red if encoded in self.updated else QtCore.Qt.black
)
return None
def rowCount(self, index):
return self.rows
def columnCount(self, index):
return self.columns
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Vertical:
# Vertical
return super().headerData(col, orientation, role)
# Horizontal
if not 0 <= col < self.columns:
return None
if role == QtCore.Qt.DisplayRole:
return f"Data-{col}"
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
return super().headerData(col, orientation, role)
def tableTimer(self):
while True:
time.sleep(5.0)
randomRow = random.randint(0, self.rows)
randomColumn = random.randint(0, self.columns)
encodedRandom = TimedModel.encode(randomRow, randomColumn)
self.random_signal.emit(encodedRandom)
#QtCore.Slot(object)
def random_slot(self, encodedRandom):
if encodedRandom in self.updated:
self.updated.remove(encodedRandom)
else:
self.updated.add(encodedRandom)
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.timedTable = QtWidgets.QTableView()
self.model = TimedModel(self.timedTable, self)
self.timedTable.setModel(self.model)
headerView = self.timedTable.horizontalHeader()
headerView.setStretchLastSection(True)
self.setCentralWidget(self.timedTable)
self.setGeometry(300, 300, 1000, 600)
self.setWindowTitle("Timed Table")
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.name = "Timed Table Application"
window = MainWindow()
window.show()
app.exec_()
I have a code to represent a pandas dataframe in a table and provides the filtering functionality as shown in the below:
I would like to insert a check box column before the first column, so that the user can select rows.
The code uses a PandasModel(QtCore.QAbstractTableModel) model to read pandas data and a CustomProxyModel(QtCore.QSortFilterProxyModel) to add filtering capability for the QTableView.
My question is if I need to add the additional checkbox column, should I add it in thePandasModel or in the CustomProxyModel or somewhere else? As an alternative solution, I found an SO solution suggesting to use a custom CheckBoxDelegate (which I added in the code below) but it seems to occupy the first column instead of inserting a new column. also it does not allow clicking.
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df=pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df.copy()
self.bolds = dict()
def toDataFrame(self):
return self._df.copy()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
try:
return self._df.columns.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
elif role == QtCore.Qt.FontRole:
return self.bolds.get(section, QtCore.QVariant())
elif orientation == QtCore.Qt.Vertical:
if role == QtCore.Qt.DisplayRole:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
return QtCore.QVariant()
def setFont(self, section, font):
self.bolds[section] = font
self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, self.columnCount())
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.iloc[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()
class CustomProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QtCore.QRegExp(
expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
class TableView(QtWidgets.QTableView):
"""
A simple table to demonstrate the QComboBox delegate.
"""
def __init__(self, *args, **kwargs):
QtWidgets.QTableView.__init__(self, *args, **kwargs)
self.setItemDelegateForColumn(0, CheckBoxDelegate(self))
class myWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtWidgets.QWidget(self)
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.view = TableView(self)
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.label = QtWidgets.QLabel(self.centralwidget)
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
self.label.setText("Regex Filter")
self.load_sites()
self.comboBox.addItems(["{0}".format(col) for col in self.model._df.columns])
self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
def load_sites(self):
df = pd.DataFrame({'site_codes': ['01', '02', '03', '04'],
'status': ['open', 'open', 'open', 'closed'],
'Location': ['east', 'north', 'south', 'east'],
'data_quality': ['poor', 'moderate', 'high', 'high']})
self.model = PandasModel(df)
self.proxy = CustomProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
# delegate = CheckBoxDelegate(None)
# self.view.setItemDelegateForColumn(0, delegate)
#QtCore.pyqtSlot(int)
def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
self.logicalIndex = logicalIndex
self.menuValues = QtWidgets.QMenu(self)
self.signalMapper = QtCore.QSignalMapper(self)
self.comboBox.blockSignals(True)
self.comboBox.setCurrentIndex(self.logicalIndex)
self.comboBox.blockSignals(True)
valuesUnique = self.model._df.iloc[:, self.logicalIndex].unique()
actionAll = QtWidgets.QAction("All", self)
actionAll.triggered.connect(self.on_actionAll_triggered)
self.menuValues.addAction(actionAll)
self.menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtWidgets.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
self.menuValues.addAction(action)
self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
self.menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
self.proxy.setFilter("", filterColumn)
font = QtGui.QFont()
self.model.setFont(filterColumn, font)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
self.proxy.setFilter(stringAction, filterColumn)
font = QtGui.QFont()
font.setBold(True)
self.model.setFont(filterColumn, font)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
self.proxy.setFilter(text, self.proxy.filterKeyColumn())
#QtCore.pyqtSlot(int)
def on_comboBox_currentIndexChanged(self, index):
self.proxy.setFilterKeyColumn(index)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(2000, 800)
sys.exit(app.exec_())
CustomProxyModel is not an option because it does not allow you to correctly map checkboxes since the number and row shown varies. One possible solution is to implement a proxy (maybe based on QAbstractProxyModel or QIdentityProxyModel) implementing that functionality but in my answer I will not propose that solution since it can complicate the logic, instead I will implement the logic in the source model so I have created a class inherited from PandasModel where a column for checkboxes is added.
The logic is override data, setData, flags and headarData methods so that the information in the column greater than the checkbox column is taken from the base class but decreased by 1.
Considering the above, the solution is as follows:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df=pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df.copy()
self.bolds = dict()
def toDataFrame(self):
return self._df.copy()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
try:
return self._df.columns.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
elif role == QtCore.Qt.FontRole:
return self.bolds.get(section, QtCore.QVariant())
elif orientation == QtCore.Qt.Vertical:
if role == QtCore.Qt.DisplayRole:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
return QtCore.QVariant()
def setFont(self, section, font):
self.bolds[section] = font
self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, self.columnCount())
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.iloc[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()
class CheckablePandasModel(PandasModel):
def __init__(self, df=pd.DataFrame(), parent=None):
super().__init__(df, parent)
self.checkable_values = set()
self._checkable_column = -1
#property
def checkable_column(self):
return self._checkable_column
#checkable_column.setter
def checkable_column(self, column):
if self.checkable_column == column:
return
last_column = self.checkable_column
self._checkable_column = column
if last_column == -1:
self.beginInsertColumns(
QtCore.QModelIndex(), self.checkable_column, self.checkable_column
)
self.endInsertColumns()
elif self.checkable_column == -1:
self.beginRemoveColumns(QtCore.QModelIndex(), last_column, last_column)
self.endRemoveColumns()
for c in (last_column, column):
if c > 0:
self.dataChanged.emit(
self.index(0, c), self.index(self.columnCount() - 1, c)
)
def columnCount(self, parent=QtCore.QModelIndex()):
return super().columnCount(parent) + (1 if self.checkable_column != -1 else 0)
def data(self, index, role=QtCore.Qt.DisplayRole):
if self.checkable_column != -1:
row, col = index.row(), index.column()
if col == self.checkable_column:
if role == QtCore.Qt.CheckStateRole:
return (
QtCore.Qt.Checked
if row in self.checkable_values
else QtCore.Qt.Unchecked
)
return QtCore.QVariant()
if col > self.checkable_column:
index = index.sibling(index.row(), col - 1)
return super().data(index, role)
def setData(self, index, value, role):
if self.checkable_column != -1:
row, col = index.row(), index.column()
if col == self.checkable_column:
if role == QtCore.Qt.CheckStateRole:
if row in self.checkable_values:
self.checkable_values.discard(row)
else:
self.checkable_values.add(row)
self.dataChanged.emit(index, index, (role,))
return True
return False
if col > self.checkable_column:
index = index.sibling(index.row(), col - 1)
return super().setData(index, value, role)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if self.checkable_column != -1:
if section == self.checkable_column and orientation == QtCore.Qt.Horizontal:
return QtCore.QVariant()
if section > self.checkable_column and orientation == QtCore.Qt.Horizontal:
section -= 1
return super().headerData(section, orientation, role)
def flags(self, index):
if self.checkable_column != -1:
col = index.column()
if col == self.checkable_column:
return QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled
if col > self.checkable_column:
index = index.sibling(index.row(), col - 1)
return super().flags(index)
class CustomProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QtCore.QRegExp(
expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class myWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtWidgets.QWidget()
self.lineEdit = QtWidgets.QLineEdit()
self.view = QtWidgets.QTableView()
self.comboBox = QtWidgets.QComboBox()
self.label = QtWidgets.QLabel()
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
self.label.setText("Regex Filter")
self.load_sites()
self.comboBox.addItems(["{0}".format(col) for col in self.model._df.columns])
self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(
self.on_view_horizontalHeader_sectionClicked
)
def load_sites(self):
df = pd.DataFrame(
{
"site_codes": ["01", "02", "03", "04"],
"status": ["open", "open", "open", "closed"],
"Location": ["east", "north", "south", "east"],
"data_quality": ["poor", "moderate", "high", "high"],
}
)
self.model = CheckablePandasModel(df)
self.model.checkable_column = 0
self.proxy = CustomProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
#QtCore.pyqtSlot(int)
def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
if logicalIndex == self.model.checkable_column:
return
self.menuValues = QtWidgets.QMenu(self)
self.comboBox.blockSignals(True)
self.comboBox.setCurrentIndex(
logicalIndex - 1
if logicalIndex > self.model.checkable_column
else logicalIndex
)
self.comboBox.blockSignals(True)
valuesUnique = set(
self.proxy.index(i, logicalIndex).data()
for i in range(self.proxy.rowCount())
)
actionAll = QtWidgets.QAction("All", self)
self.menuValues.addAction(actionAll)
self.menuValues.addSeparator()
for i, name in enumerate(valuesUnique):
action = QtWidgets.QAction(name, self)
action.setData(i)
self.menuValues.addAction(action)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
pos = headerPos + QtCore.QPoint(
self.horizontalHeader.sectionPosition(logicalIndex),
self.horizontalHeader.height(),
)
action = self.menuValues.exec_(pos)
if action is not None:
font = QtGui.QFont()
if action.data() is None: # all
self.proxy.setFilter("", logicalIndex)
else:
font.setBold(True)
self.proxy.setFilter(action.text(), logicalIndex)
self.model.setFont(logicalIndex - 1, font)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
self.proxy.setFilter(text, self.comboBox.currentIndex() + 1)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(2000, 800)
sys.exit(app.exec_())
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_())
Example of table model data
I have a gui with a tabWidget and inside each I have a tableview. Each tab describes a folder (see column type) with subdirs from which I pull the data. I want to have one main model that drives all views by attaching a QSortFilterProxy Model inbetween each tableview that filters the main model for the "type" of each individual TableView (passed into the subclass RenderTypeProxyModel). Bonus: Ideally, they should be sorted as well, so that the most recent view (see date column) comes out on top.
Here is my current version but the table remains blank for a reason I can't figure out:
import sys
import os
from datetime import datetime
from pprint import pprint
from PySide2 import QtCore, QtGui, QtWidgets
#To be replaced by env variable
pathToProject = "/run/media/centos7/Data/Projects/Programming/Pipeline/SampleProject"
allowedExportTypes = ["img-prv", "img-final", "img-cg", "img-src", "camera"]
class ExportTableModel(QtCore.QAbstractTableModel):
def __init__(self, exportData, horizontalHeaders, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__exportData = exportData
self.__horizontalHeaders = horizontalHeaders
def rowCount(self, parent):
return len(self.__exportData)
def columnCount(self, parent):
return len(self.__horizontalHeaders)
def data(self, index, role): #Returns the data stored under the given role for the item referred to by the index.
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.__exportData[row][column]
return value
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
if section < len(self.__horizontalHeaders):
return self.__horizontalHeaders[section]
else:
return "not implemented"
def tableSetup(tableView):
tableView.setAlternatingRowColors(True)
tableView.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
tableView.setSortingEnabled(True)
class RenderTypeProxyModel (QtCore.QSortFilterProxyModel): #Custom Proxy Model
def __init__(self, type, parent=None):
super(RenderTypeProxyModel,self).__init__(parent)
self.__type = type
def filterAcceptsRow(self, row, parent): #returns true if the given row should be included in the model
model = self.sourceModel()
index = model.index(row, 3, parent)
if model.data(index,QtCore.Qt.DisplayRole) == type:
return True
else:
return False
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
horizontalHeaders = ["status", "comment", "task", "type", "version", "user", "date", "filepath"] #horizontal header data
#exportData = #insert sample data list of lists here for testing
tableView = QtWidgets.QTableView()
tableView.show()
model = ExportTableModel(exportData,horizontalHeaders)
proxyModel = RenderTypeProxyModel("img-prv")
proxyModel.setSourceModel(model)
tableView.setModel(proxyModel)
tableSetup(tableView)
sys.exit(app.exec_())
Here is sample data to use for debugging: https://pastebin.com/AB1XvKju
Just feed it into the exportData variable inside the __main__ method.
The error is caused because type is a reserved word in python:
if model.data(index,QtCore.Qt.DisplayRole) == type:
you must use self.__type.
On the other hand if you want to sort the data by date it is not necessary to enable setSortingEnabled(), just use sort().
from PySide2 import QtCore, QtGui, QtWidgets
#To be replaced by env variable
allowedExportTypes = ["img-prv", "img-final", "img-cg", "img-src", "camera"]
class ExportTableModel(QtCore.QAbstractTableModel):
def __init__(self, exportData, horizontalHeaders, parent=None):
super(ExportTableModel, self).__init__(parent)
self.__exportData = exportData
self.__horizontalHeaders = horizontalHeaders
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.__exportData)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.__horizontalHeaders)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.__exportData[row][column]
return value
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
if section < self.columnCount():
return self.__horizontalHeaders[section]
return "not implemented"
def tableSetup(tableView):
tableView.setAlternatingRowColors(True)
tableView.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
class RenderTypeProxyModel (QtCore.QSortFilterProxyModel): #Custom Proxy Model
def __init__(self, _type, parent=None):
super(RenderTypeProxyModel,self).__init__(parent)
self.__type = _type
def filterAcceptsRow(self, row, parent):
_type = self.sourceModel().index(row, 3, parent).data()
return _type == self.__type
def lessThan(self, left, right):
fmt = "yyyy-MM-dd hh:mm:ss"
left_data = self.sourceModel().data(left)
right_data = self.sourceModel().data(right)
return QtCore.QDateTime.fromString(left_data, fmt) < QtCore.QDateTime.fromString(right_data, fmt)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
horizontalHeaders = ["status", "comment", "task", "type", "version", "user", "date", "filepath"] #horizontal header data
exportData = # ...
model = ExportTableModel(exportData, horizontalHeaders)
tab_widget = QtWidgets.QTabWidget()
for _type in allowedExportTypes:
tableView = QtWidgets.QTableView()
c = horizontalHeaders.index("date")
tableSetup(tableView)
proxy = RenderTypeProxyModel(_type, tableView)
proxy.setSourceModel(model)
proxy.sort(c, QtCore.Qt.AscendingOrder)
tableView.setModel(proxy)
tab_widget.addTab(tableView ,_type)
tab_widget.show()
sys.exit(app.exec_())
Update:
In the next part I have made improvements to your original code avoiding overwriting unnecessary methods and adding a delegate:
from PySide2 import QtCore, QtGui, QtWidgets
#To be replaced by env variable
allowedExportTypes = ["img-prv", "img-final", "img-cg", "img-src", "camera"]
class DateDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(DateDelegate, self).initStyleOption(option, index)
option.text = index.data().toString("yyyy-MM-dd hh:mm:ss")
class ExportTableModel(QtCore.QAbstractTableModel):
def __init__(self, exportData, horizontalHeaders, parent=None):
super(ExportTableModel, self).__init__(parent)
self.__exportData = exportData
self.__horizontalHeaders = horizontalHeaders
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.__exportData)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.__horizontalHeaders)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.__exportData[row][column]
header = self.headerData(column, QtCore.Qt.Horizontal)
if header == "date":
value = QtCore.QDateTime.fromString(value, "yyyy-MM-dd hh:mm:ss")
return value
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
if section < self.columnCount():
return self.__horizontalHeaders[section]
return "not implemented"
def create_tableview():
tableView = QtWidgets.QTableView()
tableView.setAlternatingRowColors(True)
tableView.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
return tableView
class RenderTypeProxyModel (QtCore.QSortFilterProxyModel):
def __init__(self, c_type, _type, c_date, model, parent=None):
super(RenderTypeProxyModel,self).__init__(parent)
self.setSourceModel(model)
self.setFilterKeyColumn(c_type)
self.setFilterFixedString(_type)
self.sort(c_date, QtCore.Qt.AscendingOrder)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
horizontalHeaders = ["status", "comment", "task", "type", "version", "user", "date", "filepath"] #horizontal header data
exportData = # ...
c_date = horizontalHeaders.index("date")
c_type = horizontalHeaders.index("type")
tab_widget = QtWidgets.QTabWidget()
for _type in allowedExportTypes:
tableView = create_tableview()
tab_widget.addTab(tableView ,_type)
proxy = RenderTypeProxyModel(c_type, _type, c_date, model, tableView)
tableView.setModel(proxy)
delegate = DateDelegate(tableView)
tableView.setItemDelegateForColumn(c_date, delegate)
tab_widget.show()
sys.exit(app.exec_())
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_())