Qt 之 QTableView 添加复选框(QAbstractTableModel)

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

使用QTableView,经常会遇到复选框,要实现一个好的复选框,除了常规的功能外,还应注意以下几点:

  1. 三态:不选/半选/全选
  2. 自定义风格(样式)

下面我们介绍一下常见的实现方式:

  • 编辑委托。
    方式:利用委托重载createEditor(),激活QCheckBox。
    特点:必须双击/选中,才能显示CheckBox控件。一般不满足实际中的直接显示的需要。

  • 使用QTableView的setIndexWidget(const QModelIndex &index, QWidget *widget)来实现。
    此功能用来显示可视区域内对应一个数据项的静态内容。如果想显示自定义的动态内容或执行自定义编辑器部件,子类化QItemDelegate代替。也就是说,这只适合做静态数据的显示,不适合做一些插入、更新、删除操作的数据显示。

  • 自定义模型QAbstractTableModel,通过flags()函数来实现。
    方式:通过将flags()设置为Qt::ItemIsUserCheckable实现可选中,然后配合setData()与data()来实现。
    特点:直接显示,可定义样式,默认左对齐,很难实现居中、右对齐。

  • 自定义委托QAbstractItemDelegate,通过paint()函数来实现。
    方式:通过控制editorEvent()实现鼠标的点击进行全选/半选/不选,然后由paint()实时绘制。
    特点:这种方式比较复杂,但适合扩展,除了可以嵌入复选框,还可以绘制其它控件-按钮、图片等。

效果

这里写图片描述

下面我们来介绍如何利用QAbstractTableModel的flags()函数来实现复选功能。

QAbstractTableModel

源码

table_model.cpp

#define CHECK_BOX_COLUMN 0
#define File_PATH_COLUMN 1

TableModel::TableModel(QObject *parent)
    : QAbstractTableModel(parent)
{

}

TableModel::~TableModel()
{

}

// 更新表格数据
void TableModel::updateData(QList<FileRecord> recordList)
{
    m_recordList = recordList;
    beginResetModel();
    endResetModel();
}

// 行数
int TableModel::rowCount(const QModelIndex &parent) const
{
    return m_recordList.count();
}

// 列数
int TableModel::columnCount(const QModelIndex &parent) const
{
    return 2;
}

// 设置表格项数据
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:
    {
        if (nColumn == CHECK_BOX_COLUMN)
        {
            record.bChecked = (value.toInt() == Qt::Checked);

            m_recordList.replace(index.row(), record);
            emit dataChanged(index, index);
            return true;
        }
    }
    default:
        return false;
    }
    return false;
}

// 表格项数据
QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    int nRow = index.row();
    int nColumn = index.column();
    FileRecord record = m_recordList.at(nRow);

    switch (role)
    {
    case Qt::TextColorRole:
        return QColor(Qt::white);
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
    case Qt::DisplayRole:
    {
        if (nColumn == File_PATH_COLUMN)
            return record.strFilePath;
        return "";
    }
    case Qt::CheckStateRole:
    {
        if (nColumn == CHECK_BOX_COLUMN)
            return record.bChecked ? Qt::Checked : Qt::Unchecked;
    }
    default:
        return QVariant();
    }

    return QVariant();
}

// 表头数据
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    switch (role)
    {
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
    case Qt::DisplayRole:
    {
        if (orientation == Qt::Horizontal)
        {
            if (section == CHECK_BOX_COLUMN)
                return QStringLiteral("状态");

            if (section == File_PATH_COLUMN)
                return QStringLiteral("文件路径");
        }
    }
    default:
        return QVariant();
    }

    return QVariant();
}

// 表格可选中、可复选
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return QAbstractItemModel::flags(index);

    Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    if (index.column() == CHECK_BOX_COLUMN)
        flags |= Qt::ItemIsUserCheckable;

    return flags;
}

接口说明

  • updateData
    主要用于更新数据,刷新界面。

  • data
    用来显示数据,根据角色(颜色、文本、对齐方式、选中状态等)判断需要显示的内容。

  • setData
    用来设置数据,根据角色(颜色、文本、对齐方式、选中状态等)判断需要设置的内容。

  • headerData
    用来显示水平/垂直表头的数据。

  • flags
    用来设置单元格的标志(可用、可选中、可复选等)。

样式

QSS不仅可以设置表格、表格项样式(背景、文本等),也可以通过indicator来设置复选框的样式:

QTableView {
        border: 1px solid rgb(50, 50, 50);
        background: rgb(57, 58, 60);
        gridline-color: rgb(60, 60, 60);
}
QTableView::item {
        border: none;
        border-bottom: 1px solid rgb(50, 50, 50);
}
QTableView::item:selected {
        background: rgb(0, 160, 230);
}
QTableView::indicator {
        width: 17px;
        height: 17px;
}
QTableView::indicator:enabled:unchecked {
        image: url(:/Images/checkBox);
}
QTableView::indicator:enabled:unchecked:hover {
        image: url(:/Images/checkBoxHover);
}
QTableView::indicator:enabled:unchecked:pressed {
        image: url(:/Images/checkBoxPressed);
}
QTableView::indicator:enabled:checked {
        image: url(:/Images/checkBoxChecked);
}
QTableView::indicator:enabled:checked:hover {
        image: url(:/Images/checkBoxCheckedHover);
}
QTableView::indicator:enabled:checked:pressed {
        image: url(:/Images/checkBoxCheckedPressed);
}
QTableView::indicator:enabled:indeterminate {
        image: url(:/Images/checkBoxIndeterminate);
}
QTableView::indicator:enabled:indeterminate:hover {
        image: url(:/Images/checkBoxIndeterminateHover);
}
QTableView::indicator:enabled:indeterminate:pressed {
        image: url(:/Images/checkBoxIndeterminatePressed);
}

使用

QTableView *pTableView = new QTableView(this);
TableModel *pModel = new TableModel(this);

// 设置单行选中、最后一列拉伸、表头不高亮、无边框等
pTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
pTableView->horizontalHeader()->setStretchLastSection(true);
pTableView->horizontalHeader()->setHighlightSections(false);
pTableView->verticalHeader()->setVisible(false);
pTableView->setShowGrid(false);
pTableView->setFrameShape(QFrame::NoFrame);
pTableView->setSelectionMode(QAbstractItemView::SingleSelection);
pTableView->setModel(pModel);

// 加载数据、更新界面
QList<FileRecord> recordList;
for (int i = 0; i < 5; ++i)
{
	FileRecord record;
	record.bChecked = false;
	record.strFilePath = QString("E:/Qt/image_%1.png").arg(i + 1);
	recordList.append(record);
}
pModel->updateData(recordList);
<think>我们将在QAbstractTableModel中实现一个带有复选框列的表格模型。关键点如下: 1. 使用Qt.CheckStateRole来存储和获取复选框的状态。 2. 在data()方法中,对于复选框列,返回Qt.Checked或Qt.Unchecked状态。 3. 在setData()方法中,处理复选框的状态改变。 4. 在flags()方法中,为复选框添加Qt.ItemIsUserCheckable标志,使其可被用户勾选。 假设我们的表格有两列:第一列为复选框,第二列为显示文本。 步骤: 1. 定义模型类,继承自QAbstractTableModel。 2. 初始化模型数据(例如,一个列表,每个元素是一个包含字符串和复选框状态的字典或元组)。 3. 实现rowCount(), columnCount()方法。 4. 实现data()方法,根据不同的角色和列返回数据。 5. 实现setData()方法,处理复选框状态的变化。 6. 实现flags()方法,设置每项的标志。 注意:在复选框状态改变时,我们需要发出dataChanged信号。 示例代码:</think># PyQt5 QAbstractTableModel 实现复选框列 在PyQt5中,使用`QAbstractTableModel`实现带有复选框列的表格模型是常见的需求。下面我将详细介绍实现方法,并提供完整示例代码。 ## 实现原理 1. **角色处理**:使用`Qt.ItemDataRole`中的`Qt.CheckStateRole`处理复选框状态 2. **标志设置**:在`flags()`方法中添加`Qt.ItemIsUserCheckable`标志 3. **数据更新**:在`setData()`方法中处理复选框状态变化 4. **状态存储**:使用单独的数据结构存储复选框状态 ## 完整示例代码 ```python import sys from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex, QVariant from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QVBoxLayout, QWidget class CheckableTableModel(QAbstractTableModel): def __init__(self, data, headers, parent=None): super().__init__(parent) self._data = data # 表格数据 self._headers = headers # 表头 self._checked_states = {} # 存储复选框状态 {行索引: 状态} # 初始化所有行为未选中状态 for row in range(len(data)): self._checked_states[row] = Qt.Unchecked 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 QVariant() row = index.row() col = index.column() # 第一列显示复选框 if col == 0: if role == Qt.CheckStateRole: return self._checked_states.get(row, Qt.Unchecked) elif role == Qt.DisplayRole: return "" # 不显示文本 # 其他列显示数据 if role == Qt.DisplayRole: return self._data[row][col-1] # 第一列是复选框,所以减1 return QVariant() def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): return False row = index.row() col = index.column() # 处理复选框状态变化 if col == 0 and role == Qt.CheckStateRole: self._checked_states[row] = 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 return flags def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self._headers[section] return QVariant() def get_checked_rows(self): """获取选中行的索引""" return [row for row, state in self._checked_states.items() if state == Qt.Checked] def get_checked_data(self): """获取选中行的数据""" return [self._data[row] for row in self.get_checked_rows()] class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("带复选框的表格模型") self.setGeometry(100, 100, 600, 400) # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 创建表格视图 self.tableView = QTableView() layout.addWidget(self.tableView) # 准备数据 headers = ["选择", "ID", "名称", "价格"] data = [ [1001, "苹果", 5.99], [1002, "香蕉", 3.49], [1003, "橙子", 4.29], [1004, "葡萄", 8.99], [1005, "西瓜", 12.49] ] # 创建模型并设置 self.model = CheckableTableModel(data, headers) self.tableView.setModel(self.model) # 设置列宽 self.tableView.setColumnWidth(0, 60) # 复选框列 self.tableView.setColumnWidth(1, 100) # ID列 self.tableView.setColumnWidth(2, 200) # 名称列 self.tableView.setColumnWidth(3, 100) # 价格列 # 连接信号 self.model.dataChanged.connect(self.on_data_changed) def on_data_changed(self, topLeft, bottomRight, roles): """当复选框状态改变时触发""" if Qt.CheckStateRole in roles: row = topLeft.row() state = self.model.data(topLeft, Qt.CheckStateRole) item_name = self.model.data(self.model.index(row, 2), Qt.DisplayRole) print(f"行 {row} 状态改变: {'选中' if state == Qt.Checked else '未选中'} - {item_name}") # 打印所有选中项 checked_items = self.model.get_checked_data() print(f"当前选中项: {[item[1] for item in checked_items]}") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` ## 关键实现细节 ### 1. 复选框状态存储 ```python self._checked_states = {} # 使用字典存储每行的复选框状态 ``` ### 2. 数据处理方法 ```python def data(self, index, role=Qt.DisplayRole): # 第一列处理复选框 if col == 0: if role == Qt.CheckStateRole: return self._checked_states.get(row, Qt.Unchecked) elif role == Qt.DisplayRole: return "" # 不显示文本 ``` ### 3. 状态更新方法 ```python def setData(self, index, value, role=Qt.EditRole): if col == 0 and role == Qt.CheckStateRole: self._checked_states[row] = value self.dataChanged.emit(index, index, [Qt.CheckStateRole]) return True ``` ### 4. 标志设置 ```python def flags(self, index): flags = super().flags(index) if index.column() == 0: flags |= Qt.ItemIsUserCheckable # 添加复选框标志 return flags ``` ## 高级功能扩展 ### 1. 全选/全不选功能 ```python def select_all(self, state): """全选或全不选""" for row in range(self.rowCount()): index = self.index(row, 0) self.setData(index, Qt.Checked if state else Qt.Unchecked, Qt.CheckStateRole) ``` ### 2. 三态复选框(部分选中) ```python def data(self, index, role=Qt.DisplayRole): if col == 0 and role == Qt.CheckStateRole: # 实现三态逻辑 if self._is_partially_checked(row): return Qt.PartiallyChecked return self._checked_states.get(row, Qt.Unchecked) ``` ### 3. 复选框与数据关联 ```python def get_checked_data(self): """获取选中行的完整数据""" return [self._data[row] for row in self.get_checked_rows()] ``` ## 性能优化建议 1. **批量更新**: ```python def set_checked_rows(self, rows, state): """批量设置行选中状态""" self.layoutAboutToBeChanged.emit() # 通知布局即将改变 for row in rows: self._checked_states[row] = state self.layoutChanged.emit() # 通知布局已改变 ``` 2. **数据分页**: ```python def load_data_page(self, page, page_size): """分页加载数据""" self.beginResetModel() self._data = self.fetch_data(page, page_size) self.endResetModel() ``` 3. **异步加载**: ```python class DataLoader(QThread): dataLoaded = pyqtSignal(list) def run(self): data = fetch_large_dataset() # 耗时操作 self.dataLoaded.emit(data) ``` ## 常见问题解决 ### 1. 复选框不显示 - 检查`flags()`方法是否添加了`Qt.ItemIsUserCheckable` - 确保`data()`方法对`Qt.CheckStateRole`返回了有效值 ### 2. 状态不更新 - 确认`setData()`方法在修改状态后返回`True` - 确保发出了`dataChanged`信号 ### 3. 性能问题 - 大数据集使用分页加载 - 避免频繁调用`resetModel()`,使用`dataChanged`局部更新 通过以上实现,您可以创建功能完善的带复选框表格模型,并可根据需求进一步扩展功能[^1]。
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一去丶二三里

有收获,再打赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值