PySide6 hidePopup function explanation please - python

I'm currently building a desktop application using some checkable comboboxes.
I've used the code snippet we can find easily on the web about how customizing comboboxes to make the items checkable.
It 's been working fine but I wanted each combobox list not to collapse once any item was selected.
That's where the virtual function hidePopup(self) comes in: I only had to define it within the checkable combobox class, without any code!!, to prevent the popup list from hiding... I don't understand why since there is absolutely no code but "pass" in my functions!
You'll find below the script from the checkable combobox class.
class CheckableComboBox(QtWidgets.QComboBox):
def __init__(self):
super().__init__()
self.liste_artisans = []
self.view().pressed.connect(self.handle_item_pressed)
self.setModel(QStandardItemModel(self))
def handle_item_pressed(self, index):
# getting which item is pressed
item = self.model().itemFromIndex(index)
# make it check if unchecked and vice-versa
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
self.checked_items_list()
def checked_items_list(self):
for i in range(self.count()):
text_label = self.model().item(i, 0).text()
if self.item_check_status(i) and text_label not in self.liste_artisans:
self.liste_artisans.append(text_label)
elif not self.item_check_status(i) and text_label in self.liste_artisans:
self.liste_artisans.remove(text_label)
def hidePopup(self):
pass
# method called by checked_items_list
def item_check_status(self, index):
# getting item at index
item = self.model().item(index, 0)
# return true if checked else false
return item.checkState() == Qt.Checked

Related

How to use an active QComboBox as an element of QListView in PyQt5?

I am using PyQt5 to make an application. One of my widgets will be a QListView that displays a list of required items, e.g. required to cook a particular dish, say.
For most of these, the listed item is the only possibility. But for a few items, there is more than one option that will fulfill the requirements. For those with multiple possibilities, I want to display those possibilities in a functional QComboBox. So if the user has no whole milk, they can click that item, and see that 2% milk also works.
How can I include working combo boxes among the elements of my QListView?
Below is an example that shows what I have so far. It can work in Spyder or using python -i, you just have to comment or uncomment as noted. By "work", I mean it shows the required items in QListView, but the combo boxes show only the first option, and their displays can't be changed with the mouse. However, I can say e.g. qb1.setCurrentIndex(1) at the python prompt, and then when I move the mouse pointer onto the widget, the display updates to "2% milk". I have found it helpful to be able to interact with and inspect the widget in Spyder or a python interpreter, but I still have this question. I know there are C++ examples of things like this around, but I have been unable to understand them well enough to do what I want. If we can post a working Python example of this, it will help me and others too I'm sure.
from PyQt5.QtWidgets import QApplication, QComboBox, QListView, QStyledItemDelegate
from PyQt5.QtCore import QAbstractListModel, Qt
# A delegate for the combo boxes.
class QBDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.drawText(option.rect, Qt.AlignLeft, self.parent().currentText())
# my own wrapper for the abstract list class
class PlainList(QAbstractListModel):
def __init__(self, elements):
super().__init__()
self.elements = elements
def data(self, index, role):
if role == Qt.DisplayRole:
text = self.elements[index.row()]
return text
def rowCount(self, index):
try:
return len(self.elements)
except TypeError:
return self.elements.rowCount(index)
app = QApplication([]) # in Spyder, this seems unnecessary, but harmless.
qb0 = 'powdered sugar' # no other choice
qb1 = QComboBox()
qb1.setModel(PlainList(['whole milk','2% milk','half-and-half']))
d1 = QBDelegate(qb1)
qb1.setItemDelegate(d1)
qb2 = QComboBox()
qb2.setModel(PlainList(['butter', 'lard']))
d2 = QBDelegate(qb2)
qb2.setItemDelegate(d2)
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegateForRow(1, d1)
QV.setItemDelegateForRow(2, d2)
QV.show()
app.exec_() # Comment this line out, to run in Spyder. Then you can inspect QV etc in the iPython console. Handy!
There are some misconceptions in your attempt.
First of all, setting the delegate parent as a combo box and then setting the delegate for the list view won't make the delegate show the combo box.
Besides, as the documentation clearly says:
Warning: You should not share the same instance of a delegate between views. Doing so can cause incorrect or unintuitive editing behavior since each view connected to a given delegate may receive the closeEditor() signal, and attempt to access, modify or close an editor that has already been closed.
In any case, adding the combo box to the item list is certainly not an option: the view won't have anything to do with it, and overriding the data() to show the current combo item is not a valid solution; while theoretically item data can contain any kind of object, for your purpose the model should contain data, not widgets.
In order to show a different widget for a view, you must override createEditor() and return the appropriate widget.
Then, since you probably need to keep the data available when accessing the model and for the view, the model should contain the available options and eventually return the current option or the "sub-list" depending on the situation.
Finally, rowCount() must always return the row count of the model, not that of the content of the index.
A possibility is to create a "nested model" that supports a "current index" for the selected option for inner models.
Then you could either use openPersistentEditor() or implement flags() and add the Qt.ItemIsEditable for items that contain a list model.
class QBDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
value = index.data(Qt.EditRole)
if isinstance(value, PlainList):
editor = QComboBox(parent)
editor.setModel(value)
editor.setCurrentIndex(value.currentIndex)
# submit the data whenever the index changes
editor.currentIndexChanged.connect(
lambda: self.commitData.emit(editor))
else:
editor = super().createEditor(parent, option, index)
return editor
def setModelData(self, editor, model, index):
if isinstance(editor, QComboBox):
# the default implementation tries to set the text if the
# editor is a combobox, but we need to set the index
model.setData(index, editor.currentIndex())
else:
super().setModelData(editor, model, index)
class PlainList(QAbstractListModel):
currentIndex = 0
def __init__(self, elements):
super().__init__()
self.elements = []
for element in elements:
if isinstance(element, (tuple, list)) and element:
element = PlainList(element)
self.elements.append(element)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.EditRole:
return self.elements[index.row()]
elif role == Qt.DisplayRole:
value = self.elements[index.row()]
if isinstance(value, PlainList):
return value.elements[value.currentIndex]
else:
return value
def flags(self, index):
flags = super().flags(index)
if isinstance(index.data(Qt.EditRole), PlainList):
flags |= Qt.ItemIsEditable
return flags
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
item = self.elements[index.row()]
if isinstance(item, PlainList):
item.currentIndex = value
else:
self.elements[index.row()] = value
return True
def rowCount(self, parent=None):
return len(self.elements)
app = QApplication([])
qb0 = 'powdered sugar' # no other choice
qb1 = ['whole milk','2% milk','half-and-half']
qb2 = ['butter', 'lard']
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegate(QBDelegate(QV))
## to always display the combo:
#for i in range(qlist.rowCount()):
# index = qlist.index(i)
# if index.flags() & Qt.ItemIsEditable:
# QV.openPersistentEditor(index)
QV.show()
app.exec_()

Dynamically add QTableView to dynamically created tab pages (QTabWidget)

I am trying to have a series of QTableView created at runtime and added to newly created pages of a multipage QTabWidget.
All seems to go fine, but the QTableView don't show up.
The QTabWidget gets zeroed (reset to no pages) and refurbished (...) flawlessly (at least it looks like so) depending on the selection of a combobox (and the dictionaries therein related).
I am also using a delegate callback to include a column of checkboxes to the QTableView (thanks to https://stackoverflow.com/a/50314085/7710452), which works fine stand alone.
Here is the code.
Main Window
EDIT
as recommended by eyllanesc, here is the standalone module (jump to the end of the post for details on the part I think is problematic):
"""
qt5 template
"""
import os
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import uic
from configparser import ConfigParser, ExtendedInterpolation
from lib.SearchControllers import findGuis, get_controller_dict, show_critical, show_exception
import resources.resources
from lib.CheckBoxesDelegate import CheckBoxDelegate
myForm_2, baseClass = uic.loadUiType('./forms/setup.ui')
class MainWindow(baseClass):
def __init__(self, config_obj: ConfigParser,
config_name: str,
proj_name: str,
*args,
**kwargs):
super().__init__(*args, **kwargs)
self.ui = myForm_2()
self.ui.setupUi(self)
# your code begins here
self.setWindowTitle(proj_name + " Setup")
self.ui.logo_lbl.setPixmap(qtg.QPixmap(':/logo_Small.png'))
self.config_obj = config_obj
self.config_name = config_name
self.proj_filename = proj_name
self.proj_config = ConfigParser(interpolation=ExtendedInterpolation())
self.proj_config.read(proj_name)
self.guis_dict = {}
self.components = {}
self.cdp_signals = {}
self.root_path = self.config_obj['active']['controllers']
self.tableViews = []
self.tabs = []
self.iniControllersBox()
self.setActSignals()
self.load_bulk()
self.set_signals_table()
self.update_CurController_lbl()
self.update_ControllersTab() # here is where the action gets hot
# your code ends here
self.show() # here crashes if I passed the new tab to the instance of
# QTabView. otherwise it shows empty tabs
#########################################################
def load_bulk(self):
# get the list of running components into a dictionary
for i in self.list_controllers:
i_path = os.path.join(self.root_path, i)
print(i)
self.components[i] = get_controller_dict(i_path,
self.config_obj,
'Application.xml',
'Subcomponents/Subcomponent',
'Name',
'src')
for j in self.components[i]:
print(j)
signals_key = (i , j)
tgt = os.path.join(self.root_path, self.components[i][j])
self.cdp_signals[signals_key] = get_controller_dict(i_path,
self.config_obj,
self.components[i][j],
'Signals/Signal',
'Name',
'Type',
'Routing')
def set_signals_table(self):
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(0, qtw.QTableWidgetItem('GUI caption'))
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(1, qtw.QTableWidgetItem('Monitored Signal'))
def setActSignals(self):
self.ui.controllersBox.currentIndexChanged.connect(self.update_guis_list)
self.ui.controllersBox.currentIndexChanged.connect(self.update_CurController_lbl)
self.ui.controllersBox.currentIndexChanged.connect(self.update_ControllersTab)
def update_ControllersTab(self):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = []
self.tableViews = []
curr_controller = self.ui.controllersBox.currentText()
for i in self.components[curr_controller]:
if len(self.cdp_signals[curr_controller, i]) == 0:
continue
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
# in the next line I try to create a new QTableView passing
# the last tab as parameter, in the attempt to embed the QTableView
# into the QWidget Tab
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
rowCount = 0
for row in self.cdp_signals[curr_controller, i]:
for col in range(len(self.cdp_signals[curr_controller, i][row])):
index = model.index(rowCount, col, qtc.QModelIndex())
model.setData(index, self.cdp_signals[curr_controller, i][row][col])
try:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i) # no problems, some controllers ask up to
except Exception as ex:
print(ex)
def update_CurController_lbl(self):
self.ui.active_controller_lbl.setText(self.ui.controllersBox.currentText())
def iniControllersBox(self):
self.list_controllers = [os.path.basename(f.path) for f in os.scandir(self.root_path) if f.is_dir() and str(
f.path).upper().endswith('NC')]
self.ui.controllersBox.addItems(self.list_controllers)
for i in range(self.ui.controllersBox.count()):
self.ui.controllersBox.setCurrentIndex(i)
newKey = self.ui.controllersBox.currentText()
cur_cntrlr = os.path.join(self.config_obj['active']['controllers'], self.ui.controllersBox.currentText())
self.guis_dict[newKey] = findGuis(cur_cntrlr, self.config_obj)
self.ui.controllersBox.setCurrentIndex(0)
self.update_guis_list()
def update_guis_list(self, index=0):
self.ui.GuisListBox.clear()
self.ui.GuisListBox.addItems(self.guis_dict[self.ui.controllersBox.currentText()])
if __name__ == '__main__':
config = ConfigParser()
config.read('./config.ini')
app = qtw.QApplication([sys.argv])
w = MainWindow(config, './config.ini',
'./test_setup_1.proj')
sys.exit(app.exec_())
and here the external to add the checkboxes column:
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
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.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
The 1st picture shows the layout in QTDesigner, the 2nd the result (emtpy tabs) when avoiding the crashing.
the QTabWidget has no problems in zeroing, or scale up, back to as many tab as I need, it's just that I have no clue on how to show the QTabview. My approach was to try to embed the QTabView in the tabpage passing it as parameter to the line creating the new QTabView.
Since I am using rather convoluted dictionaries, calling an XML parser to fill them up, not to mention the config files, I know even this version of my script is hardly reproduceable/runnable.
If someone had the patience of focusing on the update_ControllersTab method though, and tell me what I am doing wrong handling the QWidgets, it'd be great.
Again the basic idea is to clear the QTabWidget any time the user selects a different controller (combo box on the left):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = [] # list to hold QTabView QWidgets (pages) throughout the scope
self.tableViews = [] # list to hold QTabView(s) thorughout the scope
count how many tabs (pages) and hence embedded TabViews I need with the new controllers selected.
and then for each tab needed:
create a new tab (page)
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
create a new QTabView using a model:
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
populate the TableView with data, and then finally add the tab widget (with the suppposedly embedded QTableView to the QTabWidget (the i argument is a string from my dbases Names:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i)
This method is called also by the __init__ to initialize and apparently all goes error free, until the last 'init' statement:
`self.show()`
at which point the app crashes with:
Process finished with exit code 1073741845
on the other hand, if here instead of trying to embed the QTableView:
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
I omit the parameter, that is:
self.tableViews.append(qtw.QTableView())
the app doesn't crash anymore, but of course no QtableViews are shown, only empty tabpages:
As stupid as this may sound the problem is in... the delegate class that creates the checkboxes in the first column (see https://stackoverflow.com/a/50314085/7710452)
I commented out those two lines:
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
and... bingo!
the CheckBoxDelegate works fine in the example shown in the post (a single QTableView form). I also tinkered around adding columns and rows, and moving the checkbox column back and forth with no problems. In that standalone. But as soon as I add the class and set the delegate, i am back at square zero, app crashing with:
Process finished with exit code 1073741845
so I am left with this problem now. Thnx to whomever read this.
Problem solved, see comment to post above.

Qt Drag & Drop doesn't work fine with custom items

I'm building a GUI using PySide2 (Qt5) with a custom treeview widget (MyTreeView, inherited from QTreeView). The model is a QStandardItemModel object whereas the items are custom: MyStandardItem, inherited from QStandardItem.
The problem is: if I check the type of the moved item after a drag and drop action, it has become a QStandardItem but it should have been a MyStandardItem.
I believe that the problem is the MimeType, and after a lot of research I found out that the solution could be creating a custom model and overriding MIME related functions.
I tried to figure out how but I couldn't.
So, here are the questions:
Do I have to create a custom model or is there a simple solution?
If I have to create a custom model, which functions should I override and how should I override those functions?
For what it's worth, here is MyStandardItem implementation:
class MyStandardItem(QStandardItem):
def __init__(self, text, font, icon_path='', value='', num=0, check_state=None):
super().__init__()
self.setDragEnabled(True)
self.setDropEnabled(True)
self.setText(text)
self.setData({'value': (value, num)})
self.setToolTip(str(self.data()['value']))
self.setFont(font)
self.setIcon(QIcon(icon_path))
self.toggled = check_state
if check_state is not None:
self.setCheckable(True)
self.setCheckState(check_state)
def setCheckState(self, checkState):
super().setCheckState(checkState)
if checkState == Qt.Unchecked:
self.toggled = Qt.Unchecked
else:
self.toggled = Qt.Checked
I found a way to solve this problem without having to create a custom model.
In the MyTreeView.dropEvent function: I dont't call super().dropEvent() to complete the drag&drop action but I implement it by myself by copying item's data in a variable and creating a new MyStandardItem from those data. Then I call insertRow() to insert the new item in the given position and deleteRow() to delete the old item.
Clearly, the moved item has to be stored in a class attribute at the beginning of the action (DragEnterEvent()).
Everything works perfectly.
PS: I've already tried this way before but I always ended up by having an empty row. The difference here is that I create a new item instead of re-inserting the old one.
Here's some parts of my code to clarify what I mean. Please, note that these functions are MyTreeView's methods.
def dragEnterEvent(self, event: QDragEnterEvent):
if event.source() == self:
self.dragged_item = self.model.itemFromIndex(self.selectionModel().selectedIndexes()[0])
super().dragEnterEvent(event)
else:
...
def dropEvent(self, event: QDropEvent):
index = self.indexAt(event.pos())
if not index.isValid():
return
over_item = self.model.itemFromIndex(index)
over_value = over_item.data()['value'][0]
if event.source() == self:
item_was_moved = self._move_item(over_value, over_parent_value)
if not item_was_moved:
return
else:
...
def _move_item(self, over_value, over_parent_value):
over_value = self._check_indicator_position(over_value)
if over_value is None:
return False
dragged_parent_value = self.dragged_item.parent().data()['value'][0]
dragged_value = self.dragged_item.data()['value'][0]
row = self.dragged_item.row()
items = guifunc.copy_row_items(self.dragged_item, row)
over_value_num = int(over_value.strip('test'))
self.dragged_item.parent().insertRow(over_value_num, items)
if over_value_num < row:
row += 1
self.dragged_item.parent().removeRow(row)
return True

Creating a toggling "Check All" checkbox for a ListView

I have a ListView full of checkable items. I want to place a tristate "check all" checkbox above the ListView, and I want this checkbox to be bi-directional.
That is, if the user toggles the check all checkbox, I want all of the ListView's items to mirror the check all's selection. But if the user manually checks or unchecks items in the ListView, I want the select all checkbox to reflect that state (i.e. checked if all of the ListView items are checked, unchecked if they are all unchecked, or partially checked if some of the ListView items are checked).
This answer shows how to wire the first part (checking/unchecking the select all box propagates its state to the list view's items). However, I'm stumped as to how wire the other direction.
This is how I'm getting the Check All checkbox to propagate to the ListView:
self.layout = QtGui.QVBoxLayout()
self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
self.select_all_cb.setChecked(True)
self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
self.select_all_cb.stateChanged.connect(self.selectAllCheckChanged)
self.layout.addWidget(select_all_cb)
self.listview = QtGui.QListView(self.ui.tab)
self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.listview.setSelectionRectVisible(False)
model = QStandardItemModel()
for checkItem in self.checkItems:
item = QStandardItem(checkItem)
item.setCheckable(True)
item.setSelectable(False)
item.setCheckState(QtCore.Qt.Checked)
model.appendRow(item)
self.listview.setModel(model)
self.layout.addWidget(listview)
def selectAllCheckChanged(self):
model = self.listview.model()
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
Any suggestions on how to go the other way?
You can connect to the itemChanged signal on the QStandardItemModel and test the state of all the checkboxes.
from itertools import product
self.model.itemChanged.connect(self.test_check)
def test_check(self, item):
items = [self.model.item(r,c) for r, c in product(range(self.model.rowCount()), range(self.model.columnCount())]
if all(item.checkState() == Qt.Checked for item in items)
state = Qt.Checked
elif any(item.checkState() == Qt.Checked for item in items):
state = Qt.PartiallyChecked
else:
state = Qt.Unchecked
if self.select_all_cb.checkState() != state:
self.select_all_cb.setCheckState(state)
If you have a very large number of checkboxes, you may be able to optimize this by caching the check state of each item and updating the cache whenever the item state changes, and then checking the cache instead of pulling it from each item every time.
If you know you're going to be making changes to many items at once, you should probably blockSignals on the model and then run this function manually after making all the changes.
In your selectAllCheckChanged handler, you should also block signals on the model so it doesn't trigger this handler
def selectAllCheckChanged(self):
model = self.listview.model()
model.blockSignals(True)
try:
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
finally:
model.blockSignals(False)
In case it will help others, here's how I incorporated Brendan's answer in my code. The differences are the tristate functionality is enabled only when needed (so the user can't enable the partial-checked state), and I wired it with the clicked signal instead of stateChange to avoid selectAllCheckChanged from being triggered by listviewCheckChanged. The model.blockSignals works too, of course, but using clicked seemed more pythonic to me.
self.layout = QtGui.QVBoxLayout()
self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
self.select_all_cb.setTristate(False) # Only enable tristate when necessary so the user doesn't click it through to partially checked
self.select_all_cb.setChecked(True)
self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
self.select_all_cb.clicked.connect(self.selectAllCheckChanged) # clicked instead of stateChanged so this doesn't get triggered by ListView's changes
self.layout.addWidget(select_all_cb)
self.listview = QtGui.QListView(self.ui.tab)
self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.listview.setSelectionRectVisible(False)
model = QStandardItemModel()
for checkItem in self.checkItems:
item = QStandardItem(checkItem)
item.setCheckable(True)
item.setSelectable(False)
item.setCheckState(QtCore.Qt.Checked)
model.appendRow(item)
self.listview.setModel(model)
self.listview.clicked.connect(self.listviewCheckChanged)
self.layout.addWidget(listview)
def selectAllCheckChanged(self):
''' updates the listview based on select all checkbox '''
model = self.listview.model()
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
def listviewCheckChanged(self):
''' updates the select all checkbox based on the listview '''
model = self.listview.model()
items = [model.item(index) for index in range(model.rowCount())]
if all(item.checkState() == QtCore.Qt.Checked for item in items):
self.select_all_cb.setTristate(False)
self.select_all_cb.setCheckState(QtCore.Qt.Checked)
elif any(item.checkState() == QtCore.Qt.Checked for item in items):
self.select_all_cb.setTristate(True)
self.select_all_cb.setCheckState(QtCore.Qt.PartiallyChecked)
else:
self.select_all_cb.setTristate(False)
self.select_all_cb.setCheckState(QtCore.Qt.Unchecked)

Cant get a custom ItemDelegate to work

first off, im new to python and pyqt so please bear with me.
Im using a QTableView with a QSqlTableModel everything works as intended.
The last column of the view contains only 0 and 1 as value which i want to display as checkbox and this column should be editable.
Ive read that you should subclass QItemDelegate which i did. Unluckily my table wont show the last column as a checkbox.
I tried to set the delegate only for the last column (the way i would prefer) using setItemDelegateForColumn(), it didnt work. So i modified it and set it for the entire QTableView using setItemDelegate() reacting only to requests to the last column. It still wont work. Wont work means there are no error messages it just wont do what i say ;) It seems that none of the methods i reimplemented gets ever called except init(). So i guess im missing something fundamental.
Ive extracted the relevant lines of code, KSCheckBoxDelegate is my subclass. This is the version where the delegate is set up for the entire QTableView.
-- code from applications main class --
self.taglist = QTableView()
self.tagmodel = QSqlTableModel()
self.tagmodel.setTable("data")
self.tagmodel.select()
self.taglist.setModel(self.tagmodel)
print self.taglist.itemDelegate()
myDel = KSCheckBoxDelegate(self)
myDel.colnumber = 3
self.taglist.setItemDelegate(myDel)
-- KSCheckBoxDelegate.py --
from PyQt4.QtGui import *
class KSCheckBoxDelegate(QStyledItemDelegate):
colnumber = None
def __init__ (self, parent = None):
print "KSCheckBoxDelegate::init"
QStyledItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
print "KSCheckBoxDelegate::createEditor"
if index.column()==self.colnumber:
return QCheckBox(self)
else:
return QStyledItemDelegate.createEditor(self, parent, option, index)
def setEditorData (self, editor, index):
print "KSCheckBoxDelegate::setEditorData"
if index.column() == self.colnumber:
cont = index.model().data(index, Qt.DisplayRole).toString()
if cont == "1":
editor.setCheckState(Qt.Checked)
else:
editor.setCheckState(Qt.UnChecked)
else:
QStyledItemDelegate.setEditorData(self,editor, index)
def setModelData (self, editor, model, index):
print "KSCheckBoxDelegate::setModelData"
if index.column() == self.colnumber:
if editor.checkBox.checkState() == Qt.Checked:
model.setData(index, 1)
else:
model.setData(index, 0)
else:
QStyledItemDelegate.setModelData(self, editor, model, index)
Any hints for me on that issue?
Furthermore i have difficulties with the currentChanged() signal of the QTableViews selectionModel. Im printing the top right coordinates of the selection. I keep getting wrong indexes (not invalid) when clicking with the left mouse button. Using the cursor keys gets me the right indexes. Using selectionChanged() has the same behaviour. Im actually getting the coordinates of the second last selected cell of the QTableView. For instance im clicking on the coordinates <1,1> <2,1> the second click would show me the coordinates <1,1>.
selInd = self.taglist.selectionModel().selectedIndexes()
if(len(selInd) > 0):
self.xPosData=selInd[0].column()
self.yPosData=selInd[0].row()
Fixed that by myself, with using QTableView.currentIndex() instead of selectionModel.selectedIndexes() :)
And last off using OnManualSubmit as editStrategy doesnt throw an error (return false) when calling submitAll() but doesnt save the data either. It works with choosing OnFieldChange as editStrategy. Which i can live with but is not was i have intended to do.
Thanks for your time.
Horst
I think it would be simpler to create your own model basing QSqlTableModel, and for your 0/1 column return QVariant() for QDisplayRole and return Qt::Checked/Qt::Unchecked for Qt::CheckStateRole depending on value. For all other cases return QSqlTableModel::data
class MySqlTableModel: public QSqlTableModel
{
public:
// contructors
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole)
{
if(index.column() == 3 /* your 0/1 column */)
{
if(role == Qt::DisplayRole)
{
return QVariant();
}
else if(role == Qt::CheckStateRole)
{
QString value = QSqlTableModel::data(index, Qt::DisplayRole).toString();
return value == "1" ? Qt::Checked : Qt::Unchecked;
}
}
return QSqlTableModel::data(index, role);
}
};
I know it's C++ code, but logic is still same, so just readjust it for Python
at least i managed to have my delegate show checkboxes with the correct state in display and edit mode. Because i couldnt find a complete description on how to do it ill share it. Maybe other ppl trying to do something similar without having worked with qt ever before.
Only one thing is missing, thats the items being editable, i guess its related to the flags question i asked in my opening post.
-- KSCheckBoxDelegate.py --
class KSCheckBoxDelegate(QStyledItemDelegate):
def __init__ (self, parent = None):
print "KSCheckBoxDelegate::init"
QStyledItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
print "KSCheckBoxDelegate::createEditor"
return QCheckBox(parent)
def setEditorData (self, editor, index):
print "KSCheckBoxDelegate::setEditorData"
cont = index.model().data(index, Qt.DisplayRole).toString()
if cont == "1":
editor.setCheckState(Qt.Checked)
else:
editor.setCheckState(Qt.Unchecked)
def setModelData (self, editor, model, index):
print "KSCheckBoxDelegate::setModelData"
if editor.checkState() == Qt.Checked:
model.setData(index, 1)
else:
model.setData(index, 0)
def paint (self, painter, option, index):
myopt = QStyleOptionViewItemV4(option)
newopt = QStyleOptionButton()
newopt.rect = myopt.rect
newopt.palette = myopt.palette
if index.data().toBool():
newopt.state |= QStyle.State_On
QApplication.style().drawControl(QStyle.CE_CheckBox, newopt, painter)
def sizeHint (self, option, index):
return 18

Categories

Resources