前言
像题目所说,本篇博客完成的需求为读取xml文件,并将其显示为树形结构。这里理所当然,运用的知识应为mvc模式+QTreeView+QStandardItemModel这三大块,接下来,我将逐渐讲述着三大块的知识。
知识点详解
MVC模式
其实Qt中的MVC并不叫MVC,而是叫“MVD”,Qt中没有Controller的说法,而是使用了另外一种抽象: Delegate (委托) ,其行为和传统的MVC是相同的。写过C#的同学肯定对delegate就不陌生了,这里delegate的用法就是负责协调Model和View之间的数据。其思想如下图所示:
Model是唯一和数据集打交道的组件,View不接触数据源,其所需要的数据可以从Model中取出,而Delegate正式负责协调Model和View上数据。这种将view和数据源隔离的方式有几点好处:
1、在处理较大的数据集时每个组件各司其职,不至于降低性能。
2、一个Model可以映射到多个View,这样可以以不同的方式查看数据同一份数据。
3、如果底层数据源的存储改变了,我们只需要处理Model就可以了。
举个具体的例子:
上面的知识讲解,相信你应该已经能够理解MVD的大约模式了。而QT的MVD模式有很多其独具一格的特性,下面就是一些常见的使用类。
模型model(表示数据)
抽象基类QAbstractItemModel
列表的抽象基类QAbstractListModel、表格的抽象基类QAbstractTableModel
QDirModel类是文件与目录的存储模型
QStandardItemModel类
QStringListModel类
视图view(表示用户界面)
抽象基类QAbstractItemView
QListView—QListWidget\QUndoView
QTableView—QTableWidget
QTreeView—QTreeWidget
QColumnView
QHeaderView
实际上:QListWidget、QTableWidget、QTreeWidget已经包含数据,是模型与视图集成的类
代理delegate(自定义数据条目item的显示与编辑方式)
抽象基类QAbstractItemDelegate
QItemDelegate/QStyleItemDelegate
类QItemDelegate 由类QSqlRelationDelegate继承
在完成前言所言的上述需求中,我们只需要用到MV两种方式,第三种,大家可以看一下我的这篇博文QT学习笔记(8)-QDomDocument解析以及QTableView显示
QStandardItemModel
QStandardItemModel 是标准的以项数据(item data)为基础的标准数据模型类,通常与 QTableView 组合成 Model/View 结构,实现通用的二维数据的管理功能。
QStandardltemModel 的使用,主要用到以下 3 个类:
- QStandardItemModel:基于项数据的标准数据模型,可以处理二维数据。维护一个二维的项数据数组,每个项是一个 QStandardltem 类的变量,用于存储项的数据、字体格式、对齐方式等。
- QTableView:二维数据表视图组件,有多个行和多个列,每个基本显示单元是一个单元格,通过 setModel() 函数设置一个 QStandardItemModel 类的数据模型之后,一个单元格显示 QStandardItemModel 数据模型中的一个项。
- QItemSelectionModel:一个用于跟踪视图组件的单元格选择状态的类,当在 QTableView 选择某个单元格,或多个单元格时,通过 QItemSelectionModel 可以获得选中的单元格的模型索引,为单元格的选择操作提供方便。
然后,为了完成需求,你所要了解的是这篇博客使用QStandardItemModel创建子节点,如何使用这个类来创建子节点,这篇博客也是我的主要参考博客。
QTreeView
QTreeView是一个视图类,你需要手动给其指定模型类,才能够显示数据。此时,你便需要将其与QStandardItemModel类进行结合。关于这个知识点,我主要参考为博客为Qt中三种解析xml的方式,这三种解析方式,当然,我选择的是第二种,关于这三种的利弊,在文章中都有较好的讲解,值得一看。
程序详解
效果图
不管xml文件中有多少个节点,都可以以树形的方式进行展示。
代码
ServerTreeView.cpp
首先,你在View中,需先把要读取文件的路径传入类中,然后,调用模型的解析函数。
#include "ServerTreeView.h"
#include "ServerTreeModel.h"
CServerTreeView::CServerTreeView(QWidget *parent) : QTreeView(parent)
{
p_saveProject = new CServerSaveProject();
//在更改参数时,建议将复制过来的文件路径的斜杠更换一下方向,才能更好的识别
QString fileName="E:/Test/test.xml";//这是要写入的目标文件。
QString path = "E:/QT_Code/work_content/Day01";//这是要进行检测的目标文件夹
p_saveProject->saveProject(fileName,path);
initTreeView();
}
CServerTreeView::~CServerTreeView()
{
}
void CServerTreeView::initTreeView()
{
QString xmlPath = "E:/Test/test.xml";
p_model = new CServerTreeModel();
// p_model->readByte(byteArray);
this->setModel(p_model);//public void setModel(TreeModel newModel):根据传递的参数设置TreeModel的值
//p_model->parseFile(xmlPath);//解析xml文件
p_model->readFile(xmlPath);
p_model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("文件名"));
//this->setUpdatesEnabled(true); //恢复界面刷新
}
void CServerTreeView::receiveByte(QByteArray byteArray)
{
qDebug()<<"receiveByte";
p_model->readByte(byteArray);
}
ServerTreeModel.cpp
主要的函数都已在下面进行展示了,首先,读取文件信息,setcontent于doc之中。然后,递归调用处理节点的函数,不断的把子节点链接在父节点上面ParentItem->appendRows(childItems);
,从而实现不管xml文件有多少层,都可以实现展示。
#include "ServerTreeModel.h"
CServerTreeModel::CServerTreeModel(QObject *parent) : QStandardItemModel(parent)
{
}
CServerTreeModel::~CServerTreeModel()
{
}
bool CServerTreeModel::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
std::cerr << "Error: Cannot read file " << qPrintable(fileName)
<< ": " << qPrintable(file.errorString())
<< std::endl;
return false;
}
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn))
{
std::cerr << "Error: Parse error at line " << errorLine << ", "
<< "column " << errorColumn << ": "
<< qPrintable(errorStr) << std::endl;
return false;
}
QDomElement root = doc.documentElement();
if (root.tagName() != "root")
{
std::cerr << "Error: Not a school file" << std::endl;
return false;
}
else{
QFileInfo appInfo(root.attribute("Path"));
QString value = appInfo.baseName();//文件名
item1 = new QStandardItem((value));
this->appendRow(item1);
qDebug()<<"root";
}
parseAllMembers(root,item1);
//parseRootMembers(root);
return true;
}
void CServerTreeModel::parseAllMembers(const QDomElement &element,QStandardItem *ParentItem)
{
QDomNode child = element.firstChild();
while(!child.isNull())
{
QFileInfo appInfo(child.toElement().attribute("Path"));
QString value = appInfo.baseName();//文件名
QString suffix = appInfo.suffix();
QString readvalue = value + "."+suffix;
ChildItem = new QStandardItem(readvalue);
childItems.clear();
childItems.push_back(ChildItem);
ParentItem->appendRows(childItems);
childItems.clear();
if (child.toElement().tagName() == "Folder")
{
parseAllMembers(child.toElement(),ChildItem);
}
child = child.nextSibling();
}
}
注意:这里的代码还依然使用了绝对路径,记得更改,但在后面的代码已经有对这点进行了修改了。这里就不修改了。需要更改后的,可以看第(16)篇文章,那个就都是使用了相对地址了。
代码地址
https://download.csdn.net/download/weixin_38809485/12624914中的MyTcpFile5