I am dynamically creating a QTableView from a Pandas dataframe. I have example code here.
I can create the table, with the checkboxes but I cannot get the checkboxes to reflect the model data, or even to change at all to being unchecked.
I am following example code from this previous question and taking #raorao answer as a guide. This will display the boxes in the table, but non of the functionality is working.
Can anyone suggest any changes, or what is wrong with this code. Why is it not reflecting the model, and why can it not change?
Do check out my full example code here.
Edit one : Update after comment from Frodon :
corrected string cast to bool with a comparison xxx == 'True'
class CheckBoxDelegate(QtGui.QStyledItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
'''
Important, otherwise an editor is created if the user clicks in this cell.
** Need to hook up a signal to the model
'''
return None
def paint(self, painter, option, index):
'''
Paint a checkbox without the label.
'''
checked = index.model().data(index, QtCore.Qt.DisplayRole) == 'True'
check_box_style_option = QtGui.QStyleOptionButton()
if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
check_box_style_option.state |= QtGui.QStyle.State_Enabled
else:
check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
if checked:
check_box_style_option.state |= QtGui.QStyle.State_On
else:
check_box_style_option.state |= QtGui.QStyle.State_Off
check_box_style_option.rect = self.getCheckBoxRect(option)
# this will not run - hasFlag does not exist
#if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
#check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
check_box_style_option.state |= QtGui.QStyle.State_Enabled
QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)
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 or presses
Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
'''
print 'Check Box editor Event detected : '
if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
print 'Check Box edior Event detected : passed first check'
# Do not change the checkbox-state
if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
return False
if event.type() == QtCore.QEvent.MouseButtonDblClick:
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
return False
else:
return False
# Change the checkbox-state
self.setModelData(None, model, index)
return True
Here's a port of the same code above for PyQt5.
Posting here as there doesn't appear to be another example of a working CheckBox Delegate in QT5, and i was tearing my hair out trying to get it working. Hopefully it's useful to someone.
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView
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)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = QStandardItemModel(4, 3)
tableView = QTableView()
tableView.setModel(model)
delegate = CheckBoxDelegate(None)
tableView.setItemDelegateForColumn(1, delegate)
for row in range(4):
for column in range(3):
index = model.index(row, column, QModelIndex())
model.setData(index, 1)
tableView.setWindowTitle("Check Box Delegate")
tableView.show()
sys.exit(app.exec_())
I've found a solution for you. The trick was:
to write the setData method of the model
to always return a QVariant in the data method
Here it is. (I had to create a class called Dataframe, to make the example work without pandas. Please replace all the if has_pandas statements by yours):
from PyQt4 import QtCore, QtGui
has_pandas = False
try:
import pandas as pd
has_pandas = True
except:
pass
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(TableModel, self).__init__()
self.datatable = None
self.headerdata = None
def update(self, dataIn):
print 'Updating Model'
self.datatable = dataIn
print 'Datatable : {0}'.format(self.datatable)
if has_pandas:
headers = dataIn.columns.values
else:
headers = dataIn.columns
header_items = [
str(field)
for field in headers
]
self.headerdata = header_items
print 'Headers'
print self.headerdata
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.index)
def columnCount(self, parent=QtCore.QModelIndex()):
if has_pandas:
return len(self.datatable.columns.values)
else:
return len(self.datatable.columns)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return QtCore.QVariant('{0}'.format(self.datatable.iget_value(i, j)))
else:
return QtCore.QVariant()
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
if index.column() == 4:
self.datatable.iset_value(index.row(), 4, value)
return value
return value
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return '{0}'.format(self.headerdata[col])
def flags(self, index):
if index.column() == 4:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
else:
return QtCore.Qt.ItemIsEnabled
class TableView(QtGui.QTableView):
"""
A simple table to demonstrate the QComboBox delegate.
"""
def __init__(self, *args, **kwargs):
QtGui.QTableView.__init__(self, *args, **kwargs)
self.setItemDelegateForColumn(4, CheckBoxDelegate(self))
class CheckBoxDelegate(QtGui.QStyledItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
'''
Important, otherwise an editor is created if the user clicks in this cell.
** Need to hook up a signal to the model
'''
return None
def paint(self, painter, option, index):
'''
Paint a checkbox without the label.
'''
checked = index.data().toBool()
check_box_style_option = QtGui.QStyleOptionButton()
if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
check_box_style_option.state |= QtGui.QStyle.State_Enabled
else:
check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
if checked:
check_box_style_option.state |= QtGui.QStyle.State_On
else:
check_box_style_option.state |= QtGui.QStyle.State_Off
check_box_style_option.rect = self.getCheckBoxRect(option)
# this will not run - hasFlag does not exist
#if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
#check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
check_box_style_option.state |= QtGui.QStyle.State_Enabled
QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)
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 or presses
Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
'''
print 'Check Box editor Event detected : '
print event.type()
if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
print 'Check Box editor Event detected : passed first check'
# Do not change the checkbox-state
if event.type() == QtCore.QEvent.MouseButtonPress:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
return False
if event.type() == QtCore.QEvent.MouseButtonDblClick:
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
return False
else:
return False
# Change the checkbox-state
self.setModelData(None, model, index)
return True
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
print 'SetModelData'
newValue = not index.data().toBool()
print 'New Value : {0}'.format(newValue)
model.setData(index, newValue, QtCore.Qt.EditRole)
def getCheckBoxRect(self, option):
check_box_style_option = QtGui.QStyleOptionButton()
check_box_rect = QtGui.QApplication.style().subElementRect(QtGui.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
check_box_point = QtCore.QPoint (option.rect.x() +
option.rect.width() / 2 -
check_box_rect.width() / 2,
option.rect.y() +
option.rect.height() / 2 -
check_box_rect.height() / 2)
return QtCore.QRect(check_box_point, check_box_rect.size())
###############################################################################################################################
class Dataframe(dict):
def __init__(self, columns, values):
if len(values) != len(columns):
raise Exception("Bad values")
self.columns = columns
self.values = values
self.index = values[0]
super(Dataframe, self).__init__(dict(zip(columns, values)))
pass
def iget_value(self, i, j):
return(self.values[j][i])
def iset_value(self, i, j, value):
self.values[j][i] = value
if __name__=="__main__":
from sys import argv, exit
class Widget(QtGui.QWidget):
"""
A simple test widget to contain and own the model and table.
"""
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
l=QtGui.QVBoxLayout(self)
cdf = self.get_data_frame()
self._tm=TableModel(self)
self._tm.update(cdf)
self._tv=TableView(self)
self._tv.setModel(self._tm)
for row in range(0, self._tm.rowCount()):
self._tv.openPersistentEditor(self._tm.index(row, 4))
self.setGeometry(300, 300, 550, 200)
l.addWidget(self._tv)
def get_data_frame(self):
if has_pandas:
df = pd.DataFrame({'Name':['a','b','c','d'],
'First':[2.3,5.4,3.1,7.7], 'Last':[23.4,11.2,65.3,88.8], 'Class':[1,1,2,1], 'Valid':[True, False, True, False]})
else:
columns = ['Name', 'First', 'Last', 'Class', 'Valid']
values = [['a','b','c','d'], [2.3,5.4,3.1,7.7], [23.4,11.2,65.3,88.8], [1,1,2,1], [True, False, True, False]]
df = Dataframe(columns, values)
return df
a=QtGui.QApplication(argv)
w=Widget()
w.show()
w.raise_()
exit(a.exec_())
I added
if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.MouseMove:
return False
to prevent checkbox from flickering when moving the mouse
Related
I know how to drag rows
QTableView.verticalHeader().setSectionsMovable(True)
QTableView.verticalHeader().setDragEnabled(True)
QTableView.verticalHeader().setDragDropMode(qtw.QAbstractItemView.InternalMove)
but I want to be able to drag and drop just a single (or a pair of) cell.
Can anyone point me in the right direction?
PS: my current idea would be to intercept the clicked() -> dragEnter() -> dragLeave() or fork to dragDrop() -> dragIndicatorPosition()
events, but it sounds kinda convoluted
I did read this but I am confused on how to implement it, especially the section "Enabling drag and drop for items" seems to address exactly what I need. I'll see if I can post a "slim" example.
EDIT:
here an example with some other stuff. in MyStandardItemModel I try to do the trick:
from PyQt5 import QtCore, QtWidgets, QtSql
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView, QTableWidget
class MyStandardItemModel(QStandardItemModel):
def __init__(self, parent=None, *arg, **kwargs):
super().__init__(parent, *arg, **kwargs)
self.__readonly_cols = []
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
try:
default_Flags = QStandardItemModel.flags(self,index)
if (index.column() in self.__readonly_cols):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
else:
if (index.isValid()):
return default_Flags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
else:
return default_Flags
except Exception as ex:
print(ex)
def setReadOnly(self, columns: [int]):
for i in columns:
if i <= (self.columnCount() - 1) and i not in self.__readonly_cols:
self.__readonly_cols.append(i)
def resetReadOnly(self):
self.__readonly_cols = []
class MyTableView(QtWidgets.QTableView):
def __init__(self, parent=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
# signal to inform clicking
user_click = QtCore.pyqtSignal(int, int, bool)
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
# if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
# return False
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
try:
if int(index.data()) == 0:
model.setData(index, 1, QtCore.Qt.EditRole)
ret = True
else:
model.setData(index, 0, QtCore.Qt.EditRole)
ret = False
# emit signal with row, col, and the status
self.user_click.emit(index.row(), index.column(), ret)
except Exception as ex:
print(ex)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = MyStandardItemModel(5, 5)
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model.setHorizontalHeaderLabels(header_labels)
tableView = MyTableView()
tableView.setModel(model)
delegate = CheckBoxDelegate(None)
tableView.setItemDelegateForColumn(0, delegate)
for row in range(5):
for column in range(4):
index = model.index(row, column, QModelIndex())
model.setData(index, 0)
model.setReadOnly([1,2])
tableView.setWindowTitle("CheckBox, readonly and drag&drop example")
tableView.show()
sys.exit(app.exec_())
I'm using Pyside2, Python 3.8. I have a QTableView, the value of the first column is a bool, I want to implement a delegate to paint Editable CheckBoxs in the first column. I've tried many SO answers, the one that've been the most helpful is this one.
It partially works, ie: You have to double click the cell, then you get a 'kind of' combobox instead of a checkbox in my first column (see picture below)
I've managed to do this without going through a Delegate, using the CkeckStateRole, it worked, the checkboxs aren't editable, but I have many editable other columns and It seems that there's no escape from implementing a delegate as it is the more proper way to do it.
here's my Table Model class:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = []
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
if index.column() == 0:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self._items[index.row()][index.column()]
elif role == QtCore.Qt.CheckStateRole:
return None
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
Here's my CheckBox Delegate class:
class CheckBoxDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent = None):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
if not (QtCore.Qt.ItemIsEditable & index.flags()):
return None
check = QtWidgets.QCheckBox(parent)
check.clicked.connect(self.stateChanged)
return check
def setEditorData(self, editor, index):
editor.blockSignals(True)
editor.setChecked(index.data())
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
def paint(self, painter, option, index):
value = index.data()
if value:
value = QtCore.Qt.Checked
else:
value = QtCore.Qt.Unchecked
self.drawCheck(painter, option, option.rect, value)
self.drawFocus(painter, option, option.rect)
#QtCore.Slot()
def stateChanged(self):
print("sender", self.sender())
self.commitData.emit(self.sender())
Edit #1
After some research, I've found out that I've forgot to put self.MyTableView.setItemDelegateForColumn(0, CheckBoxDelegate(self)) in my MainWindow __init__
Well, What happens now is I get a centered CheckBox in my first column, I can't edit it, but, when I double click it, It creates a second Checkbox in the same cell at the left, which is Editable and also sets the new CheckState to my Model.
Let's say my centered CheckBox is set to True, I double click, a second Checked CheckBox is created to its left, I Uncheck it, I click on another cell, the second checkBox disappears, the centered checkBox is set to Unchecked, and my ModelData is updated.
(see picture below for what happens after double clicking the centered CheckBox)
Fix attempts
1. Not overriding paint():
The column contains either True or false, double clicking the cell creates an editable checkbox which sets the data in the model to the new value and then disappears.
2. the createEditor() method returns None
def createEditor(self, parent, option, index):
return None
It creates a single centered checkbox, no labels, but the checkbox isn't editable
3. Not overriding createEditor() method
Creates a single centered checkbox with no labels, double clicking the cell replace the checkbox with a combobox (True or False), from which I can change the CheckBox state.
Neither of those fixes gave me what I'm looking for: A signe centered checkbox created that is editable with a single click
There is absolutely no need for a custom delegate, as it's already supported by the default one. You just need to correctly return the ItemIsUserCheckable flag, and implement the CheckStateRole in both data() and setData().
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None, checkableColumns=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = ['aaaa', 'bbb', 'ccc']
if checkableColumns is None:
checkableColumns = []
elif isinstance(checkableColumns, int):
checkableColumns = [checkableColumns]
self.checkableColumns = set(checkableColumns)
def setColumnCheckable(self, column, checkable=True):
if checkable:
self.checkableColumns.add(column)
else:
self.checkableColumns.discard(column)
self.dataChanged.emit(
self.index(0, column), self.index(self.rowCount() - 1, column))
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
flags = QtCore.Qt.ItemIsEnabled
if index.column() in self.checkableColumns:
# only this flag is required
flags |= QtCore.Qt.ItemIsUserCheckable
return flags
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
value = self._items[index.row()][index.column()]
return QtCore.Qt.Checked if value else QtCore.Qt.Unchecked
elif (index.column() not in self.checkableColumns and
role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole)):
return self._items[index.row()][index.column()]
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
self._items[index.row()][index.column()] = bool(value)
self.dataChanged.emit(index, index)
return True
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
Then, if you want to center the checkbox whenever the index has no text, you can simply use a proxy style:
class ProxyStyle(QtWidgets.QProxyStyle):
def subElementRect(self, element, opt, widget=None):
if element == self.SE_ItemViewItemCheckIndicator and not opt.text:
rect = super().subElementRect(element, opt, widget)
rect.moveCenter(opt.rect.center())
return rect
return super().subElementRect(element, opt, widget)
# ...
app.setStyle(ProxyStyle())
How can i position the Checkbox of an item in a QListView at the top left? I tried using a stylesheet but when i resize my item, the checkbox does not maintain it's position in the top left. It becomes hidden.
Does anyone have any suggestions on how to achieve this? Do I use QItemDelegate or QStyledItemDelegate to solve my problem?
My goal is to make it appear like this...
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
from PictureShop.Resources import StyleUtils
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setModel(self.viewerModel)
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
self.setStyleSheet('''
QWidget::indicator {
subcontrol-position: top center;
position: relative;
left: 30px;
top: -20px;
}
''')
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon('C:/Users/jmartini/Desktop/brokenImage.png')
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()
Help from answers below. I have a bug shown here:
Got a working solution based on comments below:
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
class ItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
# overrides
def sizeHint(self, option, index):
isw, ish = option.decorationSize.toTuple()
return QtCore.QSize(isw, ish)
def getCheckboxRect(self, option):
return QtCore.QRect(4, 4, 18, 18).translated(option.rect.topLeft())
def paint(self, painter, option, index):
painter.save()
# Draw
isw, ish = option.decorationSize.toTuple()
x, y, dx, dy = option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height()
# Decoration
pic = index.data(QtCore.Qt.DecorationRole)
if pic:
painter.drawPixmap(x, y, pic.pixmap(isw, ish))
# Indicate Selected
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(QtGui.QBrush(QtGui.QColor(0,70,240,128)))
else:
painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
painter.drawRect(QtCore.QRect(x, y, dx, dy))
# Checkstate
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.getCheckboxRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
style = QtWidgets.QApplication.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, None
)
painter.restore()
def editorEvent(self, event, model, option, index):
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
style = QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.getCheckboxRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.uiListView.setModel(self.viewerModel)
self.uiListView.setItemDelegate(ItemDelegate())
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon("C:/Users/JokerMartini-Asus/Desktop/thumbnail_image.png")
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()
Using a delegate you can paint after the icon is painted:
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.features &= ~QtWidgets.QStyleOptionViewItem.HasCheckIndicator
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.checkRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
widget = option.widget
style = QtWidgets.QApplication.style() if widget is None else widget.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, widget
)
def checkRect(self, option):
height = option.rect.height()
x, y, w, h = (f * height for f in (0.15, 0.15, 0.2, 0.2))
return QtCore.QRect(x, y, w, h).translated(option.rect.topLeft())
def editorEvent(self, event, model, option, index):
# https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/itemviews/qstyleditemdelegate.cpp?h=5.13#n278
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
widget = option.widget
style = widget.style() if widget is not None else QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.checkRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsUserTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)
In my code I have successfully implemented Drag n Drop from one QListView to another QListView and internal move is also working fine.
Now, due to a necessity, I have modified view that accepts drops i.e. I am trying to Drag from QListView to QTableView instead.
The issue is, when I dropped on QTableView, every time it prints invalid index i.e. -1 (printing that in dropEvent().
In my previous implementation between list views, even when the item was dropped in between items, the index was appropriately updated. This is not the case here. Thanks for answering.
class SerialTestStepListView(QtGui.QTableView):
itemSelectionChanged = pyqtSignal()
casualSignal3 = pyqtSignal()
casualSignal4 = pyqtSignal()
def __init__(self,parent = None):
QListView.__init__(self, parent)
self.setAcceptDrops(True)
# Hide column here......
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setSelectionBehavior(self.SelectRows)
def dragEnterEvent(self, event):
if event.mimeData().hasFormat("application/xml-chirag"):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasFormat("application/xml-chirag"):
event.setDropAction(QtCore.Qt.MoveAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
bstream = data.retrieveData("application/xml-chirag", QtCore.QVariant.ByteArray)
selected = pickle.loads(bstream)
index = self.indexAt(event.pos()).row()
print("into the drop event")
print(index) # This is printing -1
print(self.indexAt(event.pos()))
self.emit(SIGNAL("casualSignal3"),selected, index)
event.accept()
def startDrag(self, event):
indx = self.indexAt(event.pos())
index = indx.row()
print("into the drag event")
self.emit(SIGNAL("casualSignal4"),indx, index)
if not indx.isValid():
pass
else:
return True
def mouseMoveEvent(self, event):
self.startDrag(event)
Model is:
class SerialTestListModel(QtCore.QAbstractListModel):
def __init__(self, testStep = [], parent = None):
QtCore.QAbstractListModel.__init__(self, parent)
self.__TestSteps = testStep
def rowCount(self, parent):
return len(self.__TestSteps)
def flags(self, index):
if index.isValid():
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEnabled | QtCore.Qt.MoveAction
else:
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEnabled
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
return self.__TestSteps[row]
def setData(self, index, value, role = QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
self.dataChanged.emit(index,index)
return True
return False
def insertRows(self, position, rows, selected, parent = QtCore.QModelIndex()):
if selected is None:
pass
else:
if ((position == -1) and (selected is not None)):
position = self.rowCount(parent)
self.beginInsertRows(parent, position, position + rows - 1)
for i in range(rows):
self.__TestSteps.insert(position, selected)
self.endInsertRows()
return True
def removeRows(self, position, rows, parent = QtCore.QModelIndex()):
if position == -1:
pass
else:
self.beginRemoveRows(parent, position, position + rows - 1)
for i in range(rows):
value = self.__TestSteps[position]
self.__TestSteps.remove(value)
self.endRemoveRows()
return True
Controller part:
SerialTestStepListViewHdlr = CTC.SerialTestStepListView()
SerialTestStepListViewHdlr.show()
SerialTestStepListViewHdlr.connect(SerialTestStepListViewHdlr, SIGNAL("casualSignal3"), acceptDrag)
def acceptDrag(selected, index):
SerialTestStepListModel = mod.SerialTestListModel(testStep)
#selected = str(selected)
SerialTestStepListModel.insertRows(index, 1, selected)
SerialTestStepListViewHdlr.setModel(SerialTestStepListModel)
This was resolved after replacing:
index = self.indexAt(event.pos()).row()
with
index = self.rowAt(event.pos().y())
in dropEvent() method.
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_())