本文主要研究了一下如何把樹形結構的數據保存到文件並讀取出來。為了更形象說明用了一個界面程序顯示,程序用了model/view框架。 數據類 DataItem 就是保存在樹形結構的基本數據。其最重要的保存數據的函數是SerialzeData [cpp] { 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; }; [cpp] 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()。 [cpp] 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; }; [cpp] 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); } 主框架類 這個類主要實現左邊的樹形把數據保存到文件中,然後在右邊的樹形結構加載顯示出來。 [cpp] 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; }; [cpp] 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() { [cpp] 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(); //必須的,不然不會刷新 } } }