Checkbox and combobox in table cell
-
Hello,
I am developing an app and i have to display items in a table. In order to have full control, i have implemented my custom table, as shown in the code below:
class table_horizontal_header(QHeaderView): def __init__(self, parent): super().__init__(Qt.Horizontal, parent) # Define checkbox icon dimension self.checkbox_width = 16 self.checkbox_height = 16 # Define filter icon dimensions self.filter_width = 16 self.filter_height = 16 # Define padding self.padding_left = 4 self.padding_right = 4 self.padding_between = 4 # Set up button properties self.setMouseTracking(True) # Initialize bookkeeping variables self.reset_bookkeeping_variables() # Initialize rects self.initialize_checkbox_rects() def initialize_checkbox_rects(self): self.checkbox_rects = [QRect(-1, -1, 0, 0)] * self.parent().model().columnCount() def update_section(self, logical_index): if logical_index is not None: self.updateSection(logical_index) def reset_bookkeeping_variables(self): self.checkbox_hovered_section = None self.checkbox_pressed_section = None self.pressed_section = None def mouseMoveEvent(self, event: QMouseEvent): super().mouseMoveEvent(event) if self.checkbox_pressed_section is None and self.pressed_section is None: if self.rect().contains(event.pos()): logical_index = self.logicalIndexAt(event.pos()) if logical_index != -1: try: if self.checkbox_rects[logical_index].contains(event.pos()): if logical_index != self.checkbox_hovered_section: self.checkbox_hovered_section = logical_index self.update_section(logical_index) else: if self.checkbox_hovered_section is not None: self.checkbox_hovered_section = None self.update_section(logical_index) except Exception as e: print(e) def leaveEvent(self, event): super().leaveEvent(event) if self.checkbox_hovered_section is not None: temp = self.checkbox_hovered_section self.reset_bookkeeping_variables() self.update_section(temp) def mousePressEvent(self, event): super().mousePressEvent(event) if event.button() == Qt.LeftButton: if self.rect().contains(event.pos()): logical_index = self.logicalIndexAt(event.pos()) # Check if the checkbox is pressed if self.checkbox_rects[logical_index].contains(event.pos()): self.checkbox_pressed_section = logical_index self.update_section(logical_index) else: self.pressed_section = logical_index # Select the entire column selectionModel = self.parent().selectionModel() selectionModel.select(QItemSelection(self.parent().model().index(0, logical_index), self.parent().model().index(self.parent().model().rowCount() - 1, logical_index)), QItemSelectionModel.ClearAndSelect) def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) if event.button() == Qt.LeftButton: if self.rect().contains(event.pos()) and self.checkbox_pressed_section is not None: logical_index = self.logicalIndexAt(event.pos()) if logical_index == self.checkbox_pressed_section and self.checkbox_rects[logical_index].contains(event.pos()): self.model().on_checkbox_header_click(self.checkbox_pressed_section) temp = self.checkbox_pressed_section self.reset_bookkeeping_variables() self.update_section(temp) def paintSection(self, painter, rect, logicalIndex): painter.save() painter.setRenderHint(QPainter.Antialiasing) # Get section text text = self.model().headerData(logicalIndex, Qt.Horizontal) # Get header dimensions self.x = rect.x() self.y = rect.y() self.width = rect.width() self.height = rect.height() # Check if section contains checkbox has_checkbox = self.model().checkbox_header[logicalIndex] # Calculate checkbox icon position if has_checkbox: checkbox_x = self.x + self.width - self.padding_right - self.checkbox_width checkbox_y = self.y + (self.height - self.checkbox_height) / 2 checkbox_rect = QRect(checkbox_x, checkbox_y, self.checkbox_width, self.checkbox_height) self.checkbox_rects[logicalIndex] = checkbox_rect # Calculate text position text_x = self.x + self.padding_left text_y = self.y text_height = self.height if has_checkbox: text_width = self.width - self.padding_left - self.padding_right - self.checkbox_width - self.padding_between else: text_width = self.width - self.padding_left - self.padding_right text_rect = QRect(text_x, text_y, text_width, text_height) # Calculate filter icon position # Set font to bold if is a cell corresponding to the header is selected, otherwise normal font. if self.parent(): selected_columns = {index.column() for index in self.parent().selectedIndexes()} # Determine if the column is selected is_selected = logicalIndex in selected_columns # Customize the font and appearance of the header font = QFont() font.setBold(is_selected) # Set the font to bold if the column is selected painter.setFont(font) # Get the default pen default_pen = QPen(painter.pen()) # Set the background gradient = QLinearGradient(rect.x(), rect.y() + rect.height(), rect.x() + rect.width(), rect.y()) gradient.setColorAt(0, 'white') gradient.setColorAt(1, '#F3F3F3') brush = QBrush(gradient) painter.setBrush(brush) painter.setPen(Qt.NoPen) painter.drawRect(rect) # # Create a pen for the border and draw the border # border_pen = QPen(QColor(0, 0, 0, 127)) # border_pen.setWidth(1) # border_pen.setStyle(Qt.SolidLine) # painter.setPen(border_pen) # painter.drawLine(self.x, self.y, self.x, self.y + self.height) # Left side # painter.drawLine(self.x, self.y + self.height, self.x + self.width, self.y + self.height) # Bottom side # Reset pen to its default painter.setPen(default_pen) # Draw text painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, text) # Draw checkbox if has_checkbox: checkbox_icon = QPixmap(checkbox_checked_path) if self.model().checkbox_header_status[logicalIndex] else QPixmap(checkbox_unchecked_path) painter.drawPixmap(checkbox_rect, checkbox_icon) if self.checkbox_pressed_section == logicalIndex: brush = QBrush(QColor(0, 0, 0, 100)) painter.setBrush(brush) painter.drawRect(checkbox_rect) elif self.checkbox_hovered_section == logicalIndex: brush = QBrush(QColor(255, 255, 255, 100)) painter.setBrush(brush) painter.drawRect(checkbox_rect) painter.restore() class custom_table_model(QAbstractTableModel): def __init__(self, data = None, default_nr_col = 6, default_nr_row = 10, checkbox_columns = None, no_edit_columns = None): super().__init__() self.default_nr_col = default_nr_col self.default_nr_row = default_nr_row self.initialize_table(data, checkbox_columns) def flags(self, index): flags = super().flags(index) col = index.column() if self.edit_data[col]: flags |= Qt.ItemIsEditable # Allow editing if edit_data is True for this column 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: return str(self._data.iloc[index.row(), index.column()]) # elif role == Qt.CheckStateRole: # value = self._data.iloc[index.row(), index.column()] # return Qt.Checked if value else Qt.Unchecked return None def setData(self, index, value, role = Qt.EditRole): if role == Qt.EditRole: self._data.iloc[index.row(), index.column()] = value return True elif role == Qt.CheckStateRole: self._data.iloc[index.row(), index.column()] = value == Qt.Checked 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 def initialize_table(self, data = None, checkbox_columns = None, no_edit_columns = None): self.set_data(data) self.initialize_checkboxes(checkbox_columns) self.initialize_checkbox_header_status() self.initialize_edit_data(no_edit_columns) def set_data(self, data = None): if data is None: empty_columns = [f'{i+1}' for i in range(self.default_nr_col)] data = pd.DataFrame(columns = empty_columns, data = [[''] * self.default_nr_col for _ in range(self.default_nr_row)]) self._data = data def initialize_checkboxes(self, checkbox_columns = None): if checkbox_columns is None: checkbox_columns = [{'name': 'include', 'check_all': True}, {'name': 'plot', 'check_all': False}, {'name': 'calibrate', 'check_all': True}] self.checkbox_header = [False] * self.columnCount() self.checkbox_data = [False] * self.rowCount() checkbox_columns_names = [item['name'] for item in checkbox_columns] checkbox_columns_check_all = [item['check_all'] for item in checkbox_columns] for index, item in enumerate(self._data.columns): if item in checkbox_columns_names: self.checkbox_data[index] = True # self._data[item] = True if (self._data[item] == 'True' or self._data[item] == 1) else False self.checkbox_header[index] = True if checkbox_columns_check_all[checkbox_columns_names.index(item)] else False def initialize_edit_data(self, no_edit_columns = None): if no_edit_columns is None: no_edit_columns = ['x_values', 'y_values'] # By default, all data is editable self.edit_data = [True] * self.columnCount() # Set edit flag to False in case it is specified by the user or it is a checkbox column for index, item in enumerate(self._data.columns): if item in no_edit_columns or self.checkbox_data[index]: self.edit_data[index] = False def initialize_checkbox_header_status(self): self.checkbox_header_status = [False] * self.columnCount() def on_checkbox_header_click(self, index): self.checkbox_header_status[index] = not self.checkbox_header_status[index] class custom_table(QTableView): def __init__(self, data = None, style: style = None): super().__init__() # Set model self.setModel(custom_table_model(data)) # Set header options self.setHorizontalHeader(table_horizontal_header(parent = self)) self.horizontalHeader().setStretchLastSection(True) # Make columns stretch to the full width self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) # Set item delegate # delegate = CustomCellDelegate(self) # self.setItemDelegate(delegate) # self.setAlternatingRowColors(True) # Set the CheckboxDelegate for boolean columns checkbox_columns = ['include', 'plot', 'calibrate'] # Replace with your checkbox column names for col, column_name in enumerate(self.model()._data.columns): if column_name in checkbox_columns: self.setItemDelegateForColumn(col, CheckboxDelegate()) def update_data(self, data = None, checkbox_columns = None): self.model().layoutAboutToBeChanged.emit() self.model().initialize_table(data, checkbox_columns) self.horizontalHeader().initialize_checkbox_rects() self.model().layoutChanged.emit()
I am facing a problem on how to show checkboxes for the coulmns that have true/false values. I want to be able to show only a checkbox in the middle of the cell for certain columns, i.e. the ones that have true/false states.
Can anyone help me on that? I am using pyside6.
Thanks a lot.
-
-
@Pl45m4 said in Checkbox and combobox in table cell:
That is what delegates are for
Assign the delegates where you have your boolean values
The standard styled delegate for widget views handles Qt::CheckStateRole. The link doesn't go directly to the role.
@Gazi At over 300 lines, this code snippet is waa[...]aay too large for a reasonable question. Please limit examples to the minimum required to explain the problem.