上传功能和秒传功能
上传协议
文件上传
客户端
- url: http://127.0.0.1:80/upload
- post数据:
------WebKitFormBoundary88asdgewtgewx\r\n
Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n
Content-Type: application/octet-stream\r\n
\r\n
真正的文件内容\r\n
------WebKitFormBoundary88asdgewtgewx
服务器端回传数据
成功 |
{"code":"008"} |
失败 |
{"code":"009"} |
MD5 秒传
客户端
- url: http://127.0.0.1:80/md5
- post数据:
{
user:xxxx,
token:xxxx,
md5:xxx,
fileName: xxx
}
服务器端
文件已存在: |
{"code":"005"} |
秒传成功: |
{"code":"006"} |
秒传失败: |
{"code":"007"} |
流程图
- 上传的方式有两个——上传按钮和右键菜单
- UploadTask将要上传的信息读取出来,添加到任务队列中
- 使用定时器实时监测任务队列中的任务,每个500毫秒检测一次,检测上传任务,每一次只能上传一个文件
- 上传时先尝试秒传
- 秒传时,服务器先验证客户端发来的http请求里面的token是否正确,如果正确,才能进行后续操作
整体
类的声明
namespace Ui {
class MyFileWg;
}
class MyFileWg : public QWidget
{
Q_OBJECT
public:
explicit MyFileWg(QWidget *parent = 0);
~MyFileWg();
// 初始化listWidget文件列表
void initListWidget();
// 添加右键菜单
void addActionMenu();
//==========>上传文件处理<==============
// 添加需要上传的文件到上传任务列表
void addUploadFiles();
// 设置md5信息的json包
QByteArray setMd5Json(QString user, QString token, QString md5, QString fileName);
// 上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
void uploadFilesAction();
// 上传真正的文件内容,不能秒传的前提下
void uploadFile(UploadFileInfo *info);
//==========>文件item展示<==============
// 清空文件列表
void clearFileList();
// 清空所有item项目
void clearItems();
// 添加上传文件项目item
void addUploadItem(QString iconPath=":/images/upload.png", QString name="上传文件");
// 文件item展示
void refreshFileItems();
//==========>显示用户的文件列表<==============
// desc是descend 降序意思
// asc 是ascend 升序意思
// Normal:普通用户列表,PvAsc:按下载量升序, PvDesc:按下载量降序
enum Display{Normal, PvAsc, PvDesc};
// 得到服务器json文件
QStringList getCountStatus(QByteArray json);
// 显示用户的文件列表
void refreshFiles(Display cmd=Normal);
// 设置json包
QByteArray setGetCountJson(QString user, QString token);
// 设置json包
QByteArray setFilesListJson(QString user, QString token, int start, int count);
// 获取用户文件列表
void getUserFilesList(Display cmd=Normal);
// 解析文件列表json信息,存放在文件列表中
void getFileJsonInfo(QByteArray data);
//==========>分享、删除文件<==============
// 处理选中的文件
void dealSelectdFile(QString cmd="分享");
QByteArray setDealFileJson(QString user, QString token, QString md5, QString filename);//设置json包
//==========>分享文件<==============
void shareFile(FileInfo *info); //分享某个文件
//==========>删除文件<==============
void delFile(FileInfo *info); //删除某个文件
//==========>获取文件属性<==============
void getFileProperty(FileInfo *info); //获取属性信息
//==========>下载文件处理<==============
// 添加需要下载的文件到下载任务列表
void addDownloadFiles();
//下载文件处理,取出下载任务列表的队首任务,下载完后,再取下一个任务
void downloadFilesAction();
//==========>下载文件标志处理<==============
void dealFilePv(QString md5, QString filename); //下载文件pv字段处理
//清除上传下载任务
void clearAllTask();
// 定时检查处理任务队列中的任务
void checkTaskList();
signals:
void loginAgainSignal();
void gotoTransfer(TransferStatus status);
private:
// 右键菜单信号的槽函数
void rightMenu(const QPoint &pos);
private:
Ui::MyFileWg *ui;
Common m_cm;
QNetworkAccessManager* m_manager;
MyMenu *m_menu; // 菜单1
QAction *m_downloadAction; // 下载
QAction *m_shareAction; // 分享
QAction *m_delAction; // 删除
QAction *m_propertyAction; // 属性
MyMenu *m_menuEmpty; // 菜单2
QAction *m_pvAscendingAction; // 按下载量升序
QAction *m_pvDescendingAction; // 按下载量降序
QAction *m_refreshAction; // 刷新
QAction *m_uploadAction; // 上传
long m_userFilesCount; //用户文件数目
int m_start; //文件位置起点
int m_count; //每次请求文件个数
//定时器
QTimer m_uploadFileTimer; //定时检查上传队列是否有任务需要上传
QTimer m_downloadTimer; //定时检查下载队列是否有任务需要下载
QList<FileInfo *> m_fileList; //文件列表
};
第一部分——上传按钮
点击上传按钮——>初始化文件列表——>检测触发事件是否为上传事件,如果是,则调用addUploadFiles()函数将需要上传的文件添加到上传任务列表。
// 初始化listWidget文件列表
void MyFileWg::initListWidget()
{
ui->listWidget->setViewMode(QListView::IconMode); //设置显示图标模式
ui->listWidget->setIconSize(QSize(80, 80)); //设置图标大小
ui->listWidget->setGridSize(QSize(100, 120)); //设置item大小
// 设置QLisView大小改变时,图标的调整模式,默认是固定的,可以改成自动调整
ui->listWidget->setResizeMode(QListView::Adjust); //自动适应布局
// 设置列表可以拖动,如果想固定不能拖动,使用QListView::Static
ui->listWidget->setMovement(QListView::Static);
// 设置图标之间的间距, 当setGridSize()时,此选项无效
ui->listWidget->setSpacing(30);
// listWidget 右键菜单
// 发出 customContextMenuRequested 信号
ui->listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &MyFileWg::rightMenu);
// 点中列表中的上传文件图标。点击事件被触发,会发送QListWidget::itemPressed信号
connect(ui->listWidget, &QListWidget::itemPressed, this, [=](QListWidgetItem* item)
{
QString str = item->text();
//如果事件是上传文件
if(str == "上传文件")
{
//添加需要上传的文件到上传任务列表
addUploadFiles();
}
});
}
第二部分——右键菜单
在文件处右键菜单,选择上传,即可调用addUploadFiles()函数则调用addUploadFiles()函数将需要上传的文件添加到上传任务列表。
注:右键菜单(在按钮处右键和在空白处右键的效果不同,因此是两个右键菜单)
void MyFileWg::addActionMenu()
{
//===================菜单1==================
m_menu = new MyMenu( this );
// 初始化菜单项
m_downloadAction = new QAction("下载", this);
m_shareAction = new QAction("分享", this);
m_delAction = new QAction("删除", this);
m_propertyAction = new QAction(QString::fromLocal8Bit("属性"), this);
// 动作1添加到菜单1
m_menu->addAction(m_downloadAction);
m_menu->addAction(m_shareAction);
m_menu->addAction(m_delAction);
m_menu->addAction(m_propertyAction);
//===================菜单2===================
m_menuEmpty = new MyMenu( this );
// 动作2
m_pvAscendingAction = new QAction("按下载量升序", this);
m_pvDescendingAction = new QAction("按下载量降序", this);
m_refreshAction = new QAction("刷新", this);
m_uploadAction = new QAction("上传", this);
// 动作2添加到菜单2
m_menuEmpty->addAction(m_pvAscendingAction);
m_menuEmpty->addAction(m_pvDescendingAction);
m_menuEmpty->addAction(m_refreshAction);
m_menuEmpty->addAction(m_uploadAction);
//=====================信号与槽===================
// 下载
connect(m_downloadAction, &QAction::triggered, [=]()
{
cout << "下载动作";
//添加需要下载的文件到下载任务列表
addDownloadFiles();
});
// 分享
connect(m_shareAction, &QAction::triggered, [=]()
{
cout << "分享动作";
dealSelectdFile("分享"); //处理选中的文件
});
// 删除
connect(m_delAction, &QAction::triggered, [=]()
{
cout << "删除动作";
dealSelectdFile("删除"); //处理选中的文件
});
// 属性
connect(m_propertyAction, &QAction::triggered, [=]()
{
cout << QString::fromLocal8Bit("属性动作");
dealSelectdFile(QString(QString::fromLocal8Bit("属性"))); //处理选中的文件
});
// 按下载量升序
connect(m_pvAscendingAction, &QAction::triggered, [=]()
{
cout << QString::fromLocal8Bit("按下载量升序");
refreshFiles(PvAsc);
});
// 按下载量降序
connect(m_pvDescendingAction, &QAction::triggered, [=]()
{
cout << QString::fromLocal8Bit("按下载量降序");
refreshFiles(PvDesc);
});
//刷新
connect(m_refreshAction, &QAction::triggered, [=]()
{
cout << "refresh";
//显示用户的文件列表
refreshFiles();
});
//上传
connect(m_uploadAction, &QAction::triggered, [=]()
{
cout << "upload";
//添加需要上传的文件到上传任务列表
addUploadFiles();
});
}
第三部分——向任务队列中添加任务
addUploadFiles()函数调用UploadTask类的appendUploadList函数,将需要上传的文件添加到上传任务列表。
// 添加需要上传的文件到上传任务列表
void MyFileWg::addUploadFiles()
{
//用于切换界面的信号。点击上传之后进行界面切换,这是后续的用户友好优化。
//切换到让用户看到上传界面的进度条
emit gotoTransfer(TransferStatus::Uplaod);
//获取上传列表实例,getInstance——单例模式
UploadTask *uploadList = UploadTask::getInstance();
if(uploadList == NULL)
{
cout << "UploadTask::getInstance() == NULL";
return;
}
QStringList list = QFileDialog::getOpenFileNames();
for(int i = 0; i < list.size(); ++i)
{
//cout << "所选文件为:"<<list.at(i);
// -1: 文件大于30m
// -2:上传的文件是否已经在上传队列中
// -3: 打开文件失败
// -4: 获取布局失败
int res = uploadList->appendUploadList(list.at(i));
if(res == -1)
{
QMessageBox::warning(this, QString::fromLocal8Bit("文件太大"),
QString::fromLocal8Bit("文件大小不能超过30m"));
}
else if(res == -2)
{
QMessageBox::warning(this, QString::fromLocal8Bit("添加失败"),
QString::fromLocal8Bit("上传文件是否已在上传队列中"));
}
else if(res == -3)
{
cout << "打开文件失败";
}
else if(res == -4)
{
cout << "获取布局失败";
}
}
}
UploadTask类的声明
//上传文件信息
struct UploadFileInfo
{
QString md5; //文件md5码
QFile *file; //文件指针
QString fileName; //文件名字
qint64 size; //文件大小
QString path; //文件路径
bool isUpload; //是否已经在上传
DataProgress *dp; //上传进度控件
};
//上传任务列表类,单例模式,一个程序只能有一个上传任务列表
//UploadTask将要上传的信息读取出来,添加到任务队列中
class UploadTask
{
public:
static UploadTask *getInstance(); //保证唯一一个实例
//追加上传文件到上传列表中
//参数:path 上传文件路径
//返回值:成功为0
//失败:
// -1: 文件大于30m
// -2:上传的文件是否已经在上传队列中
// -3: 打开文件失败
// -4: 获取布局失败
int appendUploadList(QString path);
bool isEmpty(); //判断上传队列释放为空
bool isUpload(); //是否有文件正在上传
//取出第0个上传任务,如果任务队列没有任务在上传,设置第0个任务上传
UploadFileInfo * takeTask();
//删除上传完成的任务
void dealUploadTask();
void clearList(); //清空上传列表
private:
UploadTask() //构造函数为私有
{}
~UploadTask() //析构函数为私有
{ }
//静态数据成员,类中声明,类外必须定义
static UploadTask *instance;
//它的唯一工作就是在析构函数中删除Singleton的实例
class Garbo
{
public:
~Garbo()
{
if(NULL != UploadTask::instance)
{
UploadTask::instance->clearList();
delete UploadTask::instance;
UploadTask::instance = NULL;
cout << "instance is detele";
}
}
};
//定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
//static类的析构函数在main()退出后调用
static Garbo temp; //静态数据成员,类中声明,类外定义
QList <UploadFileInfo *> list; //上传任务列表
};
appendUploadList函数的实现
先检测文件大小,不能超过30M。之后查看该文件是否已在上传队列中,如果不在上传队列中,则将文件信息添加到上传队列(此时没有进行上传操作)
//追加上传文件到上传列表中,此时还没开始上传
//参数:path 上传文件路径
//失败:
// -1: 文件大于30m
// -2:上传的文件是否已经在上传队列中
// -3: 打开文件失败
// -4: 获取布局失败
int UploadTask::appendUploadList(QString path)
{
qint64 size = QFileInfo( path ).size();
if(size > 30*1024*1024) //最大文件只能是30M
{
cout << "file is to big\n";
return -1;
}
//遍历查看一下,下载的文件是否已经在上传队列中
for(int i = 0; i != list.size(); ++i)
{
if( list.at(i)->path == path) //list[i]->path
{
cout << QFileInfo( path ).fileName() << " 已经在上传队列中 ";
return -2;
}
}
QFile *file = new QFile(path); //文件指针分配空间
if(!file->open(QIODevice::ReadOnly))
{
//如果打开文件失败,则删除 file,并使 file 指针为 0,然后返回
cout << "file open error";
delete file;
file = NULL;
return -3;
}
//获取文件属性信息
QFileInfo info(path);
//动态创建节点
Common mc;
UploadFileInfo *tmp = new UploadFileInfo;
tmp->md5 = mc.getFileMd5(path); //获取文件的md5码, common.h
tmp->file = file; //文件指针
tmp->fileName = info.fileName();//文件名字
tmp->size = size; //文件大小
tmp->path = path; //文件路径
tmp->isUpload = false;//当前文件没有被上传
//进度条是为了上传列表准备的。进度条是被动态的插入到上传列表中的
//有多少个带上传文件,就要做几个进度条
DataProgress *p = new DataProgress; //创建进度条
p->setFileName(tmp->fileName); //设置文件名字
tmp->dp = p;
//获取布局
UploadLayout *pUpload = UploadLayout::getInstance();
if(pUpload == NULL)
{
cout << "UploadTask::getInstance() == NULL";
return -4;
}
QVBoxLayout *layout = (QVBoxLayout*)pUpload->getUploadLayout();
// 添加到布局, 最后一个是弹簧, 插入到弹簧上边
//一个布局里面可以插入窗口(进度条),窗口里面可以设置布局,布局里面可以再插入窗口
//类似于做界面,先做一个窗口,然后设置他为水平布局或者垂直布局,然后再在布局中添加窗口
//这里就是将进度条插入
layout->insertWidget(layout->count()-1, p);
cout << tmp->fileName.toUtf8().data() << "已经放在上传列表";
//插入到任务队列,每个temp都是一个UploadFileInfo(一个结构体),即当前要上传的文件信息
list.append(tmp);
return 0;
}
第四部分——定时器检测任务队列中是否有任务需要上传
上传和下载都有相应的队列,因此有两个定时器分别检测这两个队列。
每500毫秒检测一次任务队列,当有任务需要上传,则调用uploadFilesAction();函数进行文件上传
// 定时检查处理任务队列中的任务
void MyFileWg::checkTaskList()
{
//定时检查上传队列是否有任务需要上传
connect(&m_uploadFileTimer, &QTimer::timeout, [=]()
{
//上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
uploadFilesAction();
});
// 启动定时器,500毫秒间隔
// 每个500毫秒,检测上传任务,每一次只能上传一个文件
m_uploadFileTimer.start(500);
// 定时检查下载队列是否有任务需要下载
connect(&m_downloadTimer, &QTimer::timeout, [=]()
{
// 下载文件处理,取出下载任务列表的队首任务,下载完后,再取下一个任务
downloadFilesAction();
});
// 启动定时器,500毫秒间隔
// 每个500毫秒,检测下载任务,每一次只能下载一个文件
m_downloadTimer.start(500);
}
第五部分——md5尝试秒传
- 首先获取上传队列,如果获取成功,则检查队列是否为空。
- 如果队列非空且此时没有上传任务在进行上传操作,则首先尝试秒传。
- 如果服务器中存在该文件,则进行秒传,否则进行文件上传操作。
// 上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
void MyFileWg::uploadFilesAction()
{
// 获取上传列表实例
UploadTask *uploadList = UploadTask::getInstance();
//判断单例对象是否获取失败
if(uploadList == NULL)
{
cout << "UploadTask::getInstance() == NULL";
return;
}
// 如果队列为空,无上传任务,终止程序
if( uploadList->isEmpty() )
{
return;
}
// 查看是否有上传任务,若此时有文件正在上传,则不能上传
if( uploadList->isUpload() )
{
return;
}
//先尝试秒传
// 获取登陆信息实例
LoginInfoInstance *login = LoginInfoInstance::getInstance(); //获取单例
// url
QNetworkRequest request;
QString url = QString("http://%1:%2/md5").arg( login->getIp() ).arg( login->getPort() );
request.setUrl( QUrl( url )); //设置url
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
// 取出第0个上传任务,如果任务队列没有任务在上传,设置第0个任务上传
UploadFileInfo *info = uploadList->takeTask();
// post数据包
QByteArray array = setMd5Json(login->getUser(), login->getToken(), info->md5, info->fileName);
// 发送post请求
QNetworkReply *reply = m_manager->post(request, array);
if(reply == NULL)
{
cout << "reply is NULL";
return;
}
// 信号和槽连接
//捕捉QNetworkReply::finished信号。当服务器进行用户信息检测之后,会发送信号给客户端
connect(reply, &QNetworkReply::finished, [=]()
{
//先判断是否出错
if (reply->error() != QNetworkReply::NoError) //有错误
{
cout << reply->errorString();
reply->deleteLater(); //释放资源
return;
}
//读取服务器返回的数据
QByteArray array = reply->readAll();
//cout << array.data();
reply->deleteLater(); //释放资源
/*
秒传文件:
文件已存在:{"code":"005"}
秒传成功: {"code":"006"}
秒传失败: {"code":"007"}
token验证失败:{"code":"111"}
*/
if("006" == m_cm.getCode(array) ) //common.h
{
//秒传文件成功
//cout << info->fileName.toUtf8().data() << "-->秒传成功";
m_cm.writeRecord(login->getUser(), info->fileName, "006");
//删除已经完成的上传任务
uploadList->dealUploadTask();
}
else if("007" == m_cm.getCode(array) )
{
// 说明后台服务器没有此文件,因此秒传失败,需要真正的文件上传
uploadFile(info);
}
else if("005" == m_cm.getCode(array) )// "005", 上传的文件已存在
{
//QMessageBox::information(this, "文件已存在", QString("%1 已存在").arg(info->fileName))
//cout << QString("%1 已存在").arg(info->fileName).toUtf8().data();
m_cm.writeRecord(login->getUser(), info->fileName, "005");
//删除已经完成的上传任务
uploadList->dealUploadTask();
}
else if("111" == m_cm.getCode(array)) //token验证失败
{
QMessageBox::warning(this, "账户异常", "请重新登陆!!!");
emit loginAgainSignal(); //发送重新登陆信号
return;
}
});
}
第六部分——对于无法秒传的文件进行文件上传操作
- 取出文件信息
- 获取用户信息实例
- 制作http请求,并将请求发送给服务器
- 进行文件上传,并通过服务器返回的信号实时更新上传进度条
- 文件上传完毕,服务器发送信号
- 分析服务器返回的信号,确定文件是否上传成功
- 删除上传队列中的任务
// 上传真正的文件内容,不能秒传的前提下
void MyFileWg::uploadFile(UploadFileInfo *info)
{
//取出上传任务
QFile *file = info->file; //文件指针
QString fileName = info->fileName; //文件名字
QString md5 = info->md5; //文件md5码
qint64 size = info->size; //文件大小
DataProgress *dp = info->dp; //进度条控件
QString boundary = m_cm.getBoundary(); //产生分隔线
//获取登陆信息实例
LoginInfoInstance *login = LoginInfoInstance::getInstance(); //获取单例
QByteArray data;
/*
------WebKitFormBoundary88asdgewtgewx\r\n
Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n
Content-Type: application/octet-stream\r\n
\r\n
真正的文件内容\r\n
------WebKitFormBoundary88asdgewtgewx
*/
//第1行,分隔线
data.append(boundary);
data.append("\r\n");
//上传文件信息
data.append("Content-Disposition: form-data; ");
data.append( QString("user=\"%1\" ").arg( login->getUser() ) ); //上传用户
data.append( QString("filename=\"%1\" ").arg(fileName) ); //文件名字
data.append( QString("md5=\"%1\" ").arg(md5) ); //文件md5码
data.append( QString("size=%1").arg(size) ); //文件大小
data.append("\r\n");
data.append("Content-Type: application/octet-stream");
data.append("\r\n");
data.append("\r\n");
// 上传文件内容
data.append( file->readAll() ); //文件内容
data.append("\r\n");
// 结束分隔线
data.append(boundary);
QNetworkRequest request; //请求对象
//http://127.0.0.1:80/upload
QString url = QString("http://%1:%2/upload").arg(login->getIp()).arg(login->getPort());
request.setUrl(QUrl( url )); //设置url
// qt默认的请求头
//request.setRawHeader("Content-Type","text/html");
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
// 发送post请求
QNetworkReply * reply = m_manager->post( request, data );
if(reply == NULL)
{
cout << "reply == NULL";
return;
}
// 有可用数据更新时。QNetworkReply::uploadProgress携带了已经上传了多少文件的信号
// 进度条的增长就靠这个函数了。bytesRead已经上传的数据量,totalBytes文件总量
connect(reply, &QNetworkReply::uploadProgress, [=](qint64 bytesRead, qint64 totalBytes)
{
if(totalBytes != 0) //这个条件很重要
{
//cout << bytesRead/1024 << ", " << totalBytes/1024;
//因为值比较大,所以除以1024,这样进度条分的份数可以少一点。不除1024也可以
dp->setProgress(bytesRead/1024, totalBytes/1024); //设置进度条长度
}
});
// 获取请求的数据完成时,就会发送信号SIGNAL(finished())
connect(reply, &QNetworkReply::finished, [=]()
{
if (reply->error() != QNetworkReply::NoError) //有错误
{
cout << reply->errorString();
reply->deleteLater(); //释放资源
return;
}
QByteArray array = reply->readAll();
reply->deleteLater();
/*
上传文件:
成功:{"code":"008"}
失败:{"code":"009"}
*/
if("008" == m_cm.getCode(array) ) //common.h
{
//cout << fileName.toUtf8().data() <<" ---> 上传完成";
m_cm.writeRecord(login->getUser(), info->fileName, "008");
}
else if("009" == m_cm.getCode(array) )
{
//cout << fileName.toUtf8().data() << " ---> 上传失败";
m_cm.writeRecord(login->getUser(), info->fileName, "009");
}
//获取上传列表实例。因为任务在单例里面,所以要调用单例去删除里面的任务
UploadTask *uploadList = UploadTask::getInstance();
if(uploadList == NULL)
{
cout << "UploadTask::getInstance() == NULL";
return;
}
uploadList->dealUploadTask(); //删除已经完成的上传任务
}
);
}
上传功能改进——生产者消费者
- 不使用定时器去检测任务,而是使用条件变量。
- 往任务队列中放任务的就是生产者。
- 如果任务队列为空,则消费者线程等待(wait)
- 如果队列不为空,则使用条件变量唤醒消费者(signal——至少唤醒一个消费者、broadcast唤醒所有消费者)
- 线程池中任务管理使用的是条件变量。
QT中的条件变量——QWaitCondition Class
QT中的读写锁——QReadWriteLock Class
通过代码去设置ui界面布局的原理
- 窗口中设置布局
- 布局中可以添加窗口
- 窗口 -> 从QWidget派生的类
- 布局-> QLayout派生的类
上传布局分析
UploadLayout.h
#ifndef UPLOADLAYOUT_H
#define UPLOADLAYOUT_H
#include "common.h"
#include <QVBoxLayout>
//上传进度布局类,单例模式
class UploadLayout
{
public:
static UploadLayout *getInstance(); //保证唯一一个实例
void setUploadLayout(QWidget *p); //设置布局
QLayout *getUploadLayout(); //获取布局
private:
UploadLayout()
{ }
~UploadLayout() //析构函数为私有
{ }
//静态数据成员,类中声明,类外必须定义
static UploadLayout *instance;
QLayout *m_layout;
QWidget *m_wg;
//它的唯一工作就是在析构函数中删除Singleton的实例
class Garbo
{
public:
~Garbo()
{
if(NULL != UploadLayout::instance)
{
delete UploadLayout::instance;
UploadLayout::instance = NULL;
cout << "instance is detele";
}
}
};
//定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
//static类的析构函数在main()退出后调用
static Garbo temp; //静态数据成员,类中声明,类外定义
};
#endif // UPLOADLAYOUT_H
UploadLayout.cpp
#include "uploadlayout.h"
//静态数据成员,类中声明,类外必须定义
UploadLayout * UploadLayout::instance = new UploadLayout;
//static类的析构函数在main()退出后调用
UploadLayout::Garbo UploadLayout::temp; //静态数据成员,类中声明,类外定义
UploadLayout * UploadLayout::getInstance()
{
return instance;
}
//设置布局
//给当前布局添加窗口,因为布局要被设置到上传窗口中,因此p是指向上传列表窗口的指针
void UploadLayout::setUploadLayout(QWidget *p)
{
m_wg = new QWidget(p);
//获取当前窗口的布局
QLayout* layout = p->layout();
//向布局中添加窗口(就是进度条窗口)
layout->addWidget(m_wg);
//设置窗口的边界
layout->setContentsMargins(0, 0, 0, 0);
//创建了一个垂直布局
QVBoxLayout* vlayout = new QVBoxLayout;
// 将垂直布局设置给窗口
m_wg->setLayout(vlayout);
// 边界间隔,设置该垂直布局中的窗口没有缝隙
vlayout->setContentsMargins(0, 0, 0, 0);
m_layout = vlayout;
// 添加弹簧
vlayout->addStretch();
}
//获取布局
QLayout *UploadLayout::getUploadLayout()
{
return m_layout;
}