MV结构下设置Qt表格的代理(2)
本文介绍了如何在Qt表格中通过代理(Delegate)改变单元格外观,重点实现了一个带复选框的代理ChkDelegate。主要内容包括:1. 代理的工作原理:当代理重写paint函数时,单元格显示由代理控制;否则仍由模型的data()函数决定。2. 实现了一个复选框代理,在第一列显示勾选状态,用户点击时显示复选框控件。3. 模型数据约定用"v"表示勾选,"x"
目录
在上一篇MV结构下设置Qt表格的代理(1)-CSDN博客中,我介绍了如何在表格里添加代理。但是上一篇讲的不完整,没有介绍如何使用代理改变表格的外观。本篇补上。
理论
对于没有代理的表格,其单元格的显示内容由QAbstractItemModel::data()函数的DisplayRole决定。当表格的某行或者某列被代理后:假如这个代理定义了paint函数的行为,则paint函数将决定单元格的显示内容;假如代理没有重写paint函数,则单元格的显示内容仍然由QAbstractItemModel::data()函数的DisplayRole决定。
实例
需求
在上一篇MV结构下设置Qt表格的代理(1)-CSDN博客的基础上,增加一个checkbox的代理ChkDelegate。ChkDelegate代理的列处于第一列,但是并不代表实际数据,只是标记各个行的选中情况。用户通过编辑第一列的checkbox控制该行的选定状态。checkbox被勾选则该行被选中。当某行数据被选中时,第一列显示一个“勾”。
代码
ChkDelegate.cpp
#include "ChkDelegate.h"
#include <QPainter>
ChkDelegate::ChkDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
ChkDelegate::~ChkDelegate()
{
}
QWidget *ChkDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex & index ) const
{
QCheckBox *editor = new QCheckBox(parent);
bool b = index.model()->data(index, Qt::DisplayRole).toBool();
editor->setChecked(b);
return editor;
}
void ChkDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
bool b = index.model()->data(index, Qt::DisplayRole).toBool();
QCheckBox *pChk = static_cast<QCheckBox*>(editor);
pChk->setChecked(b);
}
void ChkDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QCheckBox *pChk = static_cast<QCheckBox*>(editor);
bool b = pChk->isChecked();
model->setData(index, b?"v":"x", Qt::EditRole);
}
void ChkDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
void ChkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data().canConvert<bool>()) {
bool b = index.model()->data(index, Qt::DisplayRole).value<bool>();
QRect rct = option.rect;
//painter->fillRect(rct, Qt::white);
painter->setPen(QPen(Qt::black));
if(b)
{
painter->drawLine(rct.center()-QPoint(rct.width()/4, 0), rct.center()+QPoint(0, rct.height()/4));
painter->drawLine(rct.center()+QPoint(0, rct.height()/4),
rct.center()+QPoint(rct.width()/3, 0 - rct.height()/3));
}
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
Model.cpp
#include "Model.h"
//Model
Model::Model(int iTotalCol, QStringList qstrlstHeader, QList<int> lstNonEditableCols,
QObject *parent) : QAbstractTableModel(parent),
m_lstNonEditableCols(lstNonEditableCols),
m_iTotalCol(iTotalCol), m_qstrlstHeader(qstrlstHeader)
{
}
int Model::rowCount(const QModelIndex &) const
{
return m_data.size();
}
int Model::columnCount(const QModelIndex &) const
{
return m_iTotalCol;
}
QVariant Model::data(const QModelIndex &index, int role) const
{
int i = index.row(), j = index.column();
if ((i >= 0) && (i < m_data.size()))
{
if(0 == j)
{
if(role == Qt::DisplayRole)
{
return (m_data.at(i).at(0).compare("v") == 0);
}
}
else if(m_data.at(i).size() > j)
{
if(role == Qt::DisplayRole)
{
//m_data的类型是QList<QStringList>,每一个QStringList对应表格里一行数据
//m_data.at(i).at(j)对应的就是第i行,第j列的数据
return m_data.at(i).at(j);
}
else if(role == Qt::TextAlignmentRole)
return Qt::AlignCenter;
else
{
}
}
else
{
return QString("");
}
}
else
{
}
return QVariant();
}
QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole)
{
if(orientation == Qt::Horizontal)//横向排列的标题栏,也就是各个列的标题
{
if(m_qstrlstHeader.size() > section)
return m_qstrlstHeader.at(section);
else
{
return QString("");
}
}
else
{
//纵向排列的标题栏,也就是各个行的标题
return QString("");
}
}
else if(role == Qt::TextAlignmentRole)
{
//文字对齐方式设置为居中对齐
return Qt::AlignCenter;
}
else
{
}
return QAbstractTableModel::headerData(section, orientation, role);
}
void Model::vSetData(QList<QStringList> & lstData)
{
m_data.clear();
m_data.append(lstData);
//发出dataChanged信号,界面上才更新表格内容
emit dataChanged(createIndex(0,0), createIndex(rowCount()-1, columnCount()-1));
}
Qt::ItemFlags Model::flags(const QModelIndex &index) const
{
if(m_lstNonEditableCols.contains(index.column()))
//假如index对应的列是不可编辑的列
return QAbstractTableModel::flags(index);
else
//假如index对应的列是可编辑的列,就给它添加ItemIsEditable属性
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
bool Model::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role == Qt::EditRole)
{
//编辑单元格之后,用编辑结果更新对应的m_data
int row = index.row(), col = index.column();
QStringList qstrlst = m_data.at(row);
qstrlst.replace(col, value.toString());
m_data.replace(row, qstrlst);
}
return true;
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_pModel = new Model(5, {"选择","1", "2", "3", "4"}, {1,4});
ui->tableView->setModel(m_pModel);
//setEditTriggers(QTableView::DoubleClicked);注明触发编辑单元格的方式是鼠标双击
ui->tableView->setEditTriggers(QTableView::SelectedClicked);
QList<QStringList> lstqstrlst;
lstqstrlst<<QStringList{"x", "a", "b", "c", "d"};
//向表格中注入数据
m_pModel->vSetData(lstqstrlst);
m_spChkDelegate.reset(new ChkDelegate(this));
ui->tableView->setItemDelegateForColumn(0, m_spChkDelegate.data());
m_scpCmbDelg.reset(new ComboDelegate({"a","b","c"}, this));
//表格第2列采用combobox代理(其实是第3列,因为从0开始)
ui->tableView->setItemDelegateForColumn(2, m_scpCmbDelg.data());
m_scpEdtDelg.reset(new EdtDelegate(0,10, this));
//表格第3列采用lineedit代理
ui->tableView->setItemDelegateForColumn(3, m_scpEdtDelg.data());
}
MainWindow::~MainWindow()
{
delete ui;
}
解释
1 mainwindow.cpp构造函数的下面语句
ui->tableView->setItemDelegateForColumn(0, m_spChkDelegate.data());
指定ChkDelegate代理第一列。当用户选定第一列的某个单元格后,再次点击将触发编辑该单元格的功能。触发后,该单元格内将显示一个勾选框。在不触发的情况下,由于ChkDelegate已经定义了paint函数,所以第一列的外观,在未触发编辑的情况下,将由paint函数定义,而不是由Model::data决定。
2 ChkDelegate::paint函数从Model::data函数获取了第一列的选择情况,进而决定是否画“勾”:
if (index.data().canConvert<bool>()) {
bool b = index.model()->data(index, Qt::DisplayRole).value<bool>();
.....
}
3 与第一篇MV结构下设置Qt表格的代理(1)-CSDN博客不同,Model::data不再仅仅返回QString
类型,也可以返回bool类型(仅在列号为0,即第一列的情况下返回bool):
QVariant Model::data(const QModelIndex &index, int role) const
{
int i = index.row(), j = index.column();
if ((i >= 0) && (i < m_data.size()))
{
if(0 == j)
{
if(role == Qt::DisplayRole)
{
return (m_data.at(i).at(0).compare("v") == 0);
}
}
....
}
4 ChkDelegate完成编辑后,触发下面语句,更新Model::m_data的内容。这里,由于m_data是QStringList类型,所以不能直接储存bool变量。m_data的第一列约定用"v"代表勾选,“x”代表不勾选。
void ChkDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QCheckBox *pChk = static_cast<QCheckBox*>(editor);
bool b = pChk->isChecked();
model->setData(index, b?"v":"x", Qt::EditRole);
}
bool Model::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role == Qt::EditRole)
{
//编辑单元格之后,用编辑结果更新对应的m_data
int row = index.row(), col = index.column();
QStringList qstrlst = m_data.at(row);
qstrlst.replace(col, value.toString());
m_data.replace(row, qstrlst);
}
return true;
}
5 每当用户触发编辑第一列的功能时,ChkDelegate::createEditor函数都被触发。此时checkbox需要显示出上一次编辑的勾选情况,所以要从Model中调用data函数:
QWidget *ChkDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex & index ) const
{
QCheckBox *editor = new QCheckBox(parent);
bool b = index.model()->data(index, Qt::DisplayRole).toBool();
editor->setChecked(b);
return editor;
}
效果
更多推荐



所有评论(0)