Qt Model/View 学习(4) - 实现自己的QAbstractTableModel类(支持显示与修改)


0. 前言

可算到了这一篇了!

上一篇文章中把Qt::ItemDataRole介绍完了,有了前面2篇文章的铺垫,现在终于能够开始动手实现自己的Model类了。

我们在这篇文章中提到Model相关类主要有QAbstractItemModelQAbstractListModelQAbstractTableModel这三个,由于我们已经学习过QModelIndex了,实现的难度区别不大,本文就选择从QAbstractTableModel派生自己的Model类。

话不多说,直奔主题。

系列文章回顾
Qt Model/View 学习(1) - 是什么和为什么?
Qt Model/View 学习(2) - QModelIndex索引模型数据
Qt Model/View 学习(3) - 索引来一堆东西,究竟取谁(ItemDataRole)?


1. Data设计

为了(搞节目效果 )强调Data确实可以很散漫,设计如下数据结构:

//    数据Data,共20个数据
// 10个标题
QStringList titles = {"飞机重量", "经度", "纬度", "高度", "飞行次数",
                        "城市", "宠物名称", "姓名", "身高", "体重"};
// 10个数据
struct GPS
{
// 经纬高
    double lng=108.11, lat=37.11, alt=500.11;
};
struct Pilot
{
    QString name="Tom";	// 姓名
    QString city="北京";	// 城市
    double stature=180.0;	// 身高
    double weight=66.5;	// 体重
    unsigned flyTimes=52;	// 飞行次数
};

GPS pos;	// GPS对象
Pilot person;	// 飞行员对象
QString petName="Jerry";	// 宠物名称
double planeWeight=10000;  // 飞机重量

总共20个数据,有QString、double、unsigned等各种类型。在我们的Model类中将被解析为4*5的表格。

此处为了便利就直接将数据放到Model类的私有区了,在实际程序设计中可以考虑将变量整体封装成类/结构,然后将封装的对象指针传入Model类中,这样可实现将ModelData分开。


2. Model类设计

2.1 数据显示与对齐、字体修改

先找到QAbstractTableModel中关于Subclassing的部分:
在这里插入图片描述
官方文档告诉我们:必须重新实现rowCount()、columnCount()、data()函数。

那我们从QAbstractTableModel派生出自己的MyTableModel类,简简单单实现一下这几个函数。如果对于QModelIndexQt::DisplayRole还不太熟悉,那就快去看看之前的文章吧~

virtual int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
    {
//        解析为4*5矩阵,行数为4
        return 4;
    }
virtual int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
{

// 解析为4*5矩阵,列数为5
return 5;
}

// 核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
switch(role)
{
// 显示数据用
case Qt::DisplayRole:
return getTableDisplayData(index);
break;

// 其余不处理
default:
return QVariant();
break;
}
}

其中的getTableDisplayData()函数声明如下,由于只是将数据按矩阵格式对应了一下,用了一堆if-else-switch就不贴全源代码了……

QVariant getTableDisplayData(const QModelIndex& index) const
    {
/* 暂定显示形式为:第1行和第3行显示标题,第2行和第4行显示数值
 * 姓名 身高     体重  城市  宠物名称
 *
 * 飞机重量 飞行次数  经度  纬度  高度
 *
 */
//        稍作判断
        if(!index.isValid() || index.column() >= columnCount() || index.row() >= rowCount())
            return QVariant();

// 数据模型映射

// 第一行显示:姓名 身高 体重 城市 宠物名称
if(index.row()==0)
{
//titles = {“飞机重量”, “经度”, “纬度”, “高度”, “飞行次数”, “城市”, “宠物名称”, “姓名”, “身高”, “体重”};
switch(index.column())
{
case 0: return titles[7];
break;

        case 1: return titles[8];
            break;

        case 2: return titles[9];
            break;

        case 3: return titles[5];
            break;

        case 4: return titles[6];
            break;

        default: return QVariant();
            break;
        }
    }
    //后续类似......
}

main()函数中添加以下代码以使用咱们的MyTableModel类:

//    使用TableView控件
    QTableView tbl;
//    声明自己的Model对象
    MyTableModel model;

// 关键接口:设置View对应的Model
tbl.setModel(&model);

tbl.resize(600,200);
tbl.show();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Ctrl+R运行起来看看效果:
在这里插入图片描述

显示数据问题不大,但作为一名轻度强迫症,还想实现几点显示效果:

  1. 数据都居中显示;
  2. 第1行和第3行作为标题行,字体加粗一下;

如果有好好看上一篇关于ItemDataRole的文章,这两条应该是很容易实现的,只需要在data()函数中添加对齐字体Role处理代码如下:

//    核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        switch(role)
        {
//        显示数据用
        case Qt::DisplayRole:
            return getTableDisplayData(index);
            break;

// 对齐处理
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
break;

// 字体处理
case Qt::FontRole:
{
QFont font;
// row()0,2时为标题行,需要加粗
if(index.row()%2
0) font.setBold(true);
return font;
}
break;

// 其余不处理
default:
return QVariant();
break;
}
}

这样看起来就稍微舒服一些了:
在这里插入图片描述

但实话说,还是很难看,个人还会想要达到以下效果:

  1. 把行列的表头给去了;
  2. 每列的宽度设置为刚刚好;
  3. 窗口大小刚好放下表格,而不像现在显示不全;

不过遗憾地,这几条都不是在MyTableModel中能够处理的,而是对QTableView进行操作。避免跑题,此处就不贴代码了,只提思路,具体代码可以参考附件。

  1. QTableView::horizontalHeader()获取水平表头,调用QHeaderView::hide()函数将其隐藏;垂直表头处理类似;
  2. QTableView::resizeColumnsToContents()函数,自适应列宽;
  3. 计算自适应之后的表格总大小,使用QTableView::resize()函数设置控件大小,再使用QTableView::setHorizontalScrollBarPolicy()隐藏水平滚动条,类似地,垂直滚动条也隐藏;

View类将在下一篇文章进行系统讲解。

最后显示的结果如下,比较符合强迫症审美了~

在这里插入图片描述

2.2 数据修改

继续看QAbstractTableModel中关于Subclassing的部分:
在这里插入图片描述
提到:可编辑的模型需要实现setData()flags()函数,后者返回值需包含Qt::ItemIsEditable

看起来都不难,那就继续简单实现一下:


//    可编辑模型需要实现flags(),返回值包含Qt::ItemIsEditable
    virtual Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
    }
//    编辑模型的重点函数
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
    {
        switch(role)
        {
        case Qt::EditRole:
//            修改成功时需要触发dataChanged()信号,返回true;修改失败返回false
//            第0、2行为标题行,不允许修改
            if(!index.isValid() || index.row()%2==0) return false;
            return editTableData(index, value);
            break;
    default:
        return false;
        break;
    }
}

其中editTableData()函数和getTableDisplayData()类似,为了完成这样稀碎的数据的映射用了一堆switch-case比较无聊,此处只贴上部分源代码:

bool editTableData(const QModelIndex &index, const QVariant &value)
    {
//        根据传入index,修改表格上对应位置的数据

// 稍作判断
if(!index.isValid() || index.column() >= columnCount() || index.row() >= rowCount()) return false;

// 修改结果
bool ret = true;
switch(index.row())
{

// 只处理row()==1,3

    // 姓名 身高     体重  城市  宠物名称
    case 1:
        switch(index.column())
        {
        case 0:
            person.name = value.toString();
            break;

        case 1:
            person.stature = value.toDouble();
            break;

        case 2:
            person.weight = value.toDouble();
            break;

        case 3:
            person.city = value.toString();
            break;

        case 4:
            petName = value.toString();
            break;

// 其余列未修改
default:
ret = false;
break;
}

        break;
    //此处省略case 3的修改
    
    default:
        ret = false;
        break;
    }

// 修改成功则触发信号
if(ret) emit dataChanged(index, index);

    return ret;
}

Ctrl+R运行试一下,已经可以修改内容了:
在这里插入图片描述


3. 小结

  1. 设计了一堆稀碎的原始数据,说实话添加了很多编码麻烦,但是在Model中正确处理后,都没啥问题;所以在设计数据结构的时候,最好能有一定的解析规律
  2. QAbstractTableModel派生出自己的MyTableModel类,通过实现data()、rowCount()、columnCount()接口,完成数据的显示;
  3. 实现flags()、setData()接口后,完成表格编辑功能;
  4. 使用QAbstractItemView::setModel()接口,将自定义的Model对象指针传入,完成ModelView的联动;
  5. 项目的源代码在此处下载。

如有错误欢迎指正,共同进步~


今天你学废了吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值