目录

理论

实例

需求

代码

ChkDelegate.cpp

Model.cpp

mainwindow.cpp

解释

效果


在上一篇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;
}

效果

QCheckBox代理效果展示_哔哩哔哩_bilibili

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐