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();

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. 项目的源代码在此处下载。

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


今天你学废了吗?

`QAbstractTableModel` 是 PyQt 中的一个模型,用于管理二维表格数据。 `QStandardItemModel` 不同,`QAbstractTableModel` 可以管理任意型的表格数据,且支持自定义行列数和数据型。通过继承 `QAbstractTableModel` ,我们可以自定义一个模型,提供相应的数据操作接口。 在 `QAbstractTableModel` 中,我们需要实现以下几个虚函数: - `rowCount()`:获取行数; - `columnCount()`:获取列数; - `data()`:获取指定单元格的数据; - `setData()`:设置指定单元格的数据; - `headerData()`:获取表头数据; - `flags()`:获取单元格的标志位。 例如,以下代码展示了如何创建一个自定义的 `MyTableModel` 模型,并实现上述虚函数: ``` from PyQt5.QtCore import Qt, QModelIndex, QVariant from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import QTableView, QApplication from PyQt5.QtCore import QAbstractTableModel class MyTableModel(QAbstractTableModel): def __init__(self, data): super().__init__() self._data = data def rowCount(self, parent=QModelIndex()): return len(self._data) def columnCount(self, parent=QModelIndex()): if len(self._data) > 0: return len(self._data[0]) else: return 0 def data(self, index, role): if role == Qt.DisplayRole: return str(self._data[index.row()][index.column()]) else: return QVariant() def headerData(self, section, orientation, role): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return "Column %d" % section else: return "Row %d" % section else: return QVariant() def flags(self, index): return Qt.ItemIsEnabled | Qt.ItemIsSelectable # 创建一个二维数据 data = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] # 创建一个自定义的 MyTableModel 模型对象 model = MyTableModel(data) # 创建一个 QTableView 视图对象,并将 MyTableModel 模型对象设置到上面 table_view = QTableView() table_view.setModel(model) # 显示视图对象 app = QApplication([]) table_view.show() app.exec_() ``` 这样,我们就创建了一个自定义的 `MyTableModel` 模型,并实现了 `rowCount()`、`columnCount()`、`data()`、`headerData()` 和 `flags()` 等虚函数。可以将这个模型对象设置到 `QTableView` 视图对象上,从而实现表格数据的展示。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值