Qt 之 QHeaderView 添加复选框

作者: 一去、二三里
个人微信号: iwaleon
微信公众号: 高效程序员

前面分享了QTableView中如何添加复选框。本节主要介绍QTableView中的表头-QHeaderView添加复选框的功能,下面以水平表头为例,垂直表头类似!

效果

这里写图片描述

QHeaderView

源码

TableHeaderView::TableHeaderView(Qt::Orientation orientation, QWidget *parent)
    : QHeaderView(orientation, parent),
      m_bPressed(false),
      m_bChecked(false),
      m_bTristate(false),
      m_bNoChange(false),
      m_bMoving(false)
{
    // setStretchLastSection(true);
    setHighlightSections(false);
    setMouseTracking(true);

    // 响应鼠标
    setSectionsClickable(true);
}

// 槽函数,用于更新复选框状态
void TableHeaderView::onStateChanged(int state)
{
    if (state == Qt::PartiallyChecked) {
        m_bTristate = true;
        m_bNoChange = true;
    } else {
        m_bNoChange = false;
    }

    m_bChecked = (state != Qt::Unchecked);
    update();
}

// 绘制复选框
void TableHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
    painter->save();
    QHeaderView::paintSection(painter, rect, logicalIndex);
    painter->restore();

    if (logicalIndex == CHECK_BOX_COLUMN)
    {
        QStyleOptionButton option;
        option.initFrom(this);

        if (m_bChecked)
            option.state |= QStyle::State_Sunken;

        if (m_bTristate && m_bNoChange)
            option.state |= QStyle::State_NoChange;
        else
            option.state |= m_bChecked ? QStyle::State_On : QStyle::State_Off;
        if (testAttribute(Qt::WA_Hover) && underMouse()) {
            if (m_bMoving)
                option.state |= QStyle::State_MouseOver;
            else
                option.state &= ~QStyle::State_MouseOver;
        }

        QCheckBox checkBox;
        option.iconSize = QSize(20, 20);
        option.rect = rect;
        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter, &checkBox);
        //style()->drawItemPixmap(painter, rect, Qt::AlignCenter, QPixmap(":/images/checkBoxChecked"));
        //style()->drawControl(QStyle::CE_CheckBox, &option, painter, this);
    }
}

// 鼠标按下表头
void TableHeaderView::mousePressEvent(QMouseEvent *event)
{
    int nColumn = logicalIndexAt(event->pos());
    if ((event->buttons() & Qt::LeftButton) && (nColumn == CHECK_BOX_COLUMN))
    {
        m_bPressed = true;
    }
    else
    {
        QHeaderView::mousePressEvent(event);
    }
}

// 鼠标从表头释放,发送信号,更新model数据
void TableHeaderView::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_bPressed)
    {
        if (m_bTristate && m_bNoChange)
        {
            m_bChecked = true;
            m_bNoChange = false;
        }
        else
        {
            m_bChecked = !m_bChecked;
        }

        update();

        Qt::CheckState state = m_bChecked ? Qt::Checked : Qt::Unchecked;

        emit stateChanged(state);
    }
    else
    {
        QHeaderView::mouseReleaseEvent(event);
    }

    m_bPressed = false;
}

// 鼠标滑过、离开,更新复选框状态
bool TableHeaderView::event(QEvent *event)
{
    if (event->type() == QEvent::Enter || event->type() == QEvent::Leave)
    {
        QMouseEvent *pEvent = static_cast<QMouseEvent *>(event);
        int nColumn = logicalIndexAt(pEvent->x());
        if (nColumn == CHECK_BOX_COLUMN)
        {
            m_bMoving = (event->type() == QEvent::Enter);

            update();
            return true;
        }
    }

    return QHeaderView::event(event);
}

接口说明

  • onStateChanged
    槽函数,与model关联,当model的数据发生变化时,表头复选中状态也随之变化。

  • paintSection
    绘制部分,主要用于绘制复选框,主要设置QStyleOptionButton的状态,然后通过QStyle的drawPrimitive绘制样式。

  • mousePressEvent
    鼠标按下表头,设置被按下的标志为true。

  • mouseReleaseEvent
    鼠标从表头释放,发送信号,更新model数据。

  • event
    鼠标滑过、离开,更新复选框状态。

QAbstractTableModel

源码

源码过多,这里只贴更改部分及新增部分,其它部分请参考:Qt之QTableView添加复选框(QAbstractItemDelegate)

// 设置表格项数据
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

    int nColumn = index.column();
    FileRecord record = m_recordList.at(index.row());
    switch (role)
    {
    case Qt::DisplayRole:
    {
        if (nColumn == File_PATH_COLUMN)
        {
            record.strFilePath = value.toString();

            m_recordList.replace(index.row(), record);
            emit dataChanged(index, index);
            return true;
        }
    }
    case Qt::CheckStateRole:
    case Qt::UserRole:
    {
        if (nColumn == CHECK_BOX_COLUMN)
        {
            record.bChecked = value.toBool();

            m_recordList.replace(index.row(), record);
            emit dataChanged(index, index);

            if (role == Qt::UserRole)
                onStateChanged();
            return true;
        }
    }
    default:
        return false;
    }
    return false;
}

// 复选状态改变,通知表头更新复选状态
void TableModel::onStateChanged()
{
    Qt::CheckState state = Qt::Unchecked;
    int nCount = m_recordList.count();
    int nSelectedCount = 0;
    FileRecord record;
    for (int i = 0; i < nCount; ++i)
    {
        record = m_recordList.at(i);
        if (record.bChecked)
            ++nSelectedCount;
    }

    if (nSelectedCount >= nCount)
    {
        state = Qt::Checked;
    }
    else if (nSelectedCount > 0)
    {
        state = Qt::PartiallyChecked;
    }

    emit stateChanged(state);
}

// 接收表头复选状态更新变化,修改表格复选状态
void TableModel::onStateChanged(int state)
{
    QModelIndex index;
    for (int i = 0; i < rowCount(); ++i)
    {
        index = this->index(i, CHECK_BOX_COLUMN);
        setData(index, state == Qt::Checked, Qt::CheckStateRole);
    }
}

接口说明

  • setData
    设置表格项数据,关键代码:当role为Qt::UserRole时候,会调用onStateChanged()函数,来更新表头!

  • onStateChanged
    复选状态改变,通知表头更新复选状态。

  • onStateChanged(int state)
    槽函数,接收表头复选状态更新变化,修改表格复选状态。

使用

这时,就需要将header与model的信号与槽关联起来,更新复选框状态了!

QTableView *pTableView = new QTableView(this);
TableModel *pModel = new TableModel(this);
TableHeaderView *pHeader = new TableHeaderView(Qt::Horizontal, this);
CheckBoxDelegate *pDelegate = new CheckBoxDelegate(this);

pTableView->setHorizontalHeader(pHeader);
pTableView->setModel(pModel);
pTableView->setItemDelegate(pDelegate);
    
connect(pModel, SIGNAL(stateChanged(int)), pHeader, SLOT(onStateChanged(int)));
connect(pHeader, SIGNAL(stateChanged(int)), pModel, SLOT(onStateChanged(int)));
<think>我们将在PyQt5中实现QHeaderView表头添加复选框。参考引用[1]中的C++代码,我们将采用类似的思想,但使用PyQt5实现。 实现思路: 1. 自定义一个HeaderView类,继承自QHeaderView。 2. 在表头中绘制复选框,并管理其状态。 3. 处理鼠标事件,实现复选框的点击切换。 4. 通过信号传递复选框状态变化。 注意:由于QHeaderView的复杂性,我们不会像引用[1]那样使用QCheckBox控件,而是通过绘制和事件处理来模拟复选框,因为直接在表头添加控件可能会带来布局和事件冲突问题。 步骤: 1. 创建自定义HeaderView类,重写paintSection方法绘制复选框。 2. 重写mousePressEvent和mouseMoveEvent(可选)来处理复选框的点击。 3. 管理复选框的状态(存储每列的复选框状态,或者如果是全选复选框则只存储一个状态)。 4. 当复选框状态改变时发射信号。 以下代码将实现在水平表头的第一列(索引0)绘制一个复选框,并实现全选/全不选功能。如果需要多列复选框,可以扩展。 代码实现:</think># PyQt5 QHeaderView 表头添加复选框实现方法 在 PyQt5 中,可以通过自定义 `QHeaderView` 类来实现表头添加复选框的功能。下面我将详细介绍实现方法并提供完整代码示例。 ## 实现思路 1. **自定义表头类**:继承 `QHeaderView` 并重写关键方法 2. **复选框绘制**:在 `paintSection` 中绘制复选框样式 3. **事件处理**:处理鼠标事件实现复选框点击切换 4. **状态管理**:存储和更新复选框的选中状态 5. **信号机制**:自定义信号传递复选框状态变化 ## 完整代码实现 ```python import sys from PyQt5.QtWidgets import (QApplication, QHeaderView, QTableWidget, QTableWidgetItem, QWidget, QVBoxLayout, QStyle, QStyleOptionButton) from PyQt5.QtCore import Qt, QRect, QSize, pyqtSignal from PyQt5.QtGui import QPainter, QBrush, QColor, QPalette, QPen, QFont class CheckboxHeader(QHeaderView): """自定义表头类,实现复选框功能""" stateChanged = pyqtSignal(int, Qt.CheckState) # 信号:(列索引, 选中状态) def __init__(self, orientation, parent=None): super().__init__(orientation, parent) self.setSectionsClickable(True) self.setHighlightSections(True) self.checkboxRects = {} # 存储复选框位置 self.checkboxStates = {} # 存储复选框状态 self.checkboxColumns = [] # 存储需要显示复选框的列 def setCheckboxForColumn(self, column, visible=True): """设置指定列是否显示复选框""" if visible and column not in self.checkboxColumns: self.checkboxColumns.append(column) self.checkboxStates[column] = Qt.Unchecked self.updateSection(column) elif not visible and column in self.checkboxColumns: self.checkboxColumns.remove(column) if column in self.checkboxStates: del self.checkboxStates[column] self.updateSection(column) def setCheckState(self, column, state): """设置指定列的复选框状态""" if column in self.checkboxStates and self.checkboxStates[column] != state: self.checkboxStates[column] = state self.updateSection(column) self.stateChanged.emit(column, state) def getCheckState(self, column): """获取指定列的复选框状态""" return self.checkboxStates.get(column, Qt.Unchecked) def paintSection(self, painter, rect, logicalIndex): """绘制表头部分,包括复选框""" # 绘制默认表头 painter.save() super().paintSection(painter, rect, logicalIndex) painter.restore() # 如果该列需要显示复选框 if logicalIndex in self.checkboxColumns: # 计算复选框位置(居右显示) size = min(rect.height(), rect.width()) - 10 checkboxRect = QRect( rect.right() - size - 5, rect.top() + (rect.height() - size) // 2, size, size ) self.checkboxRects[logicalIndex] = checkboxRect # 绘制复选框 option = QStyleOptionButton() option.rect = checkboxRect # 设置复选框状态 if self.checkboxStates[logicalIndex] == Qt.Checked: option.state = QStyle.State_On | QStyle.State_Enabled elif self.checkboxStates[logicalIndex] == Qt.PartiallyChecked: option.state = QStyle.State_NoChange | QStyle.State_Enabled else: option.state = QStyle.State_Off | QStyle.State_Enabled # 绘制复选框 self.style().drawControl(QStyle.CE_CheckBox, option, painter) def mousePressEvent(self, event): """处理鼠标点击事件""" index = self.logicalIndexAt(event.pos()) # 如果点击的是设置了复选框的列 if index in self.checkboxColumns: checkboxRect = self.checkboxRects.get(index) if checkboxRect and checkboxRect.contains(event.pos()): # 切换复选框状态 if self.checkboxStates[index] == Qt.Checked: new_state = Qt.Unchecked else: new_state = Qt.Checked self.setCheckState(index, new_state) else: super().mousePressEvent(event) def sizeHint(self): """确保表头有足够高度显示复选框""" size = super().sizeHint() size.setHeight(max(size.height(), 30)) # 最小高度30像素 return size class TableWithCheckboxHeader(QWidget): """带复选框表头的表格""" def __init__(self): super().__init__() self.setWindowTitle("QHeaderView添加复选框") self.resize(600, 400) layout = QVBoxLayout(self) # 创建表格 self.table = QTableWidget(5, 4) self.table.setHorizontalHeaderLabels(["全选", "姓名", "部门", "状态"]) # 设置自定义表头 self.header = CheckboxHeader(Qt.Horizontal, self.table) self.table.setHorizontalHeader(self.header) # 设置复选框列 self.header.setCheckboxForColumn(0) # 第一列显示复选框 self.header.setCheckboxForColumn(3) # 第四列显示复选框 # 连接复选框状态变化信号 self.header.stateChanged.connect(self.handleHeaderCheckboxChange) # 初始化表格数据 self.initTableData() layout.addWidget(self.table) def initTableData(self): """初始化表格数据""" data = [ [False, "张三", "技术部", True], [False, "李四", "市场部", True], [False, "王五", "财务部", False], [False, "赵六", "技术部", True], [False, "钱七", "市场部", False] ] for row, rowData in enumerate(data): for col, value in enumerate(rowData): if col == 0 or col == 3: # 复选框列 item = QTableWidgetItem() item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setCheckState(Qt.Checked if value else Qt.Unchecked) else: item = QTableWidgetItem(str(value)) item.setTextAlignment(Qt.AlignCenter) self.table.setItem(row, col, item) # 设置列宽 self.table.setColumnWidth(0, 80) self.table.setColumnWidth(1, 120) self.table.setColumnWidth(2, 120) self.table.setColumnWidth(3, 80) def handleHeaderCheckboxChange(self, column, state): """处理表头复选框状态变化""" column_name = self.table.horizontalHeaderItem(column).text() print(f"表头 {column_name} 列复选框状态变化: {'选中' if state==Qt.Checked else '未选中'}") # 如果是全选列 if column == 0: # 更新所有行的复选框状态 for row in range(self.table.rowCount()): item = self.table.item(row, 0) if item: item.setCheckState(state) print(f"已{'全选' if state==Qt.Checked else '取消全选'}所有行") # 如果是状态列 elif column == 3: # 更新所有行的状态列 for row in range(self.table.rowCount()): item = self.table.item(row, 3) if item: item.setCheckState(state) print(f"已{'启用' if state==Qt.Checked else '禁用'}所有员工状态") if __name__ == "__main__": app = QApplication(sys.argv) # 设置全局样式 app.setStyleSheet(""" QHeaderView::section { background-color: #f0f6fc; border: 1px solid #c0c0c0; padding: 4px; } QTableWidget { gridline-color: #e0e0e0; alternate-background-color: #f8f8f8; } QTableWidgetItem { padding: 5px; } """) window = TableWithCheckboxHeader() window.show() sys.exit(app.exec_()) ``` ## 关键实现解析 ### 1. 自定义表头类 `CheckboxHeader` ```python class CheckboxHeader(QHeaderView): stateChanged = pyqtSignal(int, Qt.CheckState) # 自定义信号 def __init__(self, orientation, parent=None): super().__init__(orientation, parent) self.checkboxRects = {} # 存储复选框位置 self.checkboxStates = {} # 存储复选框状态 self.checkboxColumns = [] # 存储需要显示复选框的列 ``` ### 2. 复选框绘制方法 ```python def paintSection(self, painter, rect, logicalIndex): # 如果该列需要显示复选框 if logicalIndex in self.checkboxColumns: # 计算复选框位置 size = min(rect.height(), rect.width()) - 10 checkboxRect = QRect(...) self.checkboxRects[logicalIndex] = checkboxRect # 绘制复选框 option = QStyleOptionButton() option.rect = checkboxRect # 设置复选框状态 if self.checkboxStates[logicalIndex] == Qt.Checked: option.state = QStyle.State_On | QStyle.State_Enabled # ... self.style().drawControl(QStyle.CE_CheckBox, option, painter) ``` ### 3. 复选框状态管理 ```python def setCheckState(self, column, state): """设置指定列的复选框状态""" if column in self.checkboxStates and self.checkboxStates[column] != state: self.checkboxStates[column] = state self.updateSection(column) self.stateChanged.emit(column, state) # 发射信号 ``` ### 4. 鼠标事件处理 ```python def mousePressEvent(self, event): index = self.logicalIndexAt(event.pos()) # 如果点击的是设置了复选框的列 if index in self.checkboxColumns: checkboxRect = self.checkboxRects.get(index) if checkboxRect and checkboxRect.contains(event.pos()): # 切换复选框状态 if self.checkboxStates[index] == Qt.Checked: new_state = Qt.Unchecked else: new_state = Qt.Checked self.setCheckState(index, new_state) # 更新状态 ``` ### 5. 表头高度调整 ```python def sizeHint(self): """确保表头有足够高度显示复选框""" size = super().sizeHint() size.setHeight(max(size.height(), 30)) # 最小高度30像素 return size ``` ## 高级功能扩展 ### 1. 添加三态复选框 ```python def setTristate(self, column, tristate=True): """设置复选框是否支持三态""" if column in self.checkboxColumns: self.tristateColumns.add(column) def mousePressEvent(self, event): # ... if index in self.checkboxColumns and checkboxRect.contains(event.pos()): if index in self.tristateColumns: # 三态切换:未选 -> 部分选 -> 全选 -> 未选 if self.checkboxStates[index] == Qt.Unchecked: new_state = Qt.PartiallyChecked elif self.checkboxStates[index] == Qt.PartiallyChecked: new_state = Qt.Checked else: new_state = Qt.Unchecked else: # 标准二态切换 new_state = Qt.Checked if self.checkboxStates[index] != Qt.Checked else Qt.Unchecked self.setCheckState(index, new_state) ``` ### 2. 复选框与表格数据联动 ```python def handleHeaderCheckboxChange(self, column, state): """处理表头复选框状态变化""" if column == 0: # 全选列 # 更新所有行的复选框状态 for row in range(self.table.rowCount()): item = self.table.item(row, 0) if item: item.setCheckState(state) elif column == 3: # 状态列 # 更新所有行的状态列 for row in range(self.table.rowCount()): item = self.table.item(row, 3) if item: item.setCheckState(state) ``` ### 3. 自定义复选框样式 ```python # 在样式表中添加复选框样式 app.setStyleSheet(""" QHeaderView::section { background-color: #f0f6fc; border: 1px solid #c0c0c0; padding: 4px; } /* 复选框选中样式 */ QHeaderView::indicator:checked { background-color: #4CAF50; border: 1px solid #388E3C; } /* 复选框未选中样式 */ QHeaderView::indicator:unchecked { background-color: #F5F5F5; border: 1px solid #BDBDBD; } /* 复选框部分选中样式 */ QHeaderView::indicator:indeterminate { background-color: #FFC107; border: 1px solid #FFA000; } """) ``` ### 4. 动态更新表头复选框状态 ```python def updateHeaderCheckbox(self, column): """根据表格数据更新表头复选框状态""" if column not in self.header.checkboxColumns: return checked_count = 0 total_count = self.table.rowCount() for row in range(total_count): item = self.table.item(row, column) if item and item.checkState() == Qt.Checked: checked_count += 1 # 计算表头复选框状态 if checked_count == 0: state = Qt.Unchecked elif checked_count == total_count: state = Qt.Checked else: state = Qt.PartiallyChecked # 更新表头状态 self.header.setCheckState(column, state) ``` ## 常见问题解决方案 ### 1. 复选框位置不准确 **解决方案**:精确计算复选框位置 ```python def paintSection(self, painter, rect, logicalIndex): if logicalIndex in self.checkboxColumns: # 根据列宽和表头高度动态计算大小 size = min(rect.height() - 8, rect.width() * 0.3) checkboxRect = QRect( rect.left() + (rect.width() - size) // 2, # 水平居中 rect.top() + (rect.height() - size) // 2, # 垂直居中 size, size ) ``` ### 2. 复选框点击区域不灵敏 **解决方案**:增加点击区域容差 ```python def mousePressEvent(self, event): # ... if checkboxRect and checkboxRect.adjusted(-5, -5, 5, 5).contains(event.pos()): # 处理复选框点击 ``` ### 3. 表头高度不足导致显示不全 **解决方案**:设置最小高度 ```python # 在表格初始化时设置 self.header.setMinimumHeight(35) self.header.setDefaultSectionSize(120) # 设置列宽 ``` ### 4. 多列复选框状态同步问题 **解决方案**:使用状态管理类 ```python class CheckboxStateManager: """管理多列复选框状态""" def __init__(self): self.states = {} self.listeners = [] def setState(self, column, state): self.states[column] = state for listener in self.listeners: listener(column, state) def addListener(self, callback): self.listeners.append(callback) # 在表头类中使用 self.stateManager = CheckboxStateManager() self.stateManager.addListener(self.updateSection) ``` 通过以上实现,您可以在 PyQt5 表格的表头添加功能完善的复选框控件,实现全选/取消全选等批量操作功能[^1]。
评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一去丶二三里

有收获,再打赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值