【Qt6】列表模型——便捷类型

打印 上一主题 下一主题

主题 904|帖子 904|积分 2712

前一篇水文中,老周演示了 QAbstractItemModel 抽象类的继承方法。其实,在 Qt 的库里面,QAbstractItemModel 类也派生了两个基类,能让开发者继承起来【稍稍】轻松一些。
这两个类是 QAbstractListModel 和 QAbstractTableModel。

  • QAbstractListModel类专门用来实现一维列表结构模型的。它实现了 index、parent 等方法,并且把 columnCount 方法变成了私有成员(一维列表不需要它)。继承时直接实现 rowCount、data、setData 这几个方法即可;
  • QAbstractTableModel类专门用来实现二维表结构的模型。它实现了 index、parent 等方法。继承时咱们要实现 rowCount、columnCount、data、setData 等方法。
虽然它帮咱们实现了一些成员,但实际上也省不了多功夫的。下面咱们用 QAbstractListModel 举例,和上篇中的一样,操作一个 QList 数据。毕竟一维的比较简单,演示出来大伙都容易懂。
  1. // 头文件
  2. #ifndef LIST_H
  3. #define LIST_H
  4. #include <QAbstractListModel>
  5. #include <QList>
  6. class CustListModel: public QAbstractListModel
  7. {
  8.     Q_OBJECT
  9. public:
  10.     explicit CustListModel(QObject* parent = nullptr);
  11.     ~CustListModel();
  12.     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
  13.     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
  14.     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
  15.     bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
  16.     bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
  17.     Qt::ItemFlags flags(const QModelIndex &index) const override;
  18. private:
  19.     // 私有,存数据用
  20.     QList<int> m_list;
  21. };
  22. #endif
复制代码
先弄 rowCount 方法,返回总行数,这个简单一点。
  1. int CustListModel::rowCount(const QModelIndex &parent) const
  2. {
  3.     if(parent.isValid())
  4.     {
  5.         return 0;
  6.     }
  7.     return m_list.size();
  8. }
复制代码
实现 data 方法,获取数据时用。
  1. QVariant CustListModel::data(const QModelIndex &index, int role) const
  2. {
  3.     if( role == Qt::DisplayRole || role == Qt::EditRole)
  4.     {
  5.         if(!index.isValid())
  6.         {
  7.             return QVariant();
  8.         }
  9.         // 获取索引
  10.         int i = index.row();
  11.         // 返回指定索引处的值
  12.         return m_list.at(i);
  13.     }
  14.     return QVariant();
  15. }
复制代码
要返回 QList 中指定的元素,用 at 方法,传递索引给它即可。
实现 setData 方法,编辑结束后用于更新数据的。
  1. bool CustListModel::setData(const QModelIndex &index, const QVariant &value, int role)
  2. {
  3.     if(!index.isValid())
  4.         return false;
  5.     if(role == Qt::EditRole || role == Qt::DisplayRole)
  6.     {
  7.         // 解包数据
  8.         bool ok;
  9.         int val = value.toInt(&ok);
  10.         if(ok)
  11.         {
  12.             // 设置值
  13.             m_list.replace(index.row(), val);<br>
复制代码
              // 发出信号
              emit dataChanged(index,index,{role});
  1. return true;
  2.         }
  3.     }
  4.     return false;
  5. }
复制代码
要修改某个索引处的值,用 replace 方法替换。
实现 flags 方法,表明该模型的列表项支持交互、编辑、被选择。
  1. Qt::ItemFlags CustListModel::flags(const QModelIndex &index) const
  2. {
  3.     return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
  4. }
复制代码
ItemIsEnabled 表明列表项是活动的,用户可操作的;ItemIsEditable 表明列表项可编辑;ItemIsSelectable 表示列表项可以选择。
 
下面这两个方法是有点麻烦的,这里先介绍一下。
  1. bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
复制代码
row 参数表示要插入元素的索引,count 表示插入元素个数,插入元素后,原来的元素向后移动。例如:A、B、C、D,现在我要在B处插入两个元素。那么 row = 1,count = 2,B 处放入 E、F,B、C、D 向后移,变成 A、E、F、B、C、D。
然后,删除元素的方法也类似。
  1. bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
复制代码
row 是要删除的索引,count 是连续要删掉的元素个数。
insertRows 和 removeRows 方法的返回值含义相同:成功返回 true,失败返回 false。
好,现在上代码。
  1. bool CustListModel::insertRows(int row, int count, const QModelIndex &parent)
  2. {
  3.     if(parent.isValid())
  4.         return false;
  5.     // 注意这里!!
  6.     beginInsertRows(parent, row, row + count - 1);
  7.     // 开始插入
  8.     m_list.insert(row, count, 0);
  9.     // 注意这里!!
  10.     endInsertRows();
  11.     return true;
  12. }
  13. bool CustListModel::removeRows(int row, int count, const QModelIndex &parent)
  14. {
  15.     if(parent.isValid())
  16.         return false;
  17.     // 注意这里!!!
  18.     beginRemoveRows(parent, row, row + count - 1);
  19.     // 删除
  20.     m_list.remove(row, count);
  21.     // 注意这里!!!
  22.     endRemoveRows();
  23.     return true;
  24. }
复制代码
在插入数据前必须调用 beginInsertRows 方法。注意这个方法的参数含义和 insertRows 有些不一样。
  1. void beginInsertRows(const QModelIndex &parent, int first, int last)
复制代码
insertRows 方法是指定开始索引 ,然后连续加入多少个元素,而 beginInsertRows 方法是你插入元素后,它们在列表中的开始索引和结束索引。如 A、B、C,在B处插入两个元素。开始索引是 1,结束索引是 2,即结束索引的计算方法是: endIndex = startIndex + count -1。插入元素结束后必须调用 endInsertRows 方法。这一对方法的作用是让用户界面上的视图(如 QListView、QTableView 等)能及时做出响应。
同理,在删除元素时,beginRemoveRows 和 endRemoveRows 方法也必须调用。beginRemoveRows 方法的参数与 beginInsertRows 方法一样,也是索引的起止值。即要删除元素的起始索引,和被删除的最后一个元素的索引。如1、2、3、4,要删除2、3、4,那么起始索引是 1,结束索引 3。
模型已完工,下面做个界面试一试。
  1. int main(int argc, char** argv)
  2. {
  3.     QApplication myapp(argc, argv);
  4.     // 窗口
  5.     QWidget *window = new QWidget;
  6.     // 布局
  7.     QGridLayout *layout = new QGridLayout;
  8.     window->setLayout(layout);
  9.     // 列表视图
  10.     QListView* view = new QListView(window);
  11.     // 实例化模型
  12.     CustListModel* model = new CustListModel(window);
  13.     // 设置到视图中
  14.     view->setModel(model);
  15.     layout->addWidget(view, 0, 0, 3, 1);
  16.     // 按钮
  17.     QPushButton* btnAdd=new QPushButton("新增", window);
  18.     QPushButton* btnDel = new QPushButton("移除", window);
  19.     QPushButton* btnShowAll = new QPushButton("显示列表", window);
  20.     layout->addWidget(btnAdd, 0, 1);
  21.     layout->addWidget(btnDel, 1, 1);
  22.     layout->addWidget(btnShowAll, 2, 1);
  23.     layout->setColumnStretch(0, 1);
  24.     // 连接clicked信号
  25.     QObject::connect(
  26.         btnAdd,
  27.         &QPushButton::clicked,
  28.         [&view, &model, &window]()
  29.         {
  30.             bool res;
  31.             int val = QInputDialog::getInt(
  32.                 window,             //父窗口
  33.                 "输入",              //窗口标题
  34.                 "请输入整数:",       //提示文本
  35.                 0,                   //默认值
  36.                 0,                  //最小值
  37.                 1000,               //最大值
  38.                 1,                  //步长值
  39.                 &res                 //表示操作结果
  40.             );
  41.             if(!res)
  42.                 return;
  43.             // 索引为count
  44.             int i = model->rowCount();
  45.             // 添加一项
  46.             model->insertRow(i);
  47.             // 获取新索引
  48.             QModelIndex newIndex = model->index(i);
  49.             // 设置此项的值
  50.             model->setData(newIndex, QVariant(val), Qt::DisplayRole);
  51.         }
  52.     );
  53.     QObject::connect(
  54.         btnDel,
  55.         &QPushButton::clicked,
  56.         [&window, &view, &model]()
  57.         {
  58.             // 当前项
  59.             QModelIndex curri = view->currentIndex();
  60.             if(!curri.isValid())
  61.                 return;
  62.             // 删除
  63.             model->removeRow(curri.row());
  64.         }
  65.     );
  66.     QObject::connect(
  67.         btnShowAll,
  68.         &QPushButton::clicked,
  69.         [&model, &window]()
  70.         {
  71.             // 获取总数
  72.             int c = model->rowCount();
  73.             QString msg;
  74.             for(int x = 0; x < c; x++)
  75.             {
  76.                 // 获取值
  77.                 QVariant value = model->data(model->index(x), Qt::DisplayRole);
  78.                 if(value.isValid())
  79.                 {
  80.                     int n = qvariant_cast<int>(value);
  81.                     msg += QString(" %1").arg(n);
  82.                 }
  83.             }
  84.             QMessageBox::information(window,"提示",msg);
  85.         }
  86.     );
  87.     // 标题
  88.     window->setWindowTitle("小型列表");
  89.     // 显示
  90.     window->show();
  91.     return QApplication::exec();
  92. }
复制代码
QListView 组件用来显示模型中的数据。三个按钮分别用来添加、删除和显示列表项。最后一个按钮显示模型中的所有数据,它的作用是验证模型是否工作正常。
“新增”按钮被点击后,先通过 QInputDialog.getInt 静态方法,弹出一个输入内容的对话框,然后把输入的值添加到模型中。模型中添加元素用的就是 insertRow 方法——只插入一个元素,它调用了咱们上面实现的 insertRows 方法,只是把 count 参数设置为 1。这里我实现的是新元素总是追加在列表最后,即新元素的行号等于 rowCount。
删除元素时,QListView 组件的 currentIndex 方法返回当前选中项的索引。在单选模式下,99.9% 被选中的项是等同于当前项的。多选模式下就不好说了,所以,如果需要,可以访问 selectionModel 方法,再通过 selectedIndexes 方法获得被选项的列表。
看看效果。

 
QAbstractTableModel 类的用法也是差不多的,只不过要实现行和列的读写。
这两个类哪怕帮咱们实现了一些代码,但用起来还是麻烦,要是有不需要继承、开箱就用的类就会好多了。Qt 还真的提供了这样的类型。
首先说的是 QStringListModel,这个就是针对 QStringList 类的——字符串列表。QStringList 其实就是 QList。这个 QStringListModel 模型就是以字符串列表为数据源的。不需要继承(除非你需要自定义),直接可用。
咱们演练一下。
  1. // 头文件
  2. #ifndef DEMO_H
  3. #define DEMO_H
  4. #include <QWidget>
  5. #include <QPushButton>
  6. #include <QListView>
  7. #include <QVBoxLayout>
  8. class MyWindow : public QWidget
  9. {
  10.     Q_OBJECT
  11. public:
  12.     explicit MyWindow(QWidget* parent = nullptr);
  13.     ~MyWindow();
  14. private:
  15.     QVBoxLayout* m_layout;
  16.     QPushButton* m_btn;
  17.     QListView* m_view;
  18.     // 用于连接clicked信号
  19.     void onClicked();
  20. };
  21. #endif
复制代码
三个组件:按钮被点击后加载数据;QListView 是视图组件,显示数据;QVBoxLayout 是布局用的,界面元素沿垂直方向排列(纵向)。公共成员就是构造函数和析构函数。析构是空白的,实现构造函数即可。
  1. #include "../include/demo.h"
  2. #include <QStringList>
  3. #include <QStringListModel>
  4. MyWindow::MyWindow(QWidget * parent)
  5.     : QWidget::QWidget(parent)
  6. {
  7.     m_layout = new QVBoxLayout();
  8.     setLayout(m_layout);
  9.     // 按钮
  10.     m_btn = new QPushButton("加载数据", this);
  11.     m_layout->addWidget(m_btn);
  12.     // 视图
  13.     m_view = new QListView(this);
  14.     m_layout->addWidget(m_view, 1);
  15.     // 连接信号
  16.     connect(m_btn, &QPushButton::clicked, this, &MyWindow::onClicked);
  17. }
  18. MyWindow::~MyWindow()
  19. {
  20. }
复制代码
下面是重点,实现 onClicked 成员方法,构建数据并在视图中显示。
  1. void MyWindow::onClicked()
  2. {
  3.     // 创建字符串列表
  4.     QStringList list;
  5.     list << "手榴弹" << "地雷" << "鱼雷" << "燃烧弹";
  6.     // 创建模型实例
  7.     QStringListModel *model = new QStringListModel(list, this);
  8.     // 设置给视图
  9.     m_view->setModel(model);
  10. }
复制代码
setRowCount、setColumnCount 方法可以不调用,模型会根据你放的数据调整。有两点得注意:
1、这里每个 QStandardItem 代表的是一个单元格的数据,而不是一行;
2、QStandardItem 类型的变量要声明为指针类型,并且要在堆上分配;不能用栈分配,会显示空白(提前析构了)。
然后,main 函数就更简单了。
  1. int main(int argc, char** argv)
  2. {
  3.     QApplication app(argc, argv);
  4.     MyWindow* win = new MyWindow();
  5.     win->setWindowTitle("示例");
  6.     win->resize(320, 275);
  7.     win->show();
  8.     return QApplication::exec();
  9. }
复制代码
结果如下图:

 第一行和第四行,咱们修改过背景色和文本颜色。
  1. QStandardItem();
  2. explicit QStandardItem(const QString &text);
  3. QStandardItem(const QIcon &icon, const QString &text);
  4. explicit QStandardItem(int rows, int columns = 1);
复制代码
 
好了,今天就聊到这儿,QStandardItemModel 还有其他耍法,尤其是在树形结构上。咱们下一期继续扯。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

愛在花開的季節

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表