pyqt5 QTableView 添加 checkbox (一)

本文介绍如何使用PyQt5在QTableView的表头中添加复选框,通过自定义CheckBoxHeader类实现点击表头复选框来改变数据模型中对应项的状态,同时展示了一个具体的代码示例。

参考:
https://blog.youkuaiyun.com/yy123xiang/article/details/78739975

代码如下:

import sys
from PyQt5.QtWidgets import (QApplication, QHeaderView, QStyle, QStyleOptionButton, QTableView)
from PyQt5.QtCore import (pyqtSignal, Qt, QAbstractTableModel, QModelIndex, QRect, QVariant)


# https://wiki.qt.io/Technical_FAQ#How_can_I_insert_a_checkbox_into_the_header_of_my_view.3F
# https://stackoverflow.com/questions/30932528/adding-checkbox-as-vertical-header-in-qtableview

class CheckBoxHeader(QHeaderView):
    clicked = pyqtSignal(bool)

    _x_offset = 3
    _y_offset = 0
    _width = 20
    _height = 20

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super(CheckBoxHeader, self).__init__(orientation, parent)
        self.isOn = False

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        super(CheckBoxHeader, self).paintSection(painter, rect, logicalIndex)
        painter.restore()

        self._y_offset = int((rect.height()-self._width)/2.)

        if logicalIndex == 0:
            option = QStyleOptionButton()
            option.rect = QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height)
            option.state = QStyle.State_Enabled | QStyle.State_Active
            if self.isOn:
                option.state |= QStyle.State_On
            else:
                option.state |= QStyle.State_Off
            self.style().drawControl(QStyle.CE_CheckBox, option, painter)

    def mousePressEvent(self, event):
        index = self.logicalIndexAt(event.pos())
        if 0 == index:
            x = self.sectionPosition(index)
            if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height:
                if self.isOn:
                    self.isOn = False
                else:
                    self.isOn = True
                self.clicked.emit(self.isOn)
                self.update()
        super(CheckBoxHeader, self).mousePressEvent(event)


class MyModel(QAbstractTableModel):
    def __init__(self, parent=None):
        super(MyModel, self).__init__(parent)
        # Keep track of which object are checked
        self.checkList = ['Checked', 'Unchecked']

    def rowCount(self, QModelIndex):
        return len(self.checkList)

    def columnCount(self, QModelIndex):
        return 2

    def data(self, index, role):
        row = index.row()
        col = index.column()
        if role == Qt.DisplayRole:
            return 'Row %d, Column %d' % (row + 1, col + 1)
        elif role == Qt.CheckStateRole:
            if col == 0:
                return Qt.Checked if self.checkList[row] == 'Checked' else Qt.Unchecked
        elif role == Qt.ToolTipRole:
            if col == 0:
                return self.checkList[row]
        return QVariant()

    def setData(self, index, value, role):
        row = index.row()
        col = index.column()
        if role == Qt.CheckStateRole and col == 0:
            self.checkList[row] = 'Checked' if value == Qt.Checked else 'Unchecked'
        return True

    def flags(self, index):
        if index.column() == 0:
            return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
        return Qt.ItemIsEnabled

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                if section == 0:
                    return 'Title 1'
                elif section == 1:
                    return 'Title 2'

    def headerClick(self, isOn):
        self.beginResetModel()
        if isOn:
            self.checkList = ['Checked', 'Checked']
        else:
            self.checkList = ['Unchecked', 'Unchecked']
        self.endResetModel()


if __name__ == '__main__':
    a = QApplication(sys.argv)
    tableView = QTableView()
    myModel = MyModel()
    tableView.setModel(myModel)
    header = CheckBoxHeader()
    tableView.setHorizontalHeader(header)
    #tableView.horizontalHeader().setStretchLastSection(True)
    tableView.horizontalHeader().setStretchLastSection(True)
    tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
    header.clicked.connect(myModel.headerClick)
    tableView.resize(400, 200)
    tableView.show()
    a.exec_()

效果如下:
在这里插入图片描述

<think>我们之前已经实现了个进度条的自定义委托(Delegate)。现在,用户希望在QTableView中同时添加进度条和其他控件(例如按钮、复选框等)。这需要为不同的列设置不同的自定义委托。 实现思路: 1. 为每种控件类型创建自定义委托(例如进度条委托、按钮委托、复选框委托等)。 2. 根据列索引为不同的列设置不同的委托。 以下我们将实现个示例,在QTableView中同时显示: - 第列:文本(使用默认委托) - 第二列:进度条(自定义委托) - 第三列:复选框(自定义委托) - 第四列:按钮(自定义委托) 注意:按钮委托需要处理点击事件。 步骤: 1. 创建自定义委托类: - 进度条委托(已实现,但需要调整以支持多列) - 复选框委托(使用QStyleOptionButton绘制复选框) - 按钮委托(使用QStyleOptionButton绘制按钮,并处理编辑器事件) 2. 在模型中提供不同列的数据,并确保模型能够返回正确的角色(如对于复选框,需要返回Qt.CheckStateRole)。 3. 在视图中为不同列设置不同的委托。 4. 对于按钮,需要实现点击事件的响应。我们可以通过重写委托的`editorEvent`方法来实现。 我们将构建个包含四列数据的表格模型: - 任务名称(文本) - 进度(0-100的整数) - 是否启用(复选框,True/False) - 操作(按钮,点击后执行操作) 注意:由于按钮不是编辑器,我们不会创建实际的QPushButton,而是通过绘制按钮样式并在点击时触发事件。 代码结构: 1. 自定义模型(继承自QAbstractTableModel) 2. 进度条委托(继承自QStyledItemDelegate) 3. 复选框委托(继承自QStyledItemDelegate) 4. 按钮委托(继承自QStyledItemDelegate) 下面开始实现:</think># PyQt5 QTableView 实现多类型控件综合指南 在 PyQt5 中,通过自定义委托(Delegate)可以在 QTableView 中实现多种控件类型,包括进度条、按钮、复选框等。下面我将详细介绍如何实现这些控件的综合解决方案。 ## 完整实现代码 ```python import sys from PyQt5.QtWidgets import ( QApplication, QMainWindow, QTableView, QStyledItemDelegate, QStyleOptionProgressBar, QStyleOptionButton, QPushButton, QHeaderView, QVBoxLayout, QWidget, QStyle, QCheckBox ) from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex, QSize from PyQt5.QtGui import QColor, QPalette class ProgressBarDelegate(QStyledItemDelegate): """进度条委托""" def paint(self, painter, option, index): # 获取进度值 progress = index.data(Qt.DisplayRole) or 0 # 创建进度条样式选项 progress_option = QStyleOptionProgressBar() progress_option.rect = option.rect.adjusted(4, 4, -4, -4) progress_option.minimum = 0 progress_option.maximum = 100 progress_option.progress = progress progress_option.text = f"{progress}%" progress_option.textVisible = True progress_option.textAlignment = Qt.AlignCenter # 设置颜色 palette = progress_option.palette if progress < 30: palette.setColor(QPalette.Highlight, QColor(220, 100, 100)) elif progress < 70: palette.setColor(QPalette.Highlight, QColor(255, 180, 60)) else: palette.setColor(QPalette.Highlight, QColor(100, 200, 100)) progress_option.palette = palette # 绘制进度条 QApplication.style().drawControl(QStyle.CE_ProgressBar, progress_option, painter) class ButtonDelegate(QStyledItemDelegate): """按钮委托""" def __init__(self, parent=None): super().__init__(parent) self.buttons = {} def paint(self, painter, option, index): # 创建按钮样式选项 button_option = QStyleOptionButton() button_option.rect = option.rect.adjusted(4, 4, -4, -4) button_option.text = index.data(Qt.DisplayRole) or "操作" button_option.state = QStyle.State_Enabled | QStyle.State_Raised # 绘制按钮 QApplication.style().drawControl(QStyle.CE_PushButton, button_option, painter) def editorEvent(self, event, model, option, index): """处理按钮点击事件""" if event.type() == event.MouseButtonRelease: # 触发按钮点击信号 self.parent().buttonClicked.emit(index.row(), index.column()) return True return super().editorEvent(event, model, option, index) class CheckBoxDelegate(QStyledItemDelegate): """复选框委托""" def paint(self, painter, option, index): # 创建复选框样式选项 checkbox_option = QStyleOptionButton() checkbox_option.rect = option.rect.adjusted(4, 4, -4, -4) checkbox_option.state = QStyle.State_Enabled # 设置选中状态 if index.data(Qt.CheckStateRole) == Qt.Checked: checkbox_option.state |= QStyle.State_On else: checkbox_option.state |= QStyle.State_Off # 绘制复选框 QApplication.style().drawControl(QStyle.CE_CheckBox, checkbox_option, painter) def editorEvent(self, event, model, option, index): """处理复选框点击事件""" if event.type() == event.MouseButtonRelease: # 切换选中状态 current_state = index.data(Qt.CheckStateRole) new_state = Qt.Unchecked if current_state == Qt.Checked else Qt.Checked model.setData(index, new_state, Qt.CheckStateRole) return True return super().editorEvent(event, model, option, index) class TableModel(QAbstractTableModel): """自定义表格模型""" def __init__(self, data, headers): super().__init__() self._data = data self._headers = headers def rowCount(self, parent=QModelIndex()): return len(self._data) def columnCount(self, parent=QModelIndex()): return len(self._headers) def data(self, index, role=Qt.DisplayRole): row, col = index.row(), index.column() if role == Qt.DisplayRole: return self._data[row][col] elif role == Qt.CheckStateRole and col == 2: # 复选框列 return Qt.Checked if self._data[row][col] else Qt.Unchecked return None def setData(self, index, value, role=Qt.EditRole): if role == Qt.CheckStateRole and index.column() == 2: self._data[index.row()][index.column()] = (value == Qt.Checked) self.dataChanged.emit(index, index, [Qt.CheckStateRole]) return True return False def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self._headers[section] return None class MainWindow(QMainWindow): buttonClicked = pyqtSignal(int, int) # 行, 列 def __init__(self): super().__init__() self.setWindowTitle("QTableView多控件示例") self.resize(700, 300) # 创建测试数据 data = [ ["数据备份", 25, True, "开始"], ["系统更新", 50, False, "暂停"], ["文件扫描", 75, True, "查看"], ["任务完成", 100, True, "详情"] ] headers = ["任务名称", "进度", "启用", "操作"] # 创建模型和视图 model = TableModel(data, headers) self.tableView = QTableView() self.tableView.setModel(model) # 设置自定义委托 self.tableView.setItemDelegateForColumn(1, ProgressBarDelegate()) # 进度条列 self.tableView.setItemDelegateForColumn(2, CheckBoxDelegate()) # 复选框列 self.tableView.setItemDelegateForColumn(3, ButtonDelegate(self)) # 按钮列 # 连接按钮点击信号 self.buttonClicked.connect(self.handleButtonClick) # 设置列宽 header = self.tableView.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # 布局 central_widget = QWidget() layout = QVBoxLayout() layout.addWidget(self.tableView) central_widget.setLayout(layout) self.setCentralWidget(central_widget) def handleButtonClick(self, row, col): """处理按钮点击事件""" task = self.tableView.model()._data[row][0] print(f"按钮点击: 任务 '{task}' 行:{row} 列:{col}") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` ## 多控件实现原理 ### 1. 自定义委托机制 PyQt5 使用委托系统处理单元格的渲染和交互: - 进度条:使用 `QStyleOptionProgressBar` - 按钮:使用 `QStyleOptionButton` - 复选框:使用 `QStyleOptionButton` 配合 `CE_CheckBox` ```python # 进度条绘制 progress_option = QStyleOptionProgressBar() QApplication.style().drawControl(QStyle.CE_ProgressBar, progress_option, painter) # 按钮绘制 button_option = QStyleOptionButton() QApplication.style().drawControl(QStyle.CE_PushButton, button_option, painter) # 复选框绘制 checkbox_option = QStyleOptionButton() QApplication.style().drawControl(QStyle.CE_CheckBox, checkbox_option, painter) ``` ### 2. 事件处理机制 通过重写 `editorEvent()` 方法处理用户交互: ```python class ButtonDelegate(QStyledItemDelegate): def editorEvent(self, event, model, option, index): if event.type() == event.MouseButtonRelease: # 触发按钮点击事件 return True return super().editorEvent(event, model, option, index) ``` ### 3. 模型数据管理 模型需要为不同控件类型提供适当的数据角色: ```python def data(self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: return self._data[row][col] # 文本数据 elif role == Qt.CheckStateRole: return Qt.Checked if self._data[row][col] else Qt.Unchecked # 复选框状态 ``` ## 高级功能扩展 ### 1. 添加组合框委托 ```python class ComboBoxDelegate(QStyledItemDelegate): """组合框委托""" def __init__(self, items, parent=None): super().__init__(parent) self.items = items def paint(self, painter, option, index): # 绘制组合框 combo_option = QStyleOptionComboBox() combo_option.rect = option.rect.adjusted(2, 2, -2, -2) combo_option.currentText = index.data(Qt.DisplayRole) combo_option.state = QStyle.State_Enabled QApplication.style().drawComplexControl(QStyle.CC_ComboBox, combo_option, painter) def createEditor(self, parent, option, index): editor = QComboBox(parent) editor.addItems(self.items) return editor def setEditorData(self, editor, index): value = index.data(Qt.DisplayRole) idx = editor.findText(value) if idx >= 0: editor.setCurrentIndex(idx) def setModelData(self, editor, model, index): model.setData(index, editor.currentText()) ``` ### 2. 添加悬停效果 ```python def paint(self, painter, option, index): # 添加悬停效果 if option.state & QStyle.State_MouseOver: option.backgroundBrush = QBrush(QColor(240, 248, 255)) # ... 原有绘制代码 ... ``` ### 3. 实现动态数据更新 ```python class TableModel(QAbstractTableModel): # ... def updateProgress(self, row, value): """更新指定行的进度值""" if 0 <= row < len(self._data): index = self.index(row, 1) # 进度列索引 self._data[row][1] = value self.dataChanged.emit(index, index, [Qt.DisplayRole]) ``` ## 性能优化建议 1. **局部刷新**: ```python # 只刷新特定单元格 self.dataChanged.emit(topLeftIndex, bottomRightIndex) ``` 2. **分批加载**: ```python class LargeTableModel(QAbstractTableModel): def canFetchMore(self, parent): return self._rows < self.totalRows def fetchMore(self, parent): # 加载更多数据 ``` 3. **使用模型信号**: ```python model.dataChanged.connect(view.update) model.layoutChanged.connect(view.reset) ``` ## 常见问题解决方案 1. **控件无法点击**: - 确保委托正确实现了 `editorEvent()` 方法 - 检查模型是否支持 `Qt.ItemIsEditable` 标志 2. **样式不致**: - 使用 `QApplication.style()` 确保原生样式 - 避免硬编码尺寸,使用 `option.rect` 动态计算 3. **性能问题**: - 避免在 `paint()` 方法中执行复杂计算 - 使用 `QAbstractItemModel` 的 `beginResetModel()` 和 `endResetModel()` 批量更新 通过自定义委托实现多种控件类型,可以在 QTableView 中创建高度定制化的数据展示界面[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值