一、Model/View基本原理
GUI应用程序开发中往往少不了列表框,表格,树形结构等表现形式的应用。当然Qt中也提供了相应的视图类QListView,QTableView, QTreeView,这些类使用模型/视图Model/View架构来管理数据之间的关系及其呈现给用户的方式。这种体系结构引入的功能分离为开发人员提供了更大的灵活性来定制数据项的表示,并提供了一个标准模型接口,允许在现有的视图中使用广泛的数据源。
Data:是实际数据,可以数据库的一个数据表或SQL查询结果,内存中的StringList,或文件等等。
(资料图)
View:GUI界面组件,视图从数据模型获得每个数据线的模型索引(Model index),通过模型缩影获取数据,然后为界面组件提供显示数据。比如QListView,QTableView, QTreeView等。
Model:与实际数据通信,并为视图组件提供数据接口。可以理解成数据adapter,数据wrapper。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。
这样设计的好处有:
通过Model/View使数据源与显示界面分离,代码解耦;
另外还可以将同一数据模型在不同的视图中显示;
还可以在不修改数据模型的情况下,设计特殊的视图。
Delegate:在model/view结构中,还提供了代理功能(Delegate),代理功能可以让用户定制数据的界面显示和编辑方式。
model,view,delegate之间使用信号和槽进行通信。当数据发生变化时,model通过信号通知view;
当用户在UI上操作数据时(选中,点击等),view通过信号表示这些操作信息;
当用户编辑数据时,delegate通过信号通知model和view编辑器的状态。
1.数据模型 Model
QAbstractItemModel是所有数据模型的基类,这个类定义了view和delegate存取数据的接口。但原始数据不一定要存储在model里。
而通常情况是我们使用QListView,QTableView, QTreeView都会使用与之相应的模型类,分别继承自QAbstractListModel,QAbstractTableModel,QAbstractItemModel,生成自己定制的数据模型类。
2.视图组件 View
视图组件View就是显示数据模型的数据的界面组件,Qt提供如下常用视图组件:
QListView:显示单列的列表数据,适用于一维数据的操作;
QTreeView:显示树状结构数据,适用于树状结构数据的操作;
QTableView:显示表格状数据,适用于二维表格型数据的操作。
视图类的setModel()函数,即可完成view和model的数据绑定,同时在view上的修改能自动关联到model。
3.代理 delegate
代理就是视图组件上为编辑数据提供编辑器,如在table组件中双击一个单元格编辑数据是,缺省是使用QLineEdit编辑框。代理的作用首先是从model中取数据,然后显示在编辑器中,修改数据后,又将其保存到model中。
通常使用需要派生自QStyledItemDelegate类,创建自定义代理类。
二、QAbstractTableModel使用
通过上面的分析,我们知道QAbstractTableModel,主要为QTableView提供数据模型接口,我们可以子类化该抽象类并实现相关接口。下面我们做一个简单9*9乘法口诀的demo来看一下具体使用方法:
必须要实现的接口如下3个:
//返回行数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;
当然只有必须的3个接口,好像还不能很好的工作,首先我们需要给model赋初值。
新增setInitData成员函数,加载数据并刷新。
void MyTableModel::setInitData(QList& data){//重置model数据之前调用beginResetModel,此时会触发modelAboutToBeReset信号beginResetModel();//重置model中的数据m_datas = data;m_rowNum = ceil(data.size()*1.0/m_columnNum); //行数=数据总数/列数,然后向上取整//数据设置结束后调用endResetModel,此时会触发modelReset信号endResetModel();}
返回rowCount(),columnCount(),因为这里行列都是9,所以返回固定的9
int MyTableModel::rowCount(const QModelIndex &parent) const{if (parent.isValid()) {return 0;} else {return m_rowNum;}}int MyTableModel::columnCount(const QModelIndex &parent) const{if (parent.isValid()) {return 0;} else {return m_columnNum;}}
行列的动态增删
因为这个9*9乘法口诀表行列是固定的,所以这里暂不需要对行列的操作,相关接口如下,可以按需增加。
//插入相关接口bool insertColumn(int column, const QModelIndex &parent = QModelIndex())virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex())bool insertRow(int row, const QModelIndex &parent = QModelIndex())virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())//删除相关接口bool removeColumn(int column, const QModelIndex &parent = QModelIndex())virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex())bool removeRow(int row, const QModelIndex &parent = QModelIndex())virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
最最重要的一个接口data()函数
view通过model提供的data()函数,进行自身的数据呈现,可以分不同维度提供不同的数据。
QVariant MyTableModel::data(const QModelIndex &index, int role) const{if (!index.isValid()) {return QVariant();}if(index.row()*m_columnNum+index.column() < m_datas.count()){if (role == Qt::DisplayRole || role == Qt::EditRole) {return m_datas[index.row()*m_columnNum+index.column()]->content;//数据的呈现形式}else if(role == Qt::BackgroundColorRole){return m_datas[index.row()*m_columnNum+index.column()]->bgColor;//单元格背景色}else if (role == Qt::TextAlignmentRole) { //对其方式return Qt::AlignCenter;}else if(role == Qt::ToolTipRole){return m_datas[index.row()*m_columnNum+index.column()]->toolTip;//数据的提示信息}else if(role == Qt::UserRole){return QVariant::fromValue(m_datas[index.row()*m_columnNum+index.column()]);}}return QVariant();}
可以看到ItemDataRole的枚举,想要view以何种维度来展示数据,往这里面的else if里面写即可。
enum ItemDataRole {DisplayRole = 0,DecorationRole = 1,EditRole = 2,ToolTipRole = 3,StatusTipRole = 4,WhatsThisRole = 5,// MetadataFontRole = 6,TextAlignmentRole = 7,BackgroundRole = 8,ForegroundRole = 9,#if QT_DEPRECATED_SINCE(5, 13) // ### Qt 6: remove meBackgroundColorRole Q_DECL_ENUMERATOR_DEPRECATED = BackgroundRole,TextColorRole Q_DECL_ENUMERATOR_DEPRECATED = ForegroundRole,#endifCheckStateRole = 10,// AccessibilityAccessibleTextRole = 11,AccessibleDescriptionRole = 12,// More general purposeSizeHintRole = 13,InitialSortOrderRole = 14,// Internal UiLib roles. Start worrying when public roles go that high.DisplayPropertyRole = 27,DecorationPropertyRole = 28,ToolTipPropertyRole = 29,StatusTipPropertyRole = 30,WhatsThisPropertyRole = 31,// ReservedUserRole = 0x0100};
默认情况下,双击是不能编辑的,可以重写成员函数flags(),setData(),让其具有编辑功能,并编辑框更改后的值能更新到model里,也能通知到view。
Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const{if (!index.isValid())return Qt::NoItemFlags;return Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable;}bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role){if(index.row()*m_columnNum+index.column() < m_datas.count()){if (index.isValid() && role == Qt::EditRole){m_datas[index.row()*m_columnNum+index.column()]->content = value.value();emit dataChanged(index, index, QVector() << role); //发送信号触发刷新return true;}if (index.isValid() && role == Qt::BackgroundColorRole){m_datas[index.row()*m_columnNum+index.column()]->bgColor = value.value();emit dataChanged(index, index, QVector() << role); //发送信号触发刷新return true;}}return false;}
使用代理(delegate)
tableview如果不指定delegate的话,默认是使用QLineEdit编辑框。通过前面介绍我们知道delegate的作用相当于model–view之间的桥梁,相互传递数据的作用。
我想实现双击,编辑单元格颜色的效果,如下图,该怎么做呢?
首先子类化QStyledItemDelegate,在实现基类4个虚函数,4个函数的作用,看注释应该都很清楚。delegate是桥梁,是中间人的角色,所以model—view两边都要安排好。
class MyColorSelDelegate : public QStyledItemDelegate{Q_OBJECTpublic:explicit MyColorSelDelegate(QObject *parent = nullptr);~MyColorSelDelegate();//创建用于编辑模型数据的widget组件,如一个QSpinBox组件,或一个QComboBox组件;QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;//从数据模型获取数据,供widget组件进行编辑;void setEditorData(QWidget *editor, const QModelIndex &index) const override;//将widget上的数据更新到数据模型;void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;//用于给widget组件设置一个合适的大小;void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;signals:};
其次createrEditor(),返回系统自带的QColorDialog颜色拾取框的QWidget对象指针,当然这里也可以是我们自定义的任何widget;
然后setEditorData(),从数据模型获取数据,供widget组件进行编辑;
最后setModelData(),将widget上的数据更新到数据模型;
QWidget *MyColorSelDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const{QWidget* editor = new QColorDialog(parent);return editor;}void MyColorSelDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const{}void MyColorSelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const{QColorDialog* dlg = static_cast(editor);model->setData(index, dlg->selectedColor(), Qt::BackgroundColorRole);}void MyColorSelDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const{editor->setGeometry(option.rect);}
三、最后
通过QAbstractTableModel和QTableView的实际操作,我们应该能明白model/view的原理。
【领 QT开发教程 学习资料, 点击下方链接莬费领取↓↓ ,先码住不迷路~】
点击这里:
一、Model View基本原理GUI应用程序开发中往往少不了列表框,表格...
1、《心爱的心爱的》是2003年1月22日狗屋出版社出版的图书。2、作者...
据中证报,“2023年1月至3月,全国实际使用外资4084 5亿元人民币,...
仓山建新镇玉兰村临近奥体中心和飞凤山公园,是典型的城边村,曾经...
4月20日,谷雨时节,河南种业第一股秋乐种业(831087 BJ)发布上市...
1、荠菜春卷、蟹黄煨面、三丁包、千层油糕、翡翠烧卖。2、荠菜春卷...
文丨西厢有情时光不长,愿你在我的文字里,相见不晚!任何一件事情...
4月17日,南宁市教育局官宣,南宁市新民中学、南宁市三美学校转为公...
中国经济增长良好势头持续吸引世界目光。世界银行行长马尔帕斯近日...
4月20日北向资金增持39 6万股利君股份。近5个交易日中,获北向资金...
央视网消息:一季度,进一步释放内需促消费也成为促经济的重要一环...
格隆汇4月20日丨正裕工业(603089)(603089 SH)公布2022年年度报告,...
今天来聊聊关于巴州教育局24小时服务热线,巴市教育网官网的文章,...
“味美浙江”餐饮消费欢乐季活动。浙江省商务厅供图中新网杭州4月20...
新华视点|中国经济走稳向好——解读一季度经济数据亮点国家统计局1...
近日,我们受邀前往“魔都”上海参加了一场全新车型的产品体验活动...
欧洲央行周三公布的数据显示,由于能源成本下降导致进口下降,并且...
为贯彻落实吉林省“一主六双”高质量发展战略,紧紧抓住扩大内需战...
明新旭腾:第一创业证券承销保荐有限责任公司关于明新旭腾新材料股份...
4月19日,瑞银发表报告指,今年首季中国经济以一年来最快的速度增长...
作为国家油气基础设施重点工程,双台子储气库双向输气管道工程日前...
App4月20日消息,A股收市,三大指数全线收跌,上证指数收跌0 09%,...
4月的承德市隆化县,最低气温仍低于零度。位于七家镇的一处地热井施...
4月19日中午12时,北京市人民政府新闻办公室、北京市丰台区人民政府...
2021年8月25日,平顶山市中级人民法院作出一审判决,以被告人龚某犯...
兴业基金任命叶文煌为董事长官恒秋离任
1、可以通过找气泡来寻找鲫鱼窝,从冰面往下看,可以看到一簇簇或一...
18日,山西晚报记者从北京国际电影节组委会获悉,第19届北京国际体...
鞭牛士4月20日消息,TikTok电商数据服务平台“嘀嗒狗”宣布完成数千...
四月芳菲日,正是研学时。为贯彻落实教育部《中小学综合实践活动课...