QT中级(3)QTableView自定义委托(三)实现QCheckBox委托并且将QCheckBox居中

本文介绍了如何在QT中使用QStyledItemDelegate为QTableView的某一列创建自定义委托,以实现QCheckBox的居中显示和状态变化响应。通过创建委托类,重写paint和editorEvent函数,以及在主线程中设置委托,成功地在表格视图中集成QCheckBox功能。

1 写在前面的话

我们在之前写的《QT(7)-初识委托》文章末尾提到,“使用一个类继承QStyledItemDelegate实现常用的控件委托,在使用时可以直接调用接口,灵活实现各种委托”。我们接下来几篇文章将先详细讲解各个控件的委托,最后整理成一个类,并分享源码。如果大家感兴趣,可以点个关注,后面我们一起学习!

讲解比较详细,大家可以跟着一步一步做,自己就可以实现了。

2 需要用到的部分知识

《QT(3)-QTableView》
《QT(4)-QAbstractItemView》
《QT(6)-QStandardItemModel》
《QT(7)-初识委托》

3 同系列文章

QT中级(1)QTableView自定义委托(一)实现QSpinBox、QDoubleSpinBox委托
QT中级(2)QTableView自定义委托(二)实现QProgressBar委托

4 实现QCheckBox委托

在QTableView中实现QCheckBox委托并居中显示,需要以下步骤:

  1. 创建一个自定义的委托类,继承自QStyledItemDelegate。
  2. 重写该委托类的paint函数,实现QCheckBox的居中显示。
  3. 重写该委托类的editorEvent函数,实现QCheckBox的状态改变。
  4. 为QTableView的相应单元格设置该委托类。
  5. 在数据模型中处理QCheckBox状态的更改,以便在重绘时正确显示。

4.1 第一步

和前几篇文章一样,我们需要建立一个项目,并且需要我们拖拽QTableView,以及对其初始化。我们在这里就不在赘述,大家可以参考前面几篇文章是如何创建的。

4.2 第二步

  1. 创建Delegate类,方法和前面几篇文章一样
  2. 需要我们先定义三函数
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;

这三个函数的含义前面几篇文章都已经提到了,不再赘述。

4.3 第三步

  1. 实现paint函数
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{

    if(index.column() == 2)
    {
        bool value = index.data(Qt::UserRole).toInt();
        QStyleOptionButton checkBoxOption;
        QRect checkBoxRect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkBoxOption);
        checkBoxOption.rect = option.rect;
        checkBoxOption.rect.setLeft(option.rect.left() + (option.rect.width() - checkBoxRect.width()) / 2);
        checkBoxOption.rect.setTop(option.rect.top() + (option.rect.height() - checkBoxRect.height()) / 2);
        checkBoxOption.state = value ? QStyle::State_On :QStyle::State_Off;
        checkBoxOption.state |= QStyle::State_Enabled;
        QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxOption, painter);
    }//if
    else
    {
        QStyledItemDelegate::paint(painter,option,index);
    }
}
  1. 实现editorEvent函数
bool Delegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if(event->type() == QEvent::MouseButtonDblClick)//禁止双击编辑
        return true;
    //过滤鼠标松开
    if (event->type() == QEvent::MouseButtonRelease) {
        return false;
    }

    bool checked = index.data(Qt::UserRole).toBool();
    model->setData(index, !checked, Qt::UserRole);

    return QStyledItemDelegate::editorEvent(event,model,option,index);
}

4.4 最后一步

我们需要在mainwindow.cpp中的init()调用delegate类实现委托。我们将QTableView的第一列设置为委托:

更新mainwindow.cpp中的init()函数

void MainWindow::init()
{
    QStringList columnNames;
    columnNames<<"QSpinBox"<<"QComboBox"<<"QCheckBox"<<"QProgressBar"<<"···";

    model = new QStandardItemModel;
    model->setRowCount(10);
    model->setHorizontalHeaderLabels(columnNames);
    ui->tableView->setModel(model);

    Delegate *checkDelegate = new Delegate;
    ui->tableView->setItemDelegateForColumn(2,checkDelegate);
}

4.5 效果图

在这里插入图片描述

5 源码

源码

<think>我们使用QStyledItemDelegate来实现一个在QTableView中显示QCheckBox委托。关键点在于重写paint()方法绘制复选框,以及editorEvent()方法处理用户点击事件。同时,我们可能还需要重写createEditor()、setEditorData()、setModelData()等方法,但如果我们只是想要一个不可编辑但可点击的复选框(即通过点击来改变状态),我们可以通过paint和editorEvent来实现,而不必创建真正的编辑器。 下面是一个完整的示例,包括自定义委托、模型和主窗口。 步骤: 1. 创建一个自定义委托类,继承自QStyledItemDelegate。 2. 重写paint()方法:使用QStyle绘制复选框。 3. 重写editorEvent()方法:处理鼠标事件,当用户点击时切换复选框状态。 4. 重写sizeHint()方法:提供合适的大小。 5. 创建一个表格模型,其中第一列用于存储复选框的状态(Qt.Checked或Qt.Unchecked)。 6. 在模型中实现data()方法,对于复选框列返回Qt.CheckStateRole数据。 7. 在模型中实现setData()方法,处理复选框状态的变化。 8. 在模型中实现flags()方法,确保复选框列是可勾选的。 注意:我们这里使用第一列作为复选框列。 完整代码示例:</think># PyQt5 QStyledItemDelegate 实现 QCheckBox 复选框的完整指南 在 PyQt5 中,使用 `QStyledItemDelegate` 实现表格中的 QCheckBox 控件需要重写核心方法并处理绘制逻辑。以下是详细实现方案: ## 核心实现原理 ### 委托类结构 ```python class CheckBoxDelegate(QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) def paint(self, painter, option, index): # 绘制复选框 def editorEvent(self, event, model, option, index): # 处理鼠标事件 def sizeHint(self, option, index): # 设置单元格大小 ``` ## 完整实现代码 ```python import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, QStyledItemDelegate, QStyle, QStyleOptionButton, QCheckBox) from PyQt5.QtCore import Qt, QModelIndex, QAbstractTableModel, QRect, QSize from PyQt5.QtGui import QPainter class CheckBoxDelegate(QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) # 获取系统标准复选框大小 self.checkbox_size = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth) def paint(self, painter, option, index): # 保存绘制器状态 painter.save() # 获取数据 checked = index.data(Qt.CheckStateRole) == Qt.Checked # 创建样式选项 checkbox_style = QStyleOptionButton() checkbox_style.rect = self.getCheckboxRect(option) checkbox_style.state = option.state | QStyle.State_Enabled # 设置复选框状态 if checked: checkbox_style.state |= QStyle.State_On else: checkbox_style.state |= QStyle.State_Off # 绘制复选框 QApplication.style().drawControl( QStyle.CE_CheckBox, checkbox_style, painter ) # 恢复绘制器状态 painter.restore() def editorEvent(self, event, model, option, index): """处理鼠标点击事件""" # 只处理鼠标释放事件 if event.type() == event.MouseButtonRelease: # 检查点击是否在复选框区域内 if self.getCheckboxRect(option).contains(event.pos()): # 切换选中状态 current = index.data(Qt.CheckStateRole) new_state = Qt.Unchecked if current == Qt.Checked else Qt.Checked model.setData(index, new_state, Qt.CheckStateRole) return True # 事件已处理 return super().editorEvent(event, model, option, index) def getCheckboxRect(self, option): """计算复选框位置(居中显示)""" return QRect( option.rect.x() + (option.rect.width() - self.checkbox_size) // 2, option.rect.y() + (option.rect.height() - self.checkbox_size) // 2, self.checkbox_size, self.checkbox_size ) def sizeHint(self, option, index): """设置单元格大小""" return QSize(self.checkbox_size + 10, self.checkbox_size + 10) class TableModel(QAbstractTableModel): def __init__(self, data, headers, parent=None): super().__init__(parent) 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): if not index.isValid(): return None row, col = index.row(), index.column() if role == Qt.DisplayRole and col > 0: return self._data[row][col] elif role == Qt.CheckStateRole and col == 0: # 第一列为复选框 return self._data[row][0] elif role == Qt.TextAlignmentRole and col == 0: return Qt.AlignCenter return None def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): return False row, col = index.row(), index.column() if role == Qt.CheckStateRole and col == 0: self._data[row][0] = value # 通知视图数据已更改 self.dataChanged.emit(index, index, [Qt.CheckStateRole]) return True return False def flags(self, index): flags = super().flags(index) if index.column() == 0: # 第一列为可点击复选框 flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEnabled return flags def headerData(self, section, orientation, role): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self._headers[section] return None class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("QCheckBox委托示例") self.resize(500, 300) # 创建表格数据 [复选框状态, 姓名, 职位] data = [ [Qt.Checked, "张", "工程师"], [Qt.Unchecked, "李四", "设计师"], [Qt.Checked, "王五", "产品经理"], [Qt.Unchecked, "赵六", "测试工程师"] ] headers = ["选择", "姓名", "职位"] # 设置模型和视图 self.model = TableModel(data, headers) self.table = QTableView() self.table.setModel(self.model) # 应用自定义委托到选择列 self.table.setItemDelegateForColumn(0, CheckBoxDelegate()) # 调整列宽 self.table.setColumnWidth(0, 60) self.table.setColumnWidth(1, 100) self.table.setColumnWidth(2, 150) # 设置表头 header = self.table.horizontalHeader() header.setSectionResizeMode(0, header.Fixed) header.setSectionResizeMode(1, header.Stretch) self.setCentralWidget(self.table) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` ## 关键方法详解 ### 1. 绘制方法 `paint()` ```python def paint(self, painter, option, index): # 获取复选框状态 checked = index.data(Qt.CheckStateRole) == Qt.Checked # 创建样式选项 checkbox_style = QStyleOptionButton() checkbox_style.rect = self.getCheckboxRect(option) checkbox_style.state = option.state | QStyle.State_Enabled # 设置状态 if checked: checkbox_style.state |= QStyle.State_On else: checkbox_style.state |= QStyle.State_Off # 绘制 QApplication.style().drawControl(QStyle.CE_CheckBox, checkbox_style, painter) ``` ### 2. 事件处理 `editorEvent()` ```python def editorEvent(self, event, model, option, index): # 只处理鼠标释放事件 if event.type() == event.MouseButtonRelease: # 检查点击位置 if self.getCheckboxRect(option).contains(event.pos()): # 切换状态 current = index.data(Qt.CheckStateRole) new_state = Qt.Unchecked if current == Qt.Checked else Qt.Checked model.setData(index, new_state, Qt.CheckStateRole) return True # 事件已处理 return False ``` ### 3. 位置计算 `getCheckboxRect()` ```python def getCheckboxRect(self, option): # 居中显示 return QRect( option.rect.x() + (option.rect.width() - self.checkbox_size) // 2, option.rect.y() + (option.rect.height() - self.checkbox_size) // 2, self.checkbox_size, self.checkbox_size ) ``` ## 高级功能扩展 ### 1. 态复选框实现 ```python def paint(self, painter, option, index): state = index.data(Qt.CheckStateRole) # 创建样式选项 checkbox_style = QStyleOptionButton() checkbox_style.rect = self.getCheckboxRect(option) checkbox_style.state = option.state | QStyle.State_Enabled # 设置态 if state == Qt.PartiallyChecked: checkbox_style.state |= QStyle.State_NoChange elif state == Qt.Checked: checkbox_style.state |= QStyle.State_On else: checkbox_style.state |= QStyle.State_Off # 绘制 QApplication.style().drawControl(QStyle.CE_CheckBox, checkbox_style, painter) def editorEvent(self, event, model, option, index): if event.type() == event.MouseButtonRelease: if self.getCheckboxRect(option).contains(event.pos()): current = index.data(Qt.CheckStateRole) # 状态循环:未选中 -> 部分选中 -> 全选中 if current == Qt.Unchecked: new_state = Qt.PartiallyChecked elif current == Qt.PartiallyChecked: new_state = Qt.Checked else: new_state = Qt.Unchecked model.setData(index, new_state, Qt.CheckStateRole) return True return False ``` ### 2. 自定义样式 ```python def paint(self, painter, option, index): # 自定义背景色 if index.data(Qt.CheckStateRole) == Qt.Checked: painter.fillRect(option.rect, QColor(220, 255, 220)) elif index.data(Qt.CheckStateRole) == Qt.PartiallyChecked: painter.fillRect(option.rect, QColor(255, 255, 200)) # 绘制复选框 super().paint(painter, option, index) # 添加自定义文本 if index.column() > 0: painter.drawText(option.rect.adjusted(5, 0, 0, 0), Qt.AlignVCenter, index.data()) ``` ### 3. 行选择联动 ```python def editorEvent(self, event, model, option, index): if event.type() == event.MouseButtonRelease: if self.getCheckboxRect(option).contains(event.pos()): current = index.data(Qt.CheckStateRole) new_state = Qt.Unchecked if current == Qt.Checked else Qt.Checked # 更新所有行 for row in range(model.rowCount()): model.setData(model.index(row, 0), new_state, Qt.CheckStateRole) return True return False ``` ## 性能优化技巧 ### 1. 避免重复计算 ```python def __init__(self, parent=None): super().__init__(parent) # 预计算复选框大小 self.checkbox_size = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth) ``` ### 2. 部分刷新 ```python def setData(self, index, value, role): if role == Qt.CheckStateRole: # 仅刷新复选框列 self.dataChanged.emit( index, index, [Qt.CheckStateRole] # 仅通知复选框状态变化 ) ``` ### 3. 批量更新 ```python def setModelData(self, editor, model, index): # 开始批量更新 model.layoutAboutToBeChanged.emit() # 更新所有相关单元格 for row in selected_rows: model.setData(model.index(row, index.column()), new_state, Qt.CheckStateRole) # 结束批量更新 model.layoutChanged.emit() ``` ## 模型-视图架构要点 1. **数据角色**: - `Qt.DisplayRole`: 显示文本 - `Qt.CheckStateRole`: 复选框状态 - `Qt.TextAlignmentRole`: 文本对齐方式 2. **状态管理**: ```python def flags(self, index): flags = super().flags(index) if index.column() == 0: # 启用复选框交互 flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEnabled return flags ``` 3. **数据同步**: ```python def data(self, index, role): if role == Qt.CheckStateRole: # 返回存储的复选框状态 return self._data[row][0] ``` ## 数学表达式示例 在计算复选框位置时,我们使用以下公式确保居中显示: $$x = \text{option.rect.x()} + \frac{\text{option.rect.width()} - \text{checkbox_size}}{2}$$ $$y = \text{option.rect.y()} + \frac{\text{option.rect.height()} - \text{checkbox_size}}{2}$$ 其中 $\text{checkbox_size}$ 是系统标准复选框大小[^1]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m晴朗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值