本文主要研究了一下如何把树形结构的数据保存到文件并读取出来。为了更形象说明用了一个界面程序显示,程序用了model/view框架。
数据类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;
}
数据模型
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(); //必须的,不然不会刷新
}
}
}
运行结果如下图
EGOTableViewPullRefresh 是fork EGOTableViewPullRefresh开源类库进行的改进,添加了上提加载更多效果。同时也可以通过一个按钮的触发刷新事件,但是刷新的时候不能跳到top,为了动态展示,再刷新的时候按钮旋转,然后跳转回到顶部!如下如图
关于EGOTableViewPullRefresh可以参照http://blog.csdn.net/duxinfeng2010/article/details/9007311,翻译过的用法,在这个Demo基础上进行修改,点击Demo下载;
1、给工程添加一个导航栏,在application: didFinishLaunchingWithOptions:方法中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// [[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"navbar.png"] forBarMetrics:UIBarMetricsDefault];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
2、在ViewDidLoad方法中,修改背景图片,添加刷新按钮
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.tintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"navbar.png"]];
self.pullTableView.pullArrowImage = [UIImage imageNamed:@"blackArrow"];
// self.pullTableView.pullBackgroundColor = [UIColor yellowColor];
self.pullTableView.pullTextColor = [UIColor blackColor];
CGRect rect = CGRectMake(0, 0, 44, 44);
UIButton *refreshBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
refreshBtn.frame = rect;
[refreshBtn setBackgroundImage:[UIImage imageNamed:@"button_refresh"] forState:UIControlStateNormal];
[refreshBtn addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *refreshItem = [[UIBarButtonItem alloc] initWithCustomView:refreshBtn];
self.navigationItem.leftBarButtonItem = refreshItem;
}3、添加刷新按钮事件,和按钮旋转方法
//按钮旋转
- (void)startAnimation:(UIButton *)button{
CABasicAnimation *rotate =
[CABasicAnimation animationWithKeyPath:@"transform.rotation"];
[rotate setByValue:[NSNumber numberWithFloat:M_PI*4]];
rotate.duration = 3.0;
rotate.timingFunction =
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[button.layer addAnimation:rotate
forKey:@"myRotationAnimation"];
}-(void)refresh:(UIButton *)button
{
[self startAnimation:button];
// 判断一下table是否处于刷新状态,如果没有则执行本次刷新
if (!self.pullTableView.pullTableIsRefreshing) {
self.pullTableView.pullTableIsRefreshing = YES;
// 设置回到top时候table的位置
[self.pullTableView setContentOffset:CGPointMake(0, -60) animated:YES];
[self performSelector:@selector(refreshTable) withObject:nil afterDelay:3.0];
}
}源码下载地址:https://github.com/XFZLDXF/RefreshButtonDemo
在开发的过程当中,由于手机屏幕的大小的限制,我们经常需要使用滑动的方式,来显示更多的内容。在最近的工作中,遇见一个需求,需要将ListView嵌套到ScrollView中显示。于是乎有了如下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE1FF"
android:orientation="vertical" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:fadingEdgeLength="5dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
运行程序,如下结果,无论你如何调整layout_width,layout_height属性,ListView列表只显示一列!
在查阅的各种文档和资料后,发现在ScrollView中嵌套ListView空间,无法正确的计算ListView的大小,故可以通过代码,根据当前的ListView的列表项计算列表的尺寸。实现代码如下:
public class MainActivity extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView1);
String[] adapterData = new String[] { "Afghanistan", "Albania",… … "Bosnia"};
listView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,adapterData));
setListViewHeightBasedOnChildren(listView);
}
public void setListViewHeightBasedOnChildren(ListView listView) {
// 获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0, len = listAdapter.getCount(); i < len; i++) {
// listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView);
// 计算子项View 的宽高
listItem.measure(0, 0);
// 统计所有子项的总高度
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
// listView.getDividerHeight()获取子项间分隔符占用的高度
// params.height最后得到整个ListView完整显示需要的高度
listView.setLayoutParams(params);
}
} 运行结果,OK问题搞定,打完收工!