Single selecting row in QTableView activates column edit - python

How can I make the second column actively in edit mode anytime a row is selected in the tableview as seen in this gif below? I'm trying to recreate this in python/pyside.
Ideally I would like to use some sort of item delegate so i could easily handle the keyPressEvents in the columns cell and add the custom (X) clear button. However I'm not sure how to use delegates like this when using ItemModels. So any help in making this task achievable is appreciated.
class ExampleDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
line_edit = QtGui.QLineEdit(parent)
return line_edit
Here is my code and a screenshot:
import os, sys
from PySide import QtGui, QtCore
class HotkeyItem():
def __init__(self, command, shortcut):
self.command = command
self.shortcut = shortcut
class HotkeysModel(QtCore.QAbstractTableModel):
def __init__(self):
super(HotkeysModel, self).__init__()
self.items = []
self.headers = ['Command','Hotkey']
def clear(self):
self.beginResetModel()
self.items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.items)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
cnt = len(self.headers)
if section < cnt:
return self.headers[section]
return None
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.headers)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column, parent)
def addItem(self, item):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(item)
self.endInsertRows()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
row = index.row()
col = index.column()
if 0 <= row < self.rowCount():
item = self.items[row]
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'command', 'N/A')
elif col == 1:
return getattr(item, 'shortcut', '')
if role == QtCore.Qt.BackgroundRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
return QtGui.QBrush(QtGui.QColor(255, 50, 50, 255))
elif role == QtCore.Qt.FontRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
fnt = QtGui.QFont()
fnt.setBold(True)
fnt.setItalic(True)
return fnt
return None
def flags(self, index):
if not index.isValid():
return 0
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.resize(600, 400)
model = HotkeysModel()
proxyModel = QtGui.QSortFilterProxyModel()
proxyModel.setFilterKeyColumn(0)
proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxyModel.setSourceModel(model)
self.uiView = QtGui.QTableView()
self.uiView.setSortingEnabled(True)
self.uiView.setModel(proxyModel)
self.uiView.setAlternatingRowColors(True)
self.uiView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.uiView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.uiView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.uiView.verticalHeader().hide()
self.uiView.horizontalHeader().show()
lay = QtGui.QVBoxLayout()
lay.addWidget(self.uiView)
self.setLayout(lay)
self.populate()
# connections
selection = self.uiView.selectionModel()
selection.currentRowChanged.connect(self.selection_changed)
# ui->tableView->setCurrentIndex(index);
# ui->tableView->edit(index);
def selection_changed(self, index):
if index.isValid():
row = index.row()
self.uiView.setCurrentIndex(index)
self.uiView.edit(index)
def populate(self):
model = self.uiView.model().sourceModel()
model.clear()
items = [
HotkeyItem(command='Save', shortcut='Ctrl+S'),
HotkeyItem(command='Open', shortcut='Ctrl+O'),
HotkeyItem(command='Close', shortcut='Ctrl+Q'),
HotkeyItem(command='Align Top', shortcut=''),
HotkeyItem(command='Align Bottom', shortcut=''),
HotkeyItem(command='Align Left', shortcut=''),
HotkeyItem(command='Align Right', shortcut=''),
HotkeyItem(command='Align Center', shortcut='Ctrl+O')
]
for x in items:
model.addItem(x)
self.uiView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiView.resizeColumnsToContents()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Part of the problem is that for an item to be editable, it must have the flag QtCore.Qt.ItemIsEditable activated. The other part is that the index that passes selection_changed can be from the item in the first column and not from the second, so using that index you should get the index from the second column.
In Qt5 the clear button is already implemented and it is only activated using setClearButtonEnabled(True) and the icon is changed using qss, but in the case of Qt4 it does not exist, so it must be created to use this answer.
Finally you must also implement the setData() method.
import os
import sys
from PySide import QtGui, QtCore
class LineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
btnSize = self.sizeHint().height() - 5
self.clearButton = QtGui.QToolButton(self)
icon = QtGui.QIcon("clear.png")
self.clearButton.setIcon(icon)
self.clearButton.setCursor(QtCore.Qt.ArrowCursor)
self.clearButton.setStyleSheet("QToolButton { border: none; padding: 2px}")
self.clearButton.setFixedSize(btnSize, btnSize)
self.clearButton.hide()
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
self.setStyleSheet("QLineEdit{{ padding-right: {}px }}".format(btnSize - frameWidth))
self.setMinimumHeight(self.sizeHint().height())
self.clearButton.clicked.connect(self.clear)
self.textChanged.connect(self.onTextChanged)
def resizeEvent(self, event):
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
self.clearButton.move(self.width() - self.clearButton.width() - frameWidth, 0)
def onTextChanged(self, text):
self.clearButton.setVisible(text != "")
class Delegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = LineEdit(parent)
font = index.data(QtCore.Qt.FontRole)
editor.setFont(font)
return editor
def setEditorData(self, editor, index):
text = index.data()
editor.setText(text)
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
class HotkeyItem():
def __init__(self, command, shortcut):
self.command = command
self.shortcut = shortcut
class HotkeysModel(QtCore.QAbstractTableModel):
def __init__(self):
super(HotkeysModel, self).__init__()
self.items = []
self.headers = ['Command','Hotkey']
def clear(self):
self.beginResetModel()
self.items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.items)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
cnt = len(self.headers)
if section < cnt:
return self.headers[section]
return None
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.headers)
def addItem(self, item):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(item)
self.endInsertRows()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
row = index.row()
col = index.column()
if 0 <= row < self.rowCount():
item = self.items[row]
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'command', 'N/A')
elif col == 1:
return getattr(item, 'shortcut', '')
if role == QtCore.Qt.BackgroundRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
return QtGui.QBrush(QtGui.QColor(255, 50, 50, 255))
elif role == QtCore.Qt.FontRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
fnt = QtGui.QFont()
fnt.setBold(True)
fnt.setItalic(True)
return fnt
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
row = index.row()
col = index.column()
if 0 <= row < self.rowCount() and 0 <= col < self.columnCount():
it = self.items[row]
if col == 0:
it.command = value
elif col == 1:
it.shortcut = value
return True
return False
def flags(self, index):
fl = QtCore.Qt.NoItemFlags
if index.isValid():
fl |= QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if index.column() == 1:
fl |= QtCore.Qt.ItemIsEditable
return fl
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.resize(600, 400)
model = HotkeysModel()
proxyModel = QtGui.QSortFilterProxyModel()
proxyModel.setFilterKeyColumn(0)
proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxyModel.setSourceModel(model)
self.uiView = QtGui.QTableView()
self.uiView.setSortingEnabled(True)
self.uiView.setModel(proxyModel)
self.uiView.setAlternatingRowColors(True)
delegate = Delegate(self)
self.uiView.setItemDelegateForColumn(1, delegate)
self.uiView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.uiView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.uiView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.uiView.verticalHeader().hide()
self.uiView.horizontalHeader().show()
lay = QtGui.QVBoxLayout()
lay.addWidget(self.uiView)
self.setLayout(lay)
self.populate()
# connections
selection = self.uiView.selectionModel()
selection.currentChanged.connect(self.openEditor)
self.uiView.clicked.connect(self.openEditor)
def openEditor(self, index):
if index.isValid():
ix = index.sibling(index.row(), 1)
self.uiView.setCurrentIndex(ix)
self.uiView.edit(ix)
def populate(self):
model = self.uiView.model().sourceModel()
model.clear()
items = [
HotkeyItem(command='Save', shortcut='Ctrl+S'),
HotkeyItem(command='Open', shortcut='Ctrl+O'),
HotkeyItem(command='Close', shortcut='Ctrl+Q'),
HotkeyItem(command='Align Top', shortcut=''),
HotkeyItem(command='Align Bottom', shortcut=''),
HotkeyItem(command='Align Left', shortcut=''),
HotkeyItem(command='Align Right', shortcut=''),
HotkeyItem(command='Align Center', shortcut='Ctrl+O')
]
for x in items:
model.addItem(x)
self.uiView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiView.resizeColumnsToContents()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Related

QAbstractTableModel & QTableView with more than one StyledItemDelegateForColumn crashes my app

I have QTableView with QAbstractTableModel and multiple QStyledItemDelegates.
I set these delegates by setStyledItemForColumn.
In this case, my app crashes.
Crash happens when I push 1 key, or try to expand the gui to right.
But if I use one of them, my app goes well.
I think this is a kind of Qt bug.
Do you know somewhat?
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtSql
import os
import PySide2
import sys
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
class IconDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(IconDelegate, self).initStyleOption(option, index)
if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
s = option.decorationSize
s.setWidth(option.rect.width())
option.decorationSize = s
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(Delegate, self).__init__(parent=None)
def initStyleOption(self, option, index):
# super(IconDelegate, self).initStyleOption(option, index)
if index.column() == 6:
if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
s = option.decorationSize
s.setWidth(option.rect.width())
option.decorationSize = s
def createEditor(self, parent, option, index):
editor = QtWidgets.QComboBox(parent)
return editor
def setEditorData(self, editor, index):
model = index.model()
items = model.items
text = items[index.row()][index.column()]
editor.setCurrentText(text)
def setModelData(self, editor, model, index):
items = model.items
#
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super(TableView, self).__init__(parent=None)
delegate = Delegate()
self.setItemDelegate(delegate)
#Here is the crash point
# self.setItemDelegateForColumn(6, delegate)
# self.setItemDelegateForColumn(11, IconDelegate())
self.tableModel = TableModel(2, 15)
self.setModel(self.tableModel)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_1:
self.tableModel.insertRows(0)
class TableItem(object):
def __init__(self, parent=None):
self.root = False
self.word = ""
self.alignment = QtCore.Qt.AlignCenter
self.rule = ""
self.foregroundcolor = QtGui.QColor(QtCore.Qt.black)
self.backgroundcolor = QtGui.QColor(QtCore.Qt.white)
self.font = QtGui.QFont("Meiryo", 14)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, row = 0, column = 0, parent = None):
super(TableModel, self).__init__(parent = None)
self.items = [[TableItem() for c in range(column)] for r in range(row)]
self.root = QtCore.QModelIndex()
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, parent=QtCore.QModelIndex()):
return 15
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
item = self.items[row][column]
return item
def headerData(self, section, orientation, role = QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Orientation.Horizontal:
if role == QtCore.Qt.DisplayRole:
return alphabet[section]
return super(TableModel, self).headerData(section, orientation, role)
def flags(self, index):
return QtCore.Qt.ItemFlag.ItemIsEditable|QtCore.Qt.ItemFlag.ItemIsEnabled|QtCore.Qt.ItemFlag.ItemIsSelectable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row, column = index.row(), index.column()
self.items[row][column] = value
self.dataChanged.emit(index, index)
return True
elif role == QtCore.Qt.DisplayRole:
row, column = index.row(), index.column()
self.items[row][column] = value
self.dataChanged.emit(index, index)
return True
elif role == QtCore.Qt.FontRole:
string = value.toString()
s = string.split(",")
font = s[0]
self.dataChanged.emit(index, index)
return True
def insertRows(self, position, rows=1, index=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), position, position+rows-1)
for row in range(rows):
self.items.insert(position+row, [TableItem() for c in range(self.columnCount())])
self.endInsertRows()
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
self.emit(QtCore.SIGNAL("layoutChanged()"))
return True
def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
self.beginRemoveRows(QtCore.QModelIndex(), position, position+rows-1)
for row in range(rows):
self.items = self.items[:position] + \
self.items[position + rows:]
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
self.emit(QtCore.SIGNAL("layoutChanged()"))
return True
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
mainwindow = TableView()
mainwindow.show()
sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
main()
Explanation
It is not a Qt bug but it is a bug of your own code.
First of all it is recommended that you run your code from the CMD / console so that you get error information, if you do you will see that the error message is:
Traceback (most recent call last):
File "main.py", line 38, in setEditorData
editor.setCurrentText(text)
TypeError: 'PySide2.QtWidgets.QComboBox.setCurrentText' called with wrong argument types:
PySide2.QtWidgets.QComboBox.setCurrentText(TableItem)
Supported signatures:
PySide2.QtWidgets.QComboBox.setCurrentText(str)
The error clearly indicates that the setCurrentText method expects a string but is receiving a TableItem. Why do you receive a TableItem? Well with your code items[index.row()][index.column()] returns a TableItem, assuming you want to get the text "word" then you must use:
def setEditorData(self, editor, index):
model = index.model()
items = model.items
item = items[index.row()][index.column()]
text = item.word
editor.setCurrentText(text)
In both cases (setItemDelegate or setItemD) it causes the error.
But the error still persists when the window is resized since it is caused by the other delegate. Since you are partially overriding a delegate then the other party continues to use the generic information, for example expect index.data(Qt.DisplayRole) to return a string but in your case return a TableItem:
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
item = self.items[row][column]
return item
In conclusion, the OP have not correctly used the default roles, causing delegates who use this information to obtain the incorrect data.
Solution
Considering all of the above, I have corrected many errors that I have not mentioned previously because many are trivial or are out of topic, obtaining the following code:
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtSql
import os
import PySide2
import sys
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, "plugins", "platforms")
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = plugin_path
alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
class IconDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(IconDelegate, self).initStyleOption(option, index)
if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
s = option.decorationSize
s.setWidth(option.rect.width())
option.decorationSize = s
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
s = option.decorationSize
s.setWidth(option.rect.width())
option.decorationSize = s
def createEditor(self, parent, option, index):
editor = QtWidgets.QComboBox(parent)
return editor
#
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super(TableView, self).__init__(parent=None)
delegate = Delegate(self)
# self.setItemDelegate(delegate)
# Here is the crash point
self.setItemDelegateForColumn(6, delegate)
icon_delegate = IconDelegate(self)
self.setItemDelegateForColumn(11, icon_delegate)
self.tableModel = TableModel(2, 15)
self.setModel(self.tableModel)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_1:
self.tableModel.insertRows(0)
class TableItem(object):
def __init__(self, parent=None):
self.root = False
self.word = ""
self.alignment = QtCore.Qt.AlignCenter
self.rule = ""
self.foregroundcolor = QtGui.QColor(QtCore.Qt.black)
self.backgroundcolor = QtGui.QColor(QtCore.Qt.white)
self.font = QtGui.QFont("Meiryo", 14)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, row=0, column=0, parent=None):
super(TableModel, self).__init__(parent=None)
self.items = [[TableItem() for c in range(column)] for r in range(row)]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, parent=QtCore.QModelIndex()):
if self.items:
return len(self.items[0])
return 0
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
column = index.column()
if 0 <= row < self.rowCount() and 0 <= column < self.columnCount():
item = self.items[row][column]
if role == QtCore.Qt.DisplayRole:
text = item.word
return text
elif role == QtCore.Qt.EditRole:
return item
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Orientation.Horizontal:
if role == QtCore.Qt.DisplayRole and section < len(alphabet):
return alphabet[section]
return super(TableModel, self).headerData(section, orientation, role)
def flags(self, index):
return (
QtCore.Qt.ItemFlag.ItemIsEditable
| QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsSelectable
)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row, column = index.row(), index.column()
self.items[row][column].word = value
self.dataChanged.emit(index, index)
return True
elif role == QtCore.Qt.DisplayRole:
row, column = index.row(), index.column()
self.items[row][column].word = value
self.dataChanged.emit(index, index)
return True
return False
def insertRows(self, position, rows=1, index=QtCore.QModelIndex()):
self.beginInsertRows(QtCore.QModelIndex(), position, position + rows - 1)
for row in range(rows):
self.items.insert(
position + row, [TableItem() for c in range(self.columnCount())]
)
self.endInsertRows()
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
self.emit(QtCore.SIGNAL("layoutChanged()"))
return True
def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1)
for row in range(rows):
self.items = self.items[:position] + self.items[position + rows :]
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
self.emit(QtCore.SIGNAL("layoutChanged()"))
return True
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
mainwindow = TableView()
mainwindow.show()
sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
main()

PyQT5 filter proxy model filtering with multiple comboboxes [duplicate]

I am trying to make a PyQt5 GUI to show a Pandas dataframe in the form of a table and provide column filtering options, similar to the Microsoft Excel filters. So far I managed to adopt a similar SO answer. Here is the picture of my table in the GUI:
As shown in the figure above, there are two ways to filter columns: the Regex Filter and clicking on each column. There is however a problem I need help to address: the currently applied filters (either regex filter or column click) disappear when I filter a second column. I want the second filter as AND, i.e. a filter that satisfies column 1 AND column 2.
Here is my code:
#!/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()
def toDataFrame(self):
return self._df.copy()
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.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 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 = QtWidgets.QTableView(self.centralwidget)
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 = QtCore.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
#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
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
search = QtCore.QRegExp( text,
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(search)
#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(800, 600)
sys.exit(app.exec_())
If you want to implement a custom filtering process then you must override the filterAcceptsRow method, obtain the texts of each column and verify if they meet the condition, if they do return True, otherwise False. To recalculate the filter you must call the invalidateFilter method:
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 load_sites(self):
# ...
self.model = PandasModel(df)
self.proxy = CustomProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.resizeColumnsToContents()
print("finished loading sites")
# ...
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
filterColumn = self.logicalIndex
self.proxy.setFilter("", filterColumn)
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
self.proxy.setFilter(stringAction, filterColumn)
#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)
Plus:
If you want to change the font of the QHeaderView then you must return the font in the headerData as shown below:
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())
# ...
class myWindow(QtWidgets.QMainWindow):
# ...
#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)

Change state of checkbox programmatically in qtableview

I found this model to visualize qtableview with checkboxes. It works, but now I want also to change the state of checkboxes programmatically (for example a button that check/uncheck all checkboxes). I have no idea how I can do it...
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
You have to use the setData() method, in this method the dataChanged signal must be emitted when the value associated with a role changes:
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index)
return True
return False
If you want to check and uncheck the items you must iterate over the items:
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.button = QPushButton("Checked", checkable=True)
self.button.clicked.connect(self.on_clicked)
self.view = QTableView()
self.model = TableModel(self)
self.view.setModel(self.model)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.view)
#pyqtSlot(bool)
def on_clicked(self, state):
c = 0
for r in range(self.model.rowCount()):
ix = self.model.index(r, c)
self.model.setData(
ix, Qt.Checked if state else Qt.Unchecked, Qt.CheckStateRole
)
self.button.setText("Unchecked" if state else "Checked")

QTableView model using QComboBox

In this example below I have a simple QTableView which is populated using an AbstractModel. Each row in the table displays information related to a class object called Asset. It has a property called Items that contains a list of strings. I want to know how can i populate the QTableView with a combobox displaying this list of strings for each row.
Secondly when a user changes the item selected in the dropdown, i would like to trigger an event so i can later use it to properly change the color of the colored dot to green or red depending on the object's property called 'Status'
The status would indicate if the Current Version (meaning the latest item in the dropdown list) is the chosen item. If its the last item in the list, meaning the latest item, it would be green, otherwise it's red.
The property 'Active' indicates which item in the dropdownlist is currently selected.
If the status is 0 then it's out dated and if the status is 1 that means the latest version in the dropdownlist is being used.
import sys
from PySide import QtGui, QtCore
class Asset(object):
def __init__(self, name, items=None, status=0, active=0):
self._status = 0
self._name = ''
self._items = []
self._active = active
self.name = name
self.items = items if items != None else []
self.status = status
class AssetModel(QtCore.QAbstractTableModel):
attr = ["Name", "Options"]
def __init__(self, *args, **kwargs):
QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
self._items = []
def clear(self):
self._items = []
self.reset()
def rowCount(self, index=QtCore.QModelIndex()):
return len(self._items)
def columnCount(self, index=QtCore.QModelIndex()):
return len(self.attr)
def addItem(self, sbsFileObject):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(sbsFileObject)
self.endInsertRows()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return AssetModel.attr[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def getItem(self, index):
row = index.row()
if index.isValid() and 0 <= row < self.rowCount():
return index.data(role=QtCore.Qt.UserRole)
return None
def getSelectedItems(self, selection):
objs = []
for i, index in enumerate(selection):
item = self.getItem(index)
objs.append(item)
return objs
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
col = index.column()
if 0 <= col < self.columnCount():
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'name', '')
if col == 1:
return (getattr(item, 'items', []))
elif role == QtCore.Qt.UserRole:
if col == 0:
return item
elif role == QtCore.Qt.DecorationRole:
if col == 0:
status = getattr(item, 'status', 0)
col = QtGui.QColor(255,0,0,255)
if status == 1:
col = QtGui.QColor(255,128,0,255)
elif status == 2:
col = QtGui.QColor(255,255,0,255)
px = QtGui.QPixmap(120,120)
px.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(px)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
px_size = px.rect().adjusted(12,12,-12,-12)
painter.setBrush(col)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawEllipse(px_size)
painter.end()
return QtGui.QIcon(px)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.resize(400,300)
# controls
asset_model = QtGui.QSortFilterProxyModel()
asset_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
asset_model.setSourceModel(AssetModel())
self.ui_assets = QtGui.QTableView()
self.ui_assets.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_assets.setModel(asset_model)
self.ui_assets.verticalHeader().hide()
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(self.ui_assets)
self.setLayout(main_layout)
self.unit_test()
def unit_test(self):
assets = [
Asset('Doug', ['v01', 'v02', 'v03'], 0),
Asset('Amy', ['v10', 'v11', 'v13'], 1),
Asset('Kevin', ['v11', 'v22', 'v53'], 2),
Asset('Leslie', ['v13', 'v21', 'v23'], 0)
]
self.ui_assets.model().sourceModel().clear()
for i, obj in enumerate(assets):
self.ui_assets.model().sourceModel().addItem(obj)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have 2 tasks:
Make your model editable because when using the combobox you must edit the values, in addition you must implement new roles to access all the properties of Asset, for it modify the class Asset:
class Asset(object):
def __init__(self, name, items=[], active=0):
self.active = active
self.name = name
self.items = items
#property
def status(self):
return self.active == len(self.items) - 1
To make an editable model, you must implement the setData() method and enable the Qt.ItemIsEditable flag:
class AssetModel(QtCore.QAbstractTableModel):
attr = ["Name", "Options"]
ItemsRole = QtCore.Qt.UserRole + 1
ActiveRole = QtCore.Qt.UserRole + 2
def __init__(self, *args, **kwargs):
QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
self._items = []
def flags(self, index):
fl = QtCore.QAbstractTableModel.flags(self, index)
if index.column() == 1:
fl |= QtCore.Qt.ItemIsEditable
return fl
def clear(self):
self.beginResetModel()
self._items = []
self.endResetModel()
def rowCount(self, index=QtCore.QModelIndex()):
return len(self._items)
def columnCount(self, index=QtCore.QModelIndex()):
return len(self.attr)
def addItem(self, sbsFileObject):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(sbsFileObject)
self.endInsertRows()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return AssetModel.attr[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
col = index.column()
if role == AssetModel.ItemsRole:
return getattr(item, 'items')
if role == AssetModel.ActiveRole:
return getattr(item, 'active')
if 0 <= col < self.columnCount():
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'name', '')
if col == 1:
return getattr(item, 'items')[getattr(item, 'active')]
elif role == QtCore.Qt.DecorationRole:
if col == 0:
status = getattr(item, 'status')
col = QtGui.QColor(QtCore.Qt.red) if status else QtGui.QColor(QtCore.Qt.green)
px = QtGui.QPixmap(120, 120)
px.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(px)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
px_size = px.rect().adjusted(12, 12, -12, -12)
painter.setBrush(col)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
QtCore.Qt.SolidLine,
QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter.drawEllipse(px_size)
painter.end()
return QtGui.QIcon(px)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
if role == AssetModel.ActiveRole:
setattr(item, 'active', value)
return True
return QtCore.QAbstractTableModel.setData(self, index, value, role)
Use a delegate, for it you must overwrite the methods createEditor(), setEditorData() and setModelData() where we created the QComboBox, updated the selection of the QComboBox with the information of the model, and updated the model with the selection of the QComboBox. We also use paint() to make the QComboBox persistent.
class AssetDelegate(QtGui.QStyledItemDelegate):
def paint(self, painter, option, index):
if isinstance(self.parent(), QtGui.QAbstractItemView):
self.parent().openPersistentEditor(index)
QtGui.QStyledItemDelegate.paint(self, painter, option, index)
def createEditor(self, parent, option, index):
combobox = QtGui.QComboBox(parent)
combobox.addItems(index.data(AssetModel.ItemsRole))
combobox.currentIndexChanged.connect(self.onCurrentIndexChanged)
return combobox
def onCurrentIndexChanged(self, ix):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor, QtGui.QAbstractItemDelegate.NoHint)
def setEditorData(self, editor, index):
ix = index.data(AssetModel.ActiveRole)
editor.setCurrentIndex(ix)
def setModelData(self, editor, model, index):
ix = editor.currentIndex()
model.setData(index, ix, AssetModel.ActiveRole)
Then we establish the delegate and pass it as a parent to the QTableView so that it can be persisted automatically:
self.ui_assets.setItemDelegateForColumn(1, AssetDelegate(self.ui_assets))
The complete code can be found at the following link.

QColorDialog and QItemDelegate

I have a QTableView and I'm Using a QItemDelegate in it as button.
I'm trying to change the color of the button, so when i click on it i call a QColorDialog. But then I'm having trouble sending the color it back to the button.
Here's how it is going so far:
the Button and QColorDialog:
class ButtonDelegate(QItemDelegate):
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
btn = QPushButton('')
btn.setStyleSheet("background-color:rgb(86,12,65)")
ix = QPersistentModelIndex(index)
btn.clicked.connect(lambda ix = ix : self.onClicked(ix))
layout.addWidget(btn)
layout.setContentsMargins(2,2,2,2)
if not self.parent().indexWidget(index):
self.parent().setIndexWidget(index, widget)
def onClicked(self, ix):
colorPicker = QColorDialog.getColor()
#colorPicker.show()
r = str(colorPicker.red())
g = str(colorPicker.red())
b = str(colorPicker.red())
bgcolor = 'background-color:rgb(' + r + ',' + g + ',' + b +')'
What's the next step? I tried declaring the button in the delegate init as self.btn = QPushButton() and then reuse it at the onCLick Method, but the button don't even get drawn that way.
Some enlightenment?
Thank you!
edit 1
Model:
class Model(QAbstractTableModel):
def __init__(self, vtxTable, parent = None):
QAbstractTableModel.__init__(self, parent)
#data
self.groups = []
#header
self.header_labels = ['Color', 'Group', 'Done']
self.vtxTable = vtxTable
def rowCount(self, parent):
return len(self.groups)
def columnCount(self, parent):
return 3
def flags(self, index):
if index.column() == 2:
fl = fl = Qt.ItemIsEnabled | Qt.ItemIsSelectable
else:
fl = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
return fl
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.header_labels[section]
def removeRow(self, row, parent = QModelIndex()):
self.beginRemoveRows(parent, row, row)
self.groups.remove(self.groups[row])
self.endRemoveRows()
self.vtxTable.setModel(QStringListModel())
def insertRows(self, position, row, values = [] , parent = QModelIndex()):
lastposition = self.rowCount(0)
self.beginInsertRows(parent, lastposition, lastposition)
self.groups.insert(lastposition, values)
self.endInsertRows()
def setData(self, index, value, role = Qt.EditRole):
setIt = False
value = value
row = index.row()
column = index.column()
if role == Qt.EditRole:
setIt = True
if not len(value) == 0:
if value in self.getGrpNames():
warning("Group must have a unique name.")
setIt = False
else:
setIt = True
else:
warning("New group must have a name.")
setIt = False
if role == Qt.BackgroundRole:
setIt = True
if setIt:
self.groups[row][column] = value
self.dataChanged.emit(row, column)
return False
def data(self, index, role):
if not index.isValid():
return
row = index.row()
column = index.column()
if role == Qt.DisplayRole:
if column == 0:
#value = [self.groups[row][column].redF(), self.groups[row][column].greenF(), self.groups[row][column].blueF()]
value = self.groups[row][column]
else:
value = self.groups[row][column]
return value
elif role == Qt.TextAlignmentRole:
return Qt.AlignCenter;
elif role == Qt.EditRole:
index = index
return index.data()
elif role == Qt.BackgroundRole and column == 0:
value = self.groups[row][column]
def getGrpNames(self):
rows = self.rowCount(1)
grps = []
for row in range(rows):
grp = self.index(row, 1).data()
grps.append(grp)
return grps
def getAllVtx(self):
rows = self.rowCount(1)
allVtxs = []
for row in range(rows):
index = self.createIndex(row, 3)
vtxs = index.data()
for vtx in vtxs:
allVtxs.append(vtx)
return allVtxs
def getData(self):
rows = self.rowCount(1)
data = {}
for row in range(rows):
color = self.index(row, 0).data()
grp = self.index(row, 1).data()
done = self.index(row, 2).data()
vtxs = self.groups[row][3]
#index = self.createIndex(row,0)
data[grp] = [grp, color, done, vtxs]
return data
def queryVtx(self, vtx):
data = self.getData()
for key in data:
vtxs = data[key][3]
if vtx in vtxs:
return data[key][0]
else:
return False
Table View:
class Table(QTableView):
def __init__(self, *args, **kwargs):
QTableView.__init__(self, *args, **kwargs)
self.setItemDelegateForColumn(0, colorDelegate(self))
hHeader = self.horizontalHeader()
#hHeader.setSectionResizeMode(QHeaderView.Fixed);
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)
vHeader = self.verticalHeader()
vHeader.hide()
When clicking on the first cell of the row I'd like to be able top pick a color for it, and save it in the model.
Thank you.
The goal of the delegates is to customize each item that is displayed in a QAbstractItemView, the other way to do it is by inserting a widget using the indexWidget method. The advantage of the delegates is that the memory consumption is minimal. It is advisable not to use them at the same time.
The delegates have the following methods:
paint(): is the method responsible for drawing the item that is normally displayed
createEditor(): is the method is responsible for creating the editor.
setEditorData(): get the value of the model and establish it in the editor.
setModelData(): save the data obtained from the editor to the model.
An example of that type of delegate is the following:
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
class Delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
dialog = QColorDialog(parent)
return dialog
def setEditorData(self, editor, index):
color = index.data(Qt.BackgroundRole)
editor.setCurrentColor(color)
def setModelData(self, editor, model, index):
color = editor.currentColor()
model.setData(index, color, Qt.BackgroundRole)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = QStandardItemModel(4, 4)
for i in range(model.rowCount()):
for j in range(model.columnCount()):
color = QColor(qrand() % 256, qrand() % 256, qrand() % 256)
it = QStandardItem("{}{}".format(i, j))
it.setData(color, Qt.BackgroundRole)
model.setItem(i, j, it)
w = QTableView()
w.setModel(model)
w.setItemDelegate(Delegate())
w.show()
sys.exit(app.exec_())
Update:
I have improved your code since it had unnecessary elements, the main error is that these Qt::BackgroundRole must return a QColor
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import warnings
class Model(QAbstractTableModel):
def __init__(self, parent = None):
QAbstractTableModel.__init__(self, parent)
#data
self.groups = []
#header
self.header_labels = ['Color', 'Group', 'Done']
def rowCount(self, parent=QModelIndex()):
return len(self.groups)
def columnCount(self, parent=QModelIndex()):
return 3
def flags(self, index):
fl = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if not index.column() in (2, ):
fl |= Qt.ItemIsEditable
return fl
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.header_labels[section]
def removeRow(self, row, parent = QModelIndex()):
self.beginRemoveRows(parent, row, row)
self.groups.remove(self.groups[row])
self.endRemoveRows()
def insertRows(self, values = [], position=-1):
if position == -1:
position = self.rowCount()
self.beginInsertRows(QModelIndex(), position, position)
self.groups.insert(position, values)
self.endInsertRows()
def setData(self, index, value, role = Qt.EditRole):
setIt = False
row = index.row()
column = index.column()
if role == Qt.EditRole:
setIt = True
if len(value) != 0:
if value in self.getGrpNames():
warning("Group must have a unique name.")
setIt = False
else:
setIt = True
else:
warning("New group must have a name.")
setIt = False
if role == Qt.BackgroundRole:
setIt = True
if setIt:
self.groups[row][column] = value
self.dataChanged.emit(index, index)
return False
def data(self, index, role):
if not index.isValid():
return
row = index.row()
column = index.column()
value = None
if role == Qt.DisplayRole:
if column == 0:
value = self.groups[row][column]
else:
value = self.groups[row][column]
elif role == Qt.TextAlignmentRole:
value = Qt.AlignCenter;
elif role == Qt.BackgroundRole and column == 0:
value = QColor(self.groups[row][column])
return value
def getGrpNames(self):
rows = self.rowCount(1)
grps = []
for row in range(rows):
grp = self.index(row, 1).data()
grps.append(grp)
return grps
def getAllVtx(self):
rows = self.rowCount(1)
allVtxs = []
for row in range(rows):
index = self.createIndex(row, 3)
vtxs = index.data()
for vtx in vtxs:
allVtxs.append(vtx)
return allVtxs
def getData(self):
rows = self.rowCount(1)
data = {}
for row in range(rows):
color = self.index(row, 0).data()
grp = self.index(row, 1).data()
done = self.index(row, 2).data()
vtxs = self.groups[row][3]
#index = self.createIndex(row,0)
data[grp] = [grp, color, done, vtxs]
return data
def queryVtx(self, vtx):
data = self.getData()
for key in data:
vtxs = data[key][3]
if vtx in vtxs:
return data[key][0]
else:
return False
class ColorDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
dialog = QColorDialog(parent)
return dialog
def setEditorData(self, editor, index):
color = index.data(Qt.BackgroundRole)
editor.setCurrentColor(color)
def setModelData(self, editor, model, index):
color = editor.currentColor()
model.setData(index, color, Qt.BackgroundRole)
class Table(QTableView):
def __init__(self, *args, **kwargs):
QTableView.__init__(self, *args, **kwargs)
self.setItemDelegateForColumn(0, ColorDelegate(self))
hHeader = self.horizontalHeader()
#hHeader.setSectionResizeMode(QHeaderView.Fixed);
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)
vHeader = self.verticalHeader()
vHeader.hide()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Table()
model = Model()
w.setModel(model)
model.insertRows(["red", "group1", "no"])
model.insertRows(["blue", "group1", "no"], 0)
w.show()
sys.exit(app.exec_())

Categories

Resources