Qt模型/视图原理(2):自定义模型(QAbstractItemModel)

Qt模型/视图原理(2):自定义模型(QAbstractItemModel)

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

本文出自本人原创著作《Qt5.10 GUI完全参考手册》网盘地址:
https://pan.baidu.com/s/1iqagt4SEC8PUYx6t3ku39Q
《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。

自定义模型至少需要实现QAbstractItemModel类中的以下5个纯虚函数

columnCout()、rowCount()、index()、parent()、data()

为了能添加自已的数据到模型中,通常还需要重新实现setData()函数,若不重新实现setData()则无法向模型中添加数据。

自定义模型的基本原理及步骤如下
①、数据:实际数据可使用QList、数组、整型、或单独的一个类来保存,数据可存放在模型中,也可存放在文件等其他地方。
②、columnCout()、rowCount()、index()、parent()这4个函数用于共同设计模型的结构,因为使用索引表示模型中的某个数据项的位置,因此设计模型索引的结构就是设计模型的结构。具何如下
 行数和列数的设计:比如对于3行4列的表格结构,columnCout()应返回4,rowCount()应返回3;对于列表结构,则因为列表只需要1列,所以columncout()应总是返回1,rowCount()返回该列表的行数;对于树形结构模型,则更复杂,需要根据当前父节点的情况进行判断,以返回该父节点拥有的列数和行数。
 parent()函数(父模型索引)的设计:因为表格结构中的所有单元格都属于同一个父索引之下,所以可把所有单元格都视为顶级节点,因此他们的父索引可以以无效模型索引作为父索引,因此parent()可以返回一个无效模型索引;对于列表结构的模型,同样只需返回一个无效模型索引即可;对树形结构模型,此步骤比较复杂,可以通过获取当前节点的父节点及其行号和列号,然后使用createIndex()创建该父节点的索引。
 index()函数的设计:该函数用于为模型中的每个数据项创建索引,创建索引需要使用createIndex()函数,对于表格结构,只需向createIndex()函数传递当前数据项所在的行号、列号及使用的数据的指针即可;对于列表结构,则列号始终为0,其余同表格结构;对于树形结构,需要向该函数传递当前数据项位于父索引中的行号、列号及使用的数据的指针。
③、data()函数的返回值决定了视图上应显示的数据,也就是说在界面上用户看到的数据是由该函数返回的,若返回不当的值,则数据无法正常显示在视图上,下面以使用内置的标准视图类为例来讲解怎样设计此函数。data()函数会被视图类调用多次,视图每次都会向data传递一个不同的role(角色)参数值,然后视图根据data返回的值,设置该role的数据, 因此在设计data函数的返回值时,需要根据role的不同值返回不同的数据,以使视图正确的显示。

下面为需要用到的各函数的原型及说明
在这里插入图片描述
在这里插入图片描述

示例8.4:自定义表格模型
注:本小节使用的QList的几个成员函数,请参阅帮助文档,另外QModelIndexList是使用typedef重命名后的QList。
//m.h文件内容
#ifndef M_H
#define M_H
#include<QtWidgets>
#include <iostream>
using namespace std;
class D:public QAbstractItemModel{  //继承抽象类QAbstractItemModel
    public:
    int col,rw;   		//表格模型的列数和行数
    QList<QVariant> s1;	//这是模型管理的数据
    QList<int> rol;    	//存储数据的角色
    //读者可把以一两项单独封装在一个类(比如xxxItem)中进行管理,Qt内置模型就是这样处理的
//构造函数表示创建一个i行j列的表格模型
    D(int i, int j):rw(i),col(j){	for(int k=0;k<col*rw;k++){ s1<<QVariant();	rol<<-1;} }
//①、返回表格模型的行数
int rowCount(const QModelIndex &parent = QModelIndex ()) const{return rw;	}
//②、返回表格模型的列数
int columnCount(const QModelIndex &parent =QModelIndex ()) const{return col;}
//③、返回表格模型的父索引,因为所有单元格都是顶级节点,所以使用无效节点作为父节点
QModelIndex parent(const QModelIndex &index) const{      return QModelIndex();}
//④、为每个单元格创建一个唯一的索引
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex ()) const
{	cout<<"index"<<endl;          //测试语句
    		//其他模型可能需要判断传递进来的索引是否有效。
    		//if(!hasIndex(row,column,parent))return QModelIndex();
/*本示例仅需简单的为传递进来的单元格创建一个索引即可,注意第3个参数的使用(请参阅createIndex()的原型),由此处可见,第3个参数其实质就是指指向模型实际所管理的数据。*/
    		return createIndex(row, column, (void*)&(s1.at(row*column+column)));     }
//⑤、返回视图上显示的数据,该函数会被视图多次调用(注:其他虚函数同样会被Qt调用多次)
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const{
//测试用,读者可看到每次调用时Qt内部传递进来的role(角色)的值。
cout<<"data="<<role<<endl;
    		int i=index.row()*col+index.column();  //单元格所在数据s1中的位置
//以下对不同角色返回的数据会被视图使用,以正确的显示在其单元格中。
/*对于QListView和QTreeView必须对所在节点设置大小,否则这两个视图不会显示任何内容(因为节点大小为0)*/
    		//if(role==Qt::SizeHintRole)return QSize(55,55);
/*设置角色CheckStateRole的数据。本示例返回一个无效的QVariant作为该角色的数据, 若返回有效值,会使单元格的左侧显示一个复选框。*/
    		if(role==Qt::CheckStateRole)return QVariant();
    		//设置单元格中数据的对齐方式。本示例为左侧垂直居中对齐
    		if(role==Qt::TextAlignmentRole)   return Qt::AlignLeft|Qt::AlignVCenter;
   		//设置角色DecorationRole(图片)的数据
    		if(role==Qt::DecorationRole)
        		//若用户设置了DecorationRole角色的数据,则返回用户为该单元格设置的数据。
        		if(rol.at(i)==Qt::DecorationRole)          return s1.at(i);
//若用户为角色EditRole或DisplayRole角色设置了数据,则返回用户为该单元格设置的数据。
    		if(role==Qt::EditRole|role==Qt::DisplayRole)
if(rol.at(i)==Qt::EditRole|rol.at(i)==Qt::DisplayRole)	return s1.at(i);
//其余角色使用无效数据
    		return QVariant();		}	//data()结束
//⑥、重载setData以使用户可以向模型中添加数据
  	bool setData(const QModelIndex &index,const QVariant &value, int role=Qt :: EditRole)
  	{/*使用数据value和角色role分别替换列表s1和rol中原有的值,使用replace便于下一个示例(拖放)的使用。*/
  		s1.replace(index.row()*col+index.column(),value);
  		rol.replace(index.row()*col+index.column(),role);
     	emit dataChanged(index, index);    	//数据改变后,需要发送此信号。
     	return true;   } 		};			//返回true,表示数据设置成功。
#endif // M_H


//m.cpp文件内容
#include "m.h"
int main(int argc, char *argv[]){    QApplication aa(argc,argv);
    D *d=new D(3,3);   					//创建一个3行3列的表格模型
    QTableView *pv2=new QTableView;   	//使用表格视图来显示以上模型管理的数据
/*读者也可使用以下视图来显示模型d中的数据,但要去掉data()中对if(role==Qt::SizeHintRole)的注释,还要注意,若使用树形视图显示,有可能会使程序崩溃,因为本示例未完整的实现树形模型结构*/
    //QListView *pv2=new QListView;
//QTreeView *pv2=new QTreeView;

//向模型中添加数据
    d->setData(d->index(0,0,QModelIndex()),"111",Qt::DisplayRole);
    d->setData(d->index(1,0,QModelIndex()),222);
    d->setData(d->index(1,1,QModelIndex()),333);
    d->setData(d->index(2,1,QModelIndex()),QIcon("F:/1i.png"),Qt::DecorationRole);
    pv2->setModel(d);    pv2->resize(333,222);    pv2->show();    return aa.exec();  }

运行结果及说明,见图8-16,由图可见,数据显示正确
在这里插入图片描述

8.2.3 拖放支持及MIME类型

拖放操作分为拖动(Drag)和放下或放置(Drop)两种操作,当拖动时,需要把拖动的数据进行存储,存储数据这一步骤称为编码,数据以MIME类型(见6.6节)存储为QMimeData类型的对象(称为放置数据),当执行放下操作时,需要对放置数据进行处理,若需要使用这些数据,则需要把存储的数据读取出来(即解码),然后进行处理;当然,若不需要这些放置数据,也可以直接丢弃。
1、自定义拖放操作的步骤
(1)、启用视图的拖放支持
标准视图自动支持内部拖放,默认情况下,这些视图的拖放功能未启用,要启用视图的拖放支持,需进行以下设置:
 设置视图的dragEnabled属性为true以启用项目拖放。
 拖放的模式:即视图是否支持拖放,是否接受放置数据,是否接受来自自身的移动操作等。需要设置视图的dragDropMode属性。
 若要允许用户在视图内放置内部或外部项目,需将视图的viewport()的acceptDrops属性设置为true。
 若要向用户显示拖动的项目放置位置(通常该位置会以不同的形式显示,比如以阴影形式显示等),此时需设置视图的showDropIndicator属性。
 下面为具体的代码

    	v->setDragEnabled(1);           		//启用视图的拖放支持
       	v->setAcceptDrops(1); 				//接受放置数据
       	v->setDropIndicatorShown(1);		//显示放置位置
v->setDragDropMode(QAbstractItemView::DragDrop);   //设置拖放模式为支持拖放

(2)、启用数据项的拖放支持
重新实现QAbstractItemModel::flags()函数以提供合适的标志来指示哪些项目可以被拖动,哪些项目将接受放置(Drop)。
(3)、编码数据
重新实现QAbstractItemModel::mimeData()函数,把编码后的数据保存在该函数返回的QMimeData对象中。
(4)、 处理放置数据
1)、内置模型对放置数据的处理
不同类型的模型以不同的方式处理放置数据。列表模型和表格模型只提供了存储数据项的平面结构。因此,他们可能会在数据放入视图中的现有项目时插入新行(和列),或者可能会使用提供的一些数据覆盖模型中项目的内容。树模型通常能够将包含新数据的子项添加到其基础数据存储中,因此就用户而言,其行为将更具可预测性。
2)、对放置数据的自行处理
通过重新实现QAbstractItemModel::dropMimeData()函数来处理放置数据,此时需要对放置数据进行解码(即读取出QMimeData对象存储的数据的内容),并将其插入模型的底层数据结构中(或进行其他处理),若该函数修改了数据项或模型的尺寸,则必须注意确保发出所有相关的信号。因为该函数需要插入或删除等操作,所以简单的调用QAbstractItemModel子类中的已经实现了的setData(),insertRows()和insertColumns()等函数会更方便。另外,还可以使用dropMimeData()函数的默认实现来处理放置数据,dropMimeData()函数的默认实现不会覆盖模型中的任何数据,它会将放置数据作为项目的同胞插入,或者作为该项目的子项插入。若要使用该函数的默认实现,就需要重新实现以下函数:insertRows()、insertColumns()、setData()、setItemData()。

2、自定义拖放操作的其他设置
(1)、模型能接受的拖放操作的类型
重新实现QAbstractItemModel::supportedDropActions() 函数可以指示模型能接受的拖放操作的类型(比如移动、复制等),虽然可以给出Qt::DropActions的值的任意组合,但需要编写代码来支持它们,比如,对于Qt::MoveAction(移动)操作,则模型就需要提供QAbstractItemModel :: removeRows()的实现。
(2)、对多种MIME类型的支持
默认情况下,内置模型和视图只支持一种MIME类型,即
application/x-qabstractitemmodeldatalist。
若要让模型支持多种MIME类型,则需要重新实现QAbstractItemModel::mimeTypes(),该函数返回拖放操作时模型可以处理的MIME类型列表,注意,自定义数据类型必须声明为元对象。
(3)、禁止在项目上放置数据
重新实现QAbstractItemModel:: canDropMimeData()函数可禁止在项目上放置数据。

3、下面为相关的函数及其原型(QAbstractItemModel类中的成员函数)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

示例8.5:自定义拖放操作
注:本示例需在上一示例的main函数中增加以下代码(启用视图的拖放功能)。本示例的函数原型及其作用见示例后文对这些函数的讲解。
pv2->setDragEnabled(true);
    	pv2->viewport()->setAcceptDrops(true);
    	pv2->setDropIndicatorShown(true);
    	pv2->setDragDropMode(QAbstractItemView::DragDrop);

再在类D之中增下如下函数的定义

//①、重新实现flags函数,以开启模型的拖放功能
Qt::ItemFlags flags(const QModelIndex &index) const{
return 	Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled|
Qt::ItemIsEnabled|Qt::ItemIsSelectable;	}
//②、重新实现supportedDropActions以使模型即可复制又可移动
Qt::DropActions supportedDropActions() const{return Qt::CopyAction|Qt::MoveAction;}
//③、重新实现canDropMimeData函数,以决定单元格是否允许放置拖动过来的数据。
bool canDropMimeData(const QMimeData *data,Qt::DropAction action, int row, int column,
            										const QModelIndex &parent) const
{  /*本示例parent参数有效,row和column是无效的,以下代码表示,第3行第3列不接受来自拖放的数据。*/
if(parent.row()==2&&parent.column()==2) return false;	else return true;	}
//④、重新实现mimeData函数,以编码拖动时的数据(就是把拖动时的数据保存起来,并返回)
QMimeData* mimeData(const QModelIndexList &indexes) const{
QMimeData *const pm=new QMimeData();		//创建一个QMimeData对象。
     /*注意:indexes是被拖动的数据项的索引,若同时拖动了多个数据项,则indexes才会包含多个数据项。*/
for(int k=0;k<indexes.size();k++){
    		const QModelIndex &i=indexes.at(k);
          if (i.isValid()) {
              QByteArray t;
//提取数据项的索引为i,角色为DisplayRole的数据
              QVariant v=data(i, Qt::DisplayRole);   
              t.append(v.toString());			//把提取的数据保存在t之中
               pm->setData("text/plain", t); 	//然后把t中的数据以MIME类型的形式编码到pm中。
                         }   //if结束
      }   //for结束
      return pm;	}		//返回编码后的数据
//⑤、重新实现dropMimeData函数,以解码拖动时的数据并对其进行处理
bool dropMimeData(const QMimeData *data, Qt::DropAction action,int row, int column, 
const QModelIndex &parent)
    {/* 本示例parent参数有效,row和column无效,以下代码表示保存单元格所在数据s1中的位置。索引parent是拖动之后需要放置的位置的索引。*/
 	int i=parent.row()*col+parent.column();
    QByteArray t=data->data("text/plain");   //解码由mimeData函数编码后的数据。
	//若是复制操作,则把新位置parent处的数据重置为t
    if(action==Qt::CopyAction)  { setData(parent,t); }
//若是移动操作,则把新位置parent处的数据重置为t
    if(action==Qt::MoveAction) {  setData(parent,t);    }
    return true;   }		//返回true表示数据已处理。

运行结果及说明见图8-17
在这里插入图片描述

本文作者:黄邦勇帅(原名:黄勇)

在这里插入图片描述

  • 10
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本课程详细、全面地介绍了 Qt 开发中的各个技术细节,并且额外赠送在嵌入式端编写Qt程序的技巧。整个课程涵盖知识点非常多,知识模块囊括 Qt-Core 组件、QWidgets、多媒体、网络、绘图、数据库,超过200个 C++ 类的分析和使用,学完之后将拥有 Qt 图形界面开发的非常坚实的功底。 每个知识点不仅仅会通过视频讲解清楚,并且会配以精心安排的实验和作业,用来保证学习过程中切实掌握核心技术和概念,通过实验来巩固,通过实验来检验,实验与作业的目的是发现问题,发现技术盲点,通过答疑和沟通夯实技术技能。注意:本套视频教程来源于线下的实体班级,因此视频中有少量场景对话和学生问答,对此比较介意的亲们谨慎购买。注意:本套视频教程包含大量课堂源码,包含对应每个知识点的精心编排的作业。由于CSDN官方规定在课程介绍中不能出现作者的联系方式,因此在这里无法直接给出QQ答疑号,视频中的源码、资料和作业文档链接统一在购买后从CSDN平台跟我沟通,我会及时回复跟进。注意:本套视频教程包含全套10套作业题,覆盖所有视频知识点,循序渐进,各个击破,作业总纲如下:下面是部分作业题目展示,每道题都有知识点说明,是检验学习效果的一大利器:(部分作业展示,为了防止盗图盗题对题干做了模糊处理)(部分作业展示,为了防止盗图盗题对题干做了模糊处理)(部分作业展示,为了防止盗图盗题对题干做了模糊处理)(部分作业展示,为了防止盗图盗题对题干做了模糊处理)(部分作业展示,为了防止盗图盗题对题干做了模糊处理)…… ……
Qt中自定义Model/View模型,您可以继承QAbstractItemModel类,并实现其相关的虚拟函数来定义自己的数据模型。 下面是一个简单的示例,展示如何自定义一个简单的树形模型(Tree Model): ```cpp #include <QAbstractItemModel> #include <QModelIndex> #include <QVariant> class MyTreeModel : public QAbstractItemModel { public: explicit MyTreeModel(QObject *parent = nullptr); ~MyTreeModel(); // 重写父类的虚拟函数 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; }; MyTreeModel::MyTreeModel(QObject *parent) : QAbstractItemModel(parent) { // 在构造函数中初始化数据模型 } MyTreeModel::~MyTreeModel() { // 在析构函数中清理数据 } QModelIndex MyTreeModel::index(int row, int column, const QModelIndex &parent) const { // 返回指定行、列和父索引的模型索引 } QModelIndex MyTreeModel::parent(const QModelIndex &child) const { // 返回指定子索引的父索引 } int MyTreeModel::rowCount(const QModelIndex &parent) const { // 返回指定父索引的子项数目 } int MyTreeModel::columnCount(const QModelIndex &parent) const { // 返回模型的列数 } QVariant MyTreeModel::data(const QModelIndex &index, int role) const { // 返回指定模型索引和角色的数据 } ``` 在这个示例中,我们自定义了一个名为MyTreeModel的树形模型,继承自QAbstractItemModel类,并重写了QAbstractItemModel的一些虚拟函数。在这些函数中,您可以根据自己的数据结构和需求来实现相应的逻辑。 要使用自定义模型,您可以在视图中设置该模型,如: ```cpp MyTreeModel model; QTreeView view; view.setModel(&model); ``` 当您设置了自定义模型后,视图将使用模型提供的数据来显示和管理数据。 当然,这只是一个简单的示例。在实际应用中,您可能需要更复杂的数据结构和逻辑来实现您的自定义模型。但是通过继承QAbstractItemModel类,并实现其虚拟函数,您可以很好地控制自己的数据模型视图之间的交互。 希望这个示例能够帮助您开始自定义Model/View模型的开发!如果您有任何进一步的问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值