经过一段时间的学习,终于完成了文件系统的一个可视化与文件管理,简单记录一下整个程序的想法。
主要分为以下几个模块:
1.界面搭建
2.目录树可视化
此处通过QDir读取文件夹,然后通过QStandardItem创建目录树条目,在QTreeView中呈现。源码如下:
void Widget::loadLogFiles(const QString path, QStandardItemModel *model){
model->removeRows(0, model->rowCount());
QDir d(path);
d.setFilter(QDir::Files | QDir::Hidden |
QDir::NoSymLinks | QDir::AllDirs|
QDir::NoDotAndDotDot);
const QFileInfoList list = d.entryInfoList();
int i=0;
while(i<list.count()){
QStyle *style = QApplication::style();
QString fileType;
QFileInfo file = list[i];
LogStandardItem *dir = new LogStandardItem(file.fileName());
dir->setCheckable(true);
dir->setToolTip(file.path()+"/"+file.fileName());
if(file.isDir()){
dir->setIcon(style->standardIcon(QStyle::SP_DirIcon));
fileType = "日志文件夹";
}else{
dir->setIcon(style->standardIcon(QStyle::SP_FileIcon));
fileType = file.suffix();
}
QString sizeItem = QString::number(file.size());
int rowItem = model->indexFromItem(dir).row();
QString timeItem = QDateTime(file.lastModified()).
toString("yyyy-MM-dd hh:mm:ss");
model->appendRow(dir);
model->setItem(rowItem, 1, new LogStandardItem(sizeItem));
model->setItem(rowItem, 3, new LogStandardItem(timeItem));
model->setItem(rowItem, 2, new LogStandardItem(fileType));
if(file.isDir()) loadLogFiles(file.path()+"/"+file.fileName(), dir);
i++;
}
}
void Widget::loadLogFiles(const QString path, QStandardItem *model){
QDir d(path);
d.setFilter(QDir::Files | QDir::Hidden |
QDir::NoSymLinks | QDir::AllDirs|
QDir::NoDotAndDotDot);
const QFileInfoList list = d.entryInfoList();
int i=0;
while(i<list.count()){
QStyle *style = QApplication::style();
QString fileType;
QFileInfo file = list[i];
LogStandardItem *dir = new LogStandardItem(file.fileName());
dir->setCheckable(true);
dir->setToolTip(file.path()+"/"+file.fileName());
if(file.isDir()){
dir->setIcon(style->standardIcon(QStyle::SP_DirIcon));
fileType = "日志文件夹";
}else{
dir->setIcon(style->standardIcon(QStyle::SP_FileIcon));
fileType = file.suffix();
}
QString sizeItem = QString::number(file.size());
QString timeItem = QDateTime(file.lastModified()).
toString("yyyy-MM-dd hh:mm:ss");
model->appendRow(dir);
int rowItem = dir->index().row();
model->setChild(rowItem, 1, new LogStandardItem(sizeItem));
model->setChild(rowItem, 3, new LogStandardItem(timeItem));
model->setChild(rowItem, 2, new LogStandardItem(fileType));
if(file.isDir()) loadLogFiles(dir->toolTip(), dir);
i++;
}
}
3.复选框三态的实现
由于设置复选框是针对每一个条目QStandardItem的,其本身是没有半选状态的,所以我们要重新实现QStandardItem的三态,从而让其具有父子之间的联系。我们创建一个继承QStandardItem类的子类,重写其setData函数,代码如下:
void LogStandardItem::setData(const QVariant &value, int role){
if(role == Qt::CheckStateRole){
Qt::CheckState CheckState = (Qt::CheckState) value.toInt();
switch(CheckState){
case Qt::Unchecked:{
for(int i=0; i<rowCount(); i++){
child(i)->setData(Qt::Unchecked, Qt::CheckStateRole);
}
QStandardItem::setData(value, role);
if(parent()) parent()->setData(Qt::PartiallyChecked, role);
}
return;
case Qt::PartiallyChecked:{
Qt::CheckState current_state = checkState();
int CheckedNum = 0;
int UnCheckedNum = 0;
bool isPartially = false;
Qt::CheckState ChildState;
int m_rowCount = rowCount();
for(int i=0; i<m_rowCount; i++){
ChildState = child(i)->checkState();
switch(ChildState){
case Qt::PartiallyChecked: isPartially = true;break;
case Qt::Unchecked: UnCheckedNum++;break;
case Qt::Checked: CheckedNum++; break;
default: CheckedNum++;break;
}
}
Qt::CheckState NowState;
if(isPartially) NowState = Qt::PartiallyChecked;
else if(UnCheckedNum == m_rowCount) NowState = Qt::Unchecked;
else if(CheckedNum == m_rowCount) NowState = Qt::Checked;
else NowState = Qt::PartiallyChecked;
if(current_state != NowState){
QStandardItem::setData(NowState, role);
if(parent()) parent()->setData(Qt::PartiallyChecked, role);
}
}
return;
case Qt::Checked:{
for(int i=0, num = rowCount(); i<num; i++){
child(i)->setData(Qt::Checked, Qt::CheckStateRole);
}
QStandardItem::setData(value, role);
if(parent()) parent()->setData(Qt::PartiallyChecked, role);
}
return;
default:
break;
}
}
QStandardItem::setData(value, role);
}
实现了复选框的三态,其实就可以实现删除选中的文件或者文件夹的功能了。
4.双击显示日志内容
对于此功能,我们采用子线程去读取日志内容,然后将读取的内容显示于控件当中。首先我们创建关于此功能的信号与槽:
connect(m_pLogTree, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(loadLogContent(QModelIndex)));
然后将所点击的目录对应的路径传输于子线程,设置信号与槽,在子线程读取完成以后主线程将内容显示。槽函数如下:
void Widget::loadLogContent(const QModelIndex &index){
QStandardItem *item = m_pLogTreeModel->itemFromIndex(index);
QFileInfo f(item->toolTip());
if(f.isFile()){
connect(&m_tShowLogContentThread, SIGNAL(readOverSignal()),
this, SLOT(displayContent()));
m_pContentDisplayWid->setToolTip(item->toolTip());
m_tShowLogContentThread.m_sPath = item->toolTip();
m_tShowLogContentThread.start();
m_tShowLogContentThread.wait();
m_pStatusBar->setText("正在浏览日志:"+f.fileName());
}
}
在子线程中读取:
void ReadLogThread::run(){
mutex.lock();
QFile file(m_sPath);
QTextStream contentRead(&file);
QString content;
m_lLogContent.clear();
while(!contentRead.atEnd()){
content = contentRead.readLine();
m_lLogContent.append(content);
}
emit readOverSignal();
mutex.unlock();
}
通过信号readOverSignal激活主线程槽函数displayContent,通过其来将内容显示于QListWidget中:
void Widget::displayContent(){
QStringList disContent = m_tShowLogContentThread.m_lLogContent;
// 如果日志数大于0,展示第一页,如果日志数=0, 依然显示0
if(disContent.length() == 0) FlushContentPage(0);
else FlushContentPage(1);
}
void Widget::FlushContentPage(int page){
m_nContentPage = page;
while(m_pContentDisplayWid->count()>0){
QListWidgetItem *item = m_pContentDisplayWid->takeItem(0);
delete item;
}
if(m_nContentPage>0){
QStringList disContent = m_tShowLogContentThread.m_lLogContent;
for(int i=(m_nContentPage-1)*19; i<m_nContentPage*19 &&
i<m_tShowLogContentThread.m_lLogContent.length(); i++)
{
QListWidgetItem *Item = new QListWidgetItem(m_pContentDisplayWid);
Item->setSizeHint(QSize(0, 20));
Item->setText(qPrintable(m_tShowLogContentThread.m_lLogContent[i]));
m_pContentDisplayWid->addItem(Item);
}
}
QString pageInformation = QString::number(m_nContentPage) + "/" +
QString::number(qCeil(m_tShowLogContentThread.
m_lLogContent.length()*1.0/19*1.0));
m_pShowPg->setText(pageInformation);
}
5.为QListWidget增加翻页功能
先创建控件,通过QSignalMapper 将多个按钮统一到一块,点击不同的按钮,QSignalMapper则会得到不同的索引值,从而根据索引值,来判断索要进行的操作。
QSignalMapper *btMapper = new QSignalMapper;
connect(m_pDelPgsBtn, SIGNAL(clicked(bool)), btMapper, SLOT(map()));
connect(m_pDelPgBtn, SIGNAL(clicked(bool)), btMapper, SLOT(map()));
connect(m_pAddPgBtn, SIGNAL(clicked(bool)), btMapper, SLOT(map()));
connect(m_pAddPgsBtn, SIGNAL(clicked(bool)), btMapper, SLOT(map()));
btMapper->setMapping(m_pDelPgsBtn, 0);
btMapper->setMapping(m_pDelPgBtn, 1);
btMapper->setMapping(m_pAddPgBtn, 2);
btMapper->setMapping(m_pAddPgsBtn, 3);
connect(btMapper, SIGNAL(mapped(int)), this, SLOT(TurnPage(int)));
然后创建对应的槽函数,以实现翻页功能:
void Widget::TurnPage(const int index){
int page = m_nContentPage;
switch (index) {
case 0: //向前翻十页
if(page >10) FlushContentPage(page-10);
else if(page > 0) FlushContentPage(1);
else FlushContentPage(0);
break;
case 1: // 向前翻一页
if(page >1) FlushContentPage(page-1);
else if(page == 0) FlushContentPage(0);
break;
case 2: // 向后翻一页
if(page < qCeil(m_tShowLogContentThread.m_lLogContent.length() * 1.0/19 * 1.0) && page != 0)
FlushContentPage(page+1);
else if(page == 0) FlushContentPage(0);
break;
case 3: // 向后翻十页
if(page <= qCeil(m_tShowLogContentThread.m_lLogContent.length() * 1.0 / 19 * 1.0) - 10 && page != 0)
FlushContentPage(page+10);
else if(page != 0){page = qCeil(m_tShowLogContentThread.m_lLogContent.length()*1.0/19*1.0);
FlushContentPage(page);
}else FlushContentPage(0);
break;
}
}
6.检测文件夹
如果文件夹中有新的文件,则展示于程序中,如果程序中某些对应的文件夹删除,则将其一并删除:同样此功能采用子线程来实现,子线程我们设置为2秒钟扫描一次文件夹:
void Widget::checkFileAndTree(){
QTimer *tim = new QTimer();
tim->start(2000);
connect(tim, SIGNAL(timeout()), this, SLOT(checkFileAndTreeOnce()));
}
void Widget::checkFileAndTreeOnce(){
m_tUpdateTreeThread.start();
m_tUpdateTreeThread.wait();
}
检测函数放于子线程中,此处呈现部分代码,其他的无非QStandardItemModel换成QStandardItem及之前的添加函数:
void UpdateTreeThread::run(){
mutex.lock();
checkFileInTree(m_sPath, m_tModel);
checkTreeInFile(m_tModel);
mutex.unlock();
}
void UpdateTreeThread::checkFileInTree(QString path, QStandardItemModel *model){
QDir d(path);
d.setFilter(QDir::Files | QDir::Hidden |
QDir::NoSymLinks | QDir::AllDirs|
QDir::NoDotAndDotDot);
const QFileInfoList list = d.entryInfoList();
if(!list.length()) model->removeRows(0, model->rowCount());
else if(!model->rowCount()) addLists(path, model);
else{
int i=0;
while(i<list.length()){
QFileInfo file = list[i];
QString filePath = file.path()+"/"+file.fileName();
bool fileExist = false;
int j=0;
while(j<model->rowCount()){
if(filePath == model->item(j)->toolTip()){
if(!fileExist){
fileExist = true;
if(file.isDir()) checkFileInTree(filePath, model->item(j));
j++;
}else model->removeRow(model->item(j)->row());
}else j++;
}
if(!fileExist){
QStyle *style = QApplication::style();
QString fileType;
LogStandardItem *item = new LogStandardItem(file.fileName());
item->setCheckable(true);
item->setToolTip(file.path()+"/"+file.fileName());
if(file.isDir()){
item->setIcon(style->standardIcon(QStyle::SP_DirIcon));
fileType = "日志文件夹";
}else{
item->setIcon(style->standardIcon(QStyle::SP_FileIcon));
fileType = file.suffix();
}
QString sizeItem = QString::number(file.size());
QString timeItem = QDateTime(file.lastModified()).
toString("yyyy-MM-dd hh:mm:ss");
model->appendRow(item);
int rowItem = item->index().row();
model->setItem(rowItem, 1, new LogStandardItem(sizeItem));
model->setItem(rowItem, 3, new LogStandardItem(timeItem));
model->setItem(rowItem, 2, new LogStandardItem(fileType));
if(file.isDir()) addLists(item->toolTip(), item);
}
i++;
}
}
}
void UpdateTreeThread::checkTreeInFile(QStandardItemModel *model){
if(model->rowCount() == 0)return;
int i=0;
while(i<model->rowCount()){
QString path = model->item(i)->toolTip();
QFileInfo file(path);
if(!file.exists()) model->removeRow(model->item(i)->row());
else{
if(file.isDir()) checkTreeInFile(model->item(i));
i++;
}
}
}
最终就可以实现上述文件呈现及对应的操作的功能。
这段程序也有不足的地方,记录一下,以供以后努力:
1.代码模块化能力较弱
2.对于线程的使用水平太低
3.对于程序运行的成本考虑太少,严重浪费计算资源