资料保存树形结构数据

文件保存树形结构数据

本文主要研究了一下如何把树形结构的数据保存到文件并读取出来。为了更形象说明用了一个界面程序显示,程序用了model/view框架。

数据类

DataItem 就是保存在树形结构的基本数据。其最重要的保存数据的函数是SerialzeData
class DataItem
{
public:

	DataItem(int id = 100,QString name = "root");
	~DataItem();

	void SetRoot(DataItem *root);
	void SerialzeData(bool isSave,QDataStream &stream);
	void Clear();
	void Init();
	//protected:
	int GetID()
	{
		return ID;
	}

	QString GetName()
	{
		return Name;
	}

	void SetID(int id)
	{
		ID = id;
	}

	void SetName(QString name)
	{
		Name = name;
	}

	int  GetSize()
	{
		return dataVec.size();
	}

	void AddItem(DataItem *pItem);

	void DeleteItem(DataItem *pItem);

	void DeleteItem(int index);

	DataItem *GetItem(int index);

    DataItem *GetParent()
	{
		return pRoot;
	}

	int indexOf(DataItem* pItem);
private:
	int ID;
	QString Name;
	vector<DataItem*>  dataVec;
	DataItem *pRoot;
};
DataItem::DataItem( int id,QString name ):ID(id),Name(name),pRoot(NULL)
{
	//pRoot = new DataItem(100,"Root");
}

DataItem::~DataItem()
{

}
//SerialzeData 原来是,保存数据时,先保存每个项的数据,在后面保存该项的子节点个数,并递归保存各个子节点数据
void DataItem::SerialzeData( bool isSave,QDataStream &stream )
{
	if (isSave)
	{
		stream<<GetID()<<GetName();  //save ID and Name 
		stream<<dataVec.size();     //save  the number of child
		for(int i = 0; i < dataVec.size(); ++i)
		{
			dataVec[i]->SerialzeData(isSave,stream);
		}
	}
	else
	{
		int id;
		int size;
		QString name;
		stream>>id>>name;  //Get ID and Name 
		SetID(id);
		SetName(name);
		stream>>size;     //Get  the number of child
		for(int i = 0; i < size; ++i)
		{
			DataItem *pItem = new DataItem(0,"name");
			pItem->SerialzeData(isSave,stream);
			AddItem(pItem);
		}
	}
}

void DataItem::AddItem( DataItem *pItem )
{
	pItem->SetRoot(this);
	dataVec.push_back(pItem);
}

void DataItem::DeleteItem( DataItem *pItem )
{
	vector<DataItem*>::iterator it = dataVec.begin();
	for (it; it != dataVec.end(); ++it)
	{
		if (*it == pItem)
		{
			dataVec.erase(it);
			break;
		}
	}
}

void DataItem::DeleteItem( int index )
{
	if (index < dataVec.size())
	{
		vector<DataItem*>::iterator it = dataVec.begin();
		it = it + index;
		dataVec.erase(it);
	}
}

void DataItem::Init()
{
	for (int i = 0; i < 5; ++i)
	{
		DataItem *pItem = new DataItem(i,QString("child%1").arg(i));
		pRoot->AddItem(pItem);
		for (int j = 0; j < 2; ++j)
		{
			DataItem *pChild = new DataItem(j,QString("grandchild%0 -%1").arg(i).arg(j));
			pItem->AddItem(pChild);
		}
	}
}

void DataItem::SetRoot( DataItem *root )
{
	pRoot = root;
}

void DataItem::Clear()
{
	dataVec.clear();
}

DataItem * DataItem::GetItem( int index )
{
      if (index < dataVec.size())
		{
			return dataVec[index];
		}
		else
		{
			return NULL;
		}
}

int DataItem::indexOf( DataItem* pItem )
{
	int index = -1;
	for (int i = 0; i < dataVec.size(); ++i)
	{
		if (dataVec[i] == pItem)
		{
			index = i;
			break;
		}
	}
	return index;
}

数据模型

TreeDataModel的底层数据就是上面定义的DataItem。用这种视图/模型的编程方式可以尽量减少数据与界面的耦合性。由于继承了QAbstractItemModel。所以必须重写其中的五个纯虚函数columnCount (),data(),index (),parent ()和rowCount()。

class TreeDataModel:public QAbstractItemModel
{
	Q_OBJECT
public:
	TreeDataModel(QObject *parent = NULL);
	~TreeDataModel();

	void SetRoot(DataItem *pRoot)
	{
		m_pTreeData = pRoot;
	}
	QModelIndex	parent ( const QModelIndex & index ) const;
	 QVariant	data ( const QModelIndex & index, int role = Qt::DisplayRole ) const ;
	 QVariant	headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;
	 QModelIndex	index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const;
	 int	columnCount ( const QModelIndex & parent = QModelIndex() ) const;
	 int	rowCount ( const QModelIndex & parent = QModelIndex() ) const;
	 DataItem* dataFromIndex(const QModelIndex &index) const;

	 void SaveData(QDataStream &out);
	 void LoadData(QDataStream &in);
protected:
private:
	DataItem  *m_pTreeData;
};

TreeDataModel::TreeDataModel( QObject *parent /*= NULL*/ ):QAbstractItemModel(parent)
{
	m_pTreeData = NULL;
}

TreeDataModel::~TreeDataModel()
{

}

QVariant TreeDataModel::data( const QModelIndex & index, int role /*= Qt::DisplayRole */ ) const
{
	DataItem *pItem = dataFromIndex(index);
	if ((pItem)&&(role == Qt::DisplayRole))
	{
		switch (index.column())
		{
		case 0:
			return pItem->GetID();
		case 1:
			return pItem->GetName();
		}
	}
	return QVariant();

}

QVariant TreeDataModel::headerData( int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole */ ) const
{
	if ((section <2) && (orientation == Qt::Horizontal)&& (role == Qt::DisplayRole))
	{
		switch (section)
		{
		case 0:
			return tr("编号");

		case 1:
			return tr("名称");
		default:
			return QVariant();
		}
	}
	else
	{
		return QVariant();
	}
}

QModelIndex TreeDataModel::index( int row, int column, const QModelIndex & parent /*= QModelIndex() */ ) const
{
	if (!m_pTreeData ||row < 0 || column < 0)
	{
		return QModelIndex();
	}
	else
	{
		DataItem *pItem = dataFromIndex(parent);
		if (pItem)
		{
			DataItem *pChild = pItem->GetItem(row);
			if (pChild)
			{
				return createIndex(row,column,pChild);
			}
		}
		return QModelIndex();
	}
}

int TreeDataModel::columnCount( const QModelIndex & parent /*= QModelIndex() */ ) const
{
	return 2;
}

int TreeDataModel::rowCount( const QModelIndex & parent /*= QModelIndex() */ ) const
{
	DataItem *pItem = dataFromIndex(parent);
	if (pItem)
	{
		return pItem->GetSize();
	}
	return 0;
}

DataItem* TreeDataModel::dataFromIndex( const QModelIndex &index ) const
{
	if (index.isValid())
	{
		return static_cast<DataItem*>(index.internalPointer());
	} 
	else
	{
		return m_pTreeData;               //这里不要返回NULL
	}
}

QModelIndex TreeDataModel::parent( const QModelIndex & index ) const
{
	if (index.isValid())
	{
		DataItem *pItem = dataFromIndex(index);
		if (pItem)
		{
			DataItem *pParent = pItem->GetParent();
			if (pParent)
			{
				DataItem *pGrandParent = pParent->GetParent();
				if (pGrandParent)
				{
					int row = pGrandParent->indexOf(pParent);
					return createIndex(row,index.column(),pParent);
				}
			}
		}
	}
	return QModelIndex();
}

void TreeDataModel::SaveData( QDataStream &out )
{
	m_pTreeData->SerialzeData(true,out);
}

void TreeDataModel::LoadData( QDataStream &in )
{
	m_pTreeData->SerialzeData(false,in);
}

主框架类

这个类主要实现左边的树形把数据保存到文件中,然后在右边的树形结构加载显示出来。
class MainWidget:public QWidget
{
	Q_OBJECT
public:
	MainWidget(QWidget *patent = NULL);
	~MainWidget();

protected slots:
	void leftSelectBtnSlot();
	void rightSelectBtnSlot();
	void saveBtnSlot();
	void loadBtnSlot();

private:
	QSplitter         *m_pSplitter;
	QTreeView         *m_pLeftTreeView;
	QTreeView         *m_pRightTreeView;
	QPushButton       *m_pLeftSaveBtn;
	QPushButton       *m_pRightLoadBtn;
	QPushButton       *m_pLeftSelectBtn;
	QPushButton       *m_pRightSelectBtn;
	QLineEdit         *m_pLeftLEdit;
	QLineEdit         *m_pRightLEdit;
	QGridLayout       *m_pLeftLayout;
	QGridLayout       *m_pRightLayout;

	TreeDataModel     *m_pLeftModel;
	TreeDataModel     *m_pRightModel;


};


MainWidget::MainWidget( QWidget *patent /*= NULL*/ ):QWidget(patent)
{
	m_pLeftModel = new TreeDataModel();
	m_pRightModel = new TreeDataModel();

	m_pSplitter = new QSplitter(this);
	QFrame *pLeftFrame = new QFrame(this);
	QFrame *pRightFrame = new QFrame(this);
	m_pLeftLayout = new QGridLayout(pLeftFrame);
	m_pRightLayout = new QGridLayout(pRightFrame);
	m_pLeftLEdit = new QLineEdit(this);
	m_pRightLEdit = new QLineEdit(this);
	m_pLeftSaveBtn = new QPushButton(tr("保存"),this);
	m_pRightLoadBtn = new QPushButton(tr("加载"),this);
	m_pLeftTreeView = new QTreeView(this);
	m_pRightTreeView = new QTreeView(this);
	m_pLeftSelectBtn = new QPushButton(tr("选择文件"),this);
	m_pRightSelectBtn = new QPushButton(tr("选择文件"),this);
	m_pRightLEdit->setReadOnly(true);

	
	m_pLeftLayout->addWidget(m_pLeftSelectBtn,0,0,1,1);
	m_pLeftLayout->addWidget(m_pLeftLEdit,0,1,1,1);
	m_pLeftLayout->addWidget(m_pLeftSaveBtn,0,2,1,1);
	m_pLeftLayout->addWidget(m_pLeftTreeView,1,0,3,3);

	m_pRightLayout->addWidget(m_pRightSelectBtn,0,0,1,1);
	m_pRightLayout->addWidget(m_pRightLEdit,0,1,1,1);
	m_pRightLayout->addWidget(m_pRightLoadBtn,0,2,1,1);
	m_pRightLayout->addWidget(m_pRightTreeView,1,0,3,3);

	m_pLeftTreeView->setModel(m_pLeftModel);
	m_pRightTreeView->setModel(m_pRightModel);
	DataItem *pTreeData = new DataItem();
	pTreeData->SetRoot(pTreeData);
	pTreeData->Init();
	
	m_pLeftModel->SetRoot(pTreeData);
	//m_pRightModel->SetRoot(pTreeData);

	m_pSplitter->addWidget(pLeftFrame);
	m_pSplitter->addWidget(pRightFrame);

	connect(m_pLeftSelectBtn,SIGNAL(clicked()),this,SLOT(leftSelectBtnSlot()));
	connect(m_pRightSelectBtn,SIGNAL(clicked()),this,SLOT(rightSelectBtnSlot()));
	connect(m_pLeftSaveBtn,SIGNAL(clicked()),this,SLOT(saveBtnSlot()));
	connect(m_pRightLoadBtn,SIGNAL(clicked()),this,SLOT(loadBtnSlot()));
	this->setFixedSize(QSize(650,250));
}

MainWidget::~MainWidget()
{

}

void MainWidget::leftSelectBtnSlot()     //这里只是选择了一个文件夹路径,在保存之前还需要加文件名
{
	QFileDialog Dialog(this,tr("选择目录"),"","");
	Dialog.setFileMode(QFileDialog::Directory);
	//Dialog.setNameFilter("*.data");
	if (Dialog.exec())
	{
		QStringList dirs = Dialog.selectedFiles();
		if (dirs.size() > 0)
		{
			m_pLeftLEdit->setText(QDir::toNativeSeparators(dirs.at(0)));    
		}
	} 
}

void MainWidget::rightSelectBtnSlot()      //选择之前保存的.data文件进行加载显示
{
	QFileDialog Dialog(this,tr("选择文件"),"","");
	Dialog.setFileMode(QFileDialog::ExistingFile);
	Dialog.setNameFilter("*.data");
	if (Dialog.exec())
	{
		QStringList files = Dialog.selectedFiles();
		if (files.size() > 0)
		{
			m_pRightLEdit->setText(QDir::toNativeSeparators(files.at(0)));
		}
	} 
}

void MainWidget::saveBtnSlot()
{
	QString filePath = m_pLeftLEdit->text();
	if ((filePath.isEmpty()) || filePath.endsWith("\\") || filePath.endsWith("/"))   //必须得添加文件名,文件名规定后缀为.data
	{
		QMessageBox::information(this,tr("提示"),tr("请输入文件名"),QMessageBox::Ok);
		return;
	}
	else if(filePath.endsWith("data"))
	{
		QFile file(filePath);
		if (file.open(QIODevice::WriteOnly))
		{
			QDataStream outStream(&file);
			m_pLeftModel->SaveData(outStream);
		}
	}
}

void MainWidget::loadBtnSlot()
{
	QString filePath = m_pRightLEdit->text();
	if((!filePath.isEmpty()) &&filePath.endsWith("data"))
	{
		DataItem *pTreeData = new DataItem();
		//pTreeData->SetRoot(pTreeData);
		m_pRightModel->SetRoot(pTreeData);

		QFile file(filePath);
		if (file.open(QIODevice::ReadOnly))
		{
			QDataStream inStream(&file);
			m_pRightModel->LoadData(inStream);
			m_pRightTreeView->setModel(m_pRightModel);
			m_pRightTreeView->reset();                  //必须的,不然不会刷新
		}
	}
}

运行结果如下图
资料保存树形结构数据