Custom checkbox in a table column
-
wrote on 24 Jan 2024, 18:25 last edited by
Hello,
I am trying to show a table in pyqt, where for the first column i have a boolean state. I would like the cells of the first column to show a checkbox only. I want to use custom images for the true/false states of the checkbox.
I have been trying for some time, but unfortunately i did not have success. I have been playing with item delegates, but no luck.
Any help is greatly appreciated.
Bests,
Gazi -
Hi,
How did you implement your delegate ?
-
wrote on 24 Jan 2024, 20:39 last edited by
@SGaist Here is the minimum example i could make. I know it looks long, but it is just the minimum number of required functions implemented.
import sys from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QTableView, QCheckBox, QStyledItemDelegate from PySide6.QtCore import Qt, QModelIndex, QAbstractTableModel from PySide6.QtGui import QColor import numpy as np import pandas as pd class item_delegate(QStyledItemDelegate): def __init__(self, parent): super().__init__(parent) # Define checkbox icon dimension self.checkbox_width = 16 self.checkbox_height = 16 self.alternate_row_background_color = QColor(0, 0, 0, 25) def createEditor(self, parent, option, index): if index.column() == 0: editor = QCheckBox() editor.setChecked(index.data(Qt.EditRole)) return editor return super().createEditor(parent, option, index) def setEditorData(self, editor, index): if index.column() == 0: editor.setChecked(index.data(Qt.EditRole)) else: super().setEditorData(editor, index) def setModelData(self, editor, model, index): if index.column() == 0: model.setData(index, editor.isChecked(), Qt.EditRole) else: super().setModelData(editor, model, index) def paint(self, painter, option, index): rect = option.rect super().paint(painter, option, index) # Set background color if index.row() % 2 == 1: painter.fillRect(rect, self.alternate_row_background_color) class custom_table_model(QAbstractTableModel): def __init__(self, data = None): super().__init__() if data is None: data = pd.DataFrame() self._data = data def flags(self, index): flags = super().flags(index) flags |= Qt.ItemIsEditable return flags def rowCount(self, parent = QModelIndex()): return len(self._data) def columnCount(self, parent = QModelIndex()): return len(self._data.columns) def data(self, index, role = Qt.DisplayRole): if role == Qt.DisplayRole or role == Qt.EditRole: result = self._data.iloc[index.row(), index.column()] if not isinstance(result, (bool, np.bool_)): result = str(result) else: result = bool(result) return result return None def setData(self, index, value, role = Qt.DisplayRole): if role == Qt.DisplayRole or role == Qt.EditRole: self._data.iloc[index.row(), index.column()] = value return True return super().setData(index, value, role) def headerData(self, section, orientation, role = Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return str(self._data.columns[section]) if orientation == Qt.Vertical: return str(self._data.index[section]) return None class custom_table(QTableView): def __init__(self, data = None): super().__init__() # Init data self.initialize_data() # Set model self.setModel(custom_table_model(self.data)) # Set item delegate self.setItemDelegate(item_delegate(parent = self)) def initialize_data(self): self.data = pd.DataFrame({ 'include': [True, True, True, True], 'x': [1, 12, 50, 65], 'animal': ['dog', 'cat', 'cow', 'horse'] }) class MyWindow(QMainWindow): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("Minimal PySide6 Example") # Create a central widget central_widget = QWidget() self.setCentralWidget(central_widget) # Create a vertical layout for the central widget layout = QVBoxLayout(central_widget) # Create the drawer widget and add it to the layout table = custom_table() layout.addWidget(table) def main(): if not QApplication.instance(): app = QApplication(sys.argv) else: app = QApplication.instance() window = MyWindow() window.show() sys.exit(app.exec()) main()
I am looking forward to your answer.
Thanks
-
@SGaist Here is the minimum example i could make. I know it looks long, but it is just the minimum number of required functions implemented.
import sys from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QTableView, QCheckBox, QStyledItemDelegate from PySide6.QtCore import Qt, QModelIndex, QAbstractTableModel from PySide6.QtGui import QColor import numpy as np import pandas as pd class item_delegate(QStyledItemDelegate): def __init__(self, parent): super().__init__(parent) # Define checkbox icon dimension self.checkbox_width = 16 self.checkbox_height = 16 self.alternate_row_background_color = QColor(0, 0, 0, 25) def createEditor(self, parent, option, index): if index.column() == 0: editor = QCheckBox() editor.setChecked(index.data(Qt.EditRole)) return editor return super().createEditor(parent, option, index) def setEditorData(self, editor, index): if index.column() == 0: editor.setChecked(index.data(Qt.EditRole)) else: super().setEditorData(editor, index) def setModelData(self, editor, model, index): if index.column() == 0: model.setData(index, editor.isChecked(), Qt.EditRole) else: super().setModelData(editor, model, index) def paint(self, painter, option, index): rect = option.rect super().paint(painter, option, index) # Set background color if index.row() % 2 == 1: painter.fillRect(rect, self.alternate_row_background_color) class custom_table_model(QAbstractTableModel): def __init__(self, data = None): super().__init__() if data is None: data = pd.DataFrame() self._data = data def flags(self, index): flags = super().flags(index) flags |= Qt.ItemIsEditable return flags def rowCount(self, parent = QModelIndex()): return len(self._data) def columnCount(self, parent = QModelIndex()): return len(self._data.columns) def data(self, index, role = Qt.DisplayRole): if role == Qt.DisplayRole or role == Qt.EditRole: result = self._data.iloc[index.row(), index.column()] if not isinstance(result, (bool, np.bool_)): result = str(result) else: result = bool(result) return result return None def setData(self, index, value, role = Qt.DisplayRole): if role == Qt.DisplayRole or role == Qt.EditRole: self._data.iloc[index.row(), index.column()] = value return True return super().setData(index, value, role) def headerData(self, section, orientation, role = Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return str(self._data.columns[section]) if orientation == Qt.Vertical: return str(self._data.index[section]) return None class custom_table(QTableView): def __init__(self, data = None): super().__init__() # Init data self.initialize_data() # Set model self.setModel(custom_table_model(self.data)) # Set item delegate self.setItemDelegate(item_delegate(parent = self)) def initialize_data(self): self.data = pd.DataFrame({ 'include': [True, True, True, True], 'x': [1, 12, 50, 65], 'animal': ['dog', 'cat', 'cow', 'horse'] }) class MyWindow(QMainWindow): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("Minimal PySide6 Example") # Create a central widget central_widget = QWidget() self.setCentralWidget(central_widget) # Create a vertical layout for the central widget layout = QVBoxLayout(central_widget) # Create the drawer widget and add it to the layout table = custom_table() layout.addWidget(table) def main(): if not QApplication.instance(): app = QApplication(sys.argv) else: app = QApplication.instance() window = MyWindow() window.show() sys.exit(app.exec()) main()
I am looking forward to your answer.
Thanks
-
You have to handle the
Qt.CheckStateRole
in your model.Then in your delegate, paint whatever you want in place of the checkbox. I would recommend that you remove the delegate while adding the support for
Qt.CheckStateRole
and once you have that working you can enable the delegate again.On a side note, you forget to set the parent of the editor, it will thus be rendered outside the view as an independent widget, I don't think you want that.
-
You have to handle the
Qt.CheckStateRole
in your model.Then in your delegate, paint whatever you want in place of the checkbox. I would recommend that you remove the delegate while adding the support for
Qt.CheckStateRole
and once you have that working you can enable the delegate again.On a side note, you forget to set the parent of the editor, it will thus be rendered outside the view as an independent widget, I don't think you want that.
wrote on 28 Jan 2024, 20:38 last edited byWell, first, the parent helped to have the checkbox not as a separate window.
Second, handling the Qt.CheckStateRole is not the solution, i believe. Let me explain why:
In my use case, i need to accomplish smth more complex than just a regular checkbox. I want to have full control over the checkbox.- For starters, as it is implemented now, the checkbox is shown only when the cell is double clicked, i.e. in the edit mode. Normally it shows the string, true or false.
- Second, i want the checkbox to be shown in the middle of the cell and I have 2 custom images for the checkbox checked and unchecked states. I tried but could not manage to use the paint event of my custom checkbox inside the paint event of the delegate.
- Third, in my custom checkbox, I handle the mouse events explicitly, so that when i hover the checkbox, i can give the user an indication that he can click the checkbox, i.e. i change the paint of the checkbox depending on whether the user is hovering the checkbox or not.
Considering these points, i do not believe that the CheckStateRole is the solution, but somehow i need to be able to use a custom component in the cells of a table. I cannot use the QTableWidgetItem, because this will make my code slow, since in my use case the table can have 10000 lines.
Any help is appreciated on how to achieve these goals.
Thank you.
Bests,
Gazi -
Well, first, the parent helped to have the checkbox not as a separate window.
Second, handling the Qt.CheckStateRole is not the solution, i believe. Let me explain why:
In my use case, i need to accomplish smth more complex than just a regular checkbox. I want to have full control over the checkbox.- For starters, as it is implemented now, the checkbox is shown only when the cell is double clicked, i.e. in the edit mode. Normally it shows the string, true or false.
- Second, i want the checkbox to be shown in the middle of the cell and I have 2 custom images for the checkbox checked and unchecked states. I tried but could not manage to use the paint event of my custom checkbox inside the paint event of the delegate.
- Third, in my custom checkbox, I handle the mouse events explicitly, so that when i hover the checkbox, i can give the user an indication that he can click the checkbox, i.e. i change the paint of the checkbox depending on whether the user is hovering the checkbox or not.
Considering these points, i do not believe that the CheckStateRole is the solution, but somehow i need to be able to use a custom component in the cells of a table. I cannot use the QTableWidgetItem, because this will make my code slow, since in my use case the table can have 10000 lines.
Any help is appreciated on how to achieve these goals.
Thank you.
Bests,
Gaziwrote on 28 Jan 2024, 20:58 last edited by JonB@Gazi said in Custom checkbox in a table column:
Second, handling the Qt.CheckStateRole is not the solution,
I think you are mistaken. @SGaist will correct me if not or flesh it out.
Qt.CheckStateRole
is just the model's representation of whether the checkbox is unchecked/checked (or tristate) communicated back & forth with the view. All 3 of your issues don't have anything to do with that. They should be handled in the delegate which handles the input/display. I believe all of them can be achieved there. -
@Gazi said in Custom checkbox in a table column:
Second, handling the Qt.CheckStateRole is not the solution,
I think you are mistaken. @SGaist will correct me if not or flesh it out.
Qt.CheckStateRole
is just the model's representation of whether the checkbox is unchecked/checked (or tristate) communicated back & forth with the view. All 3 of your issues don't have anything to do with that. They should be handled in the delegate which handles the input/display. I believe all of them can be achieved there.wrote on 29 Jan 2024, 08:11 last edited by@JonB said in Custom checkbox in a table column:
Qt.CheckStateRole
Exactly, the Qt.CheckStateRole does not solve the issues I have with how the checkbox is displayed.
-
@JonB said in Custom checkbox in a table column:
Qt.CheckStateRole
Exactly, the Qt.CheckStateRole does not solve the issues I have with how the checkbox is displayed.
wrote on 29 Jan 2024, 11:00 last edited by@Gazi
Which is why @SGaist wrote:@SGaist said in Custom checkbox in a table column:
You have to handle the Qt.CheckStateRole in your model.
Then in your delegate, paint whatever you want in place of the checkbox -
@Gazi
Which is why @SGaist wrote:@SGaist said in Custom checkbox in a table column:
You have to handle the Qt.CheckStateRole in your model.
Then in your delegate, paint whatever you want in place of the checkboxwrote on 29 Jan 2024, 12:32 last edited by@JonB Right. I managed to somehow use the paint function of the custom checkbox I created (no clue why it is not trival to just use a new component in the cells you want). However, I have now the issue that, even though i am using the paint function of my custom checkbox component, i can only interact with the checkbox after double clicking the cell, i.e. entering the edit mode.
How can I make the cell in edit mode all the time, or even better, once I hover on a cell, it should become automatically editable?
Thanks.
-
@JonB Right. I managed to somehow use the paint function of the custom checkbox I created (no clue why it is not trival to just use a new component in the cells you want). However, I have now the issue that, even though i am using the paint function of my custom checkbox component, i can only interact with the checkbox after double clicking the cell, i.e. entering the edit mode.
How can I make the cell in edit mode all the time, or even better, once I hover on a cell, it should become automatically editable?
Thanks.
wrote on 29 Jan 2024, 13:05 last edited by JonB@Gazi
Here is my recollection:-
If you are not in edit mode but want a checkbox to be editable (i.e. clickable) I think you would have to do that by recognising the click on the box area and toggling the state from code.
-
You can enter edit mode programmatically any time you want (e.g. on hover) by calling void QAbstractItemView::edit(const QModelIndex &index) on the
QTableView
yourself at whatever point. You can also affect what causes the table view to enter edit mode itself via enum QAbstractItemView::EditTrigger and https://doc.qt.io/qt-6/qabstractitemview.html#editTriggers-prop.
Section https://doc.qt.io/qt-6/qstyleditemdelegate.html#subclassing-qstyleditemdelegate also has a bit to say about checkboxes.
Finally, I think you should have a look at the Star Delegate Example. That has things like always painting with
StarRating::EditMode::Editable
andIt is possible to open editors programmatically by calling QAbstractItemView::edit(), instead of relying on edit triggers. This could be used to support other edit triggers than those offered by the QAbstractItemView::EditTrigger enum. For example, in the Star Delegate example, hovering over an item with the mouse might make sense as a way to pop up an editor.
-
-
@Gazi
Here is my recollection:-
If you are not in edit mode but want a checkbox to be editable (i.e. clickable) I think you would have to do that by recognising the click on the box area and toggling the state from code.
-
You can enter edit mode programmatically any time you want (e.g. on hover) by calling void QAbstractItemView::edit(const QModelIndex &index) on the
QTableView
yourself at whatever point. You can also affect what causes the table view to enter edit mode itself via enum QAbstractItemView::EditTrigger and https://doc.qt.io/qt-6/qabstractitemview.html#editTriggers-prop.
Section https://doc.qt.io/qt-6/qstyleditemdelegate.html#subclassing-qstyleditemdelegate also has a bit to say about checkboxes.
Finally, I think you should have a look at the Star Delegate Example. That has things like always painting with
StarRating::EditMode::Editable
andIt is possible to open editors programmatically by calling QAbstractItemView::edit(), instead of relying on edit triggers. This could be used to support other edit triggers than those offered by the QAbstractItemView::EditTrigger enum. For example, in the Star Delegate example, hovering over an item with the mouse might make sense as a way to pop up an editor.
-
-
@JonB How to close then the editor when going out of the cell? One could use the closeEditor() function, but this needs the editor as input, which is not returned by the edit() function.
@Gazi Check editorEvent
-
@JonB How to close then the editor when going out of the cell? One could use the closeEditor() function, but this needs the editor as input, which is not returned by the edit() function.
wrote on 29 Jan 2024, 13:42 last edited by JonB@Gazi
No, butcreateEditor()
is called, which you could always save. There iseditorEvent()
too. I suggested you look at the Star Delegate code, which even has avoid StarDelegate::commitAndCloseEditor()
method. Did you do so?
1/14