⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生:man_student:。 如果觉得本文能帮到您,
麻烦点个赞
????呗!
近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支持一下呗。????:star::heart:
(学习笔记)
Qt实践项目:仿Everything软件实现一个QtEverything
一、前言:
最近刚好重新学习了一下Qt的知识,觉得知识点之间还是比较散乱,网上大部份书籍的例子也比较零碎,想找个一个实战的项目将一些知识点穿起来,同时项目本身又比较有趣,可以进行不断地优化,发现文件查找器系统是个很有趣的实践项目。
作为Qt的入门项目,我个人觉得这个项目真的是太棒了,涵盖了许多知识点,同时还可以对此不断地进行优化。
本文将对Qt文件查找系统进行技术总结。该系统包含了许多技术细节,包括文件管理器的打开、记忆下拉框内容、filetable
的使用、文件信息显示、文件图标获取、自定义排序、QFileSystemWatch
监视文件目录、变化后重新加载目录、库的制作、qmake使用、makefile生成外部库后引入到项目中、Qt调用第三方库的方法、表格激活状态下选中右键、右键菜单、删除功能实现、启动文件、复制文件名以及检测剪切板、快捷键使用、快捷键冲突、窗口焦点捕获、托盘、系统消息设置、正则化表达式的书写、正则化表达式的应用、模糊匹配中按照顺序进行匹配、模糊匹配中遇到空格的处理、模糊匹配算法的优化、以及如何提高模糊查询的效率(QtConcurrent)。
如果你觉得这篇文章帮到了你,欢迎
点赞+收藏+评论
, 对系统感兴趣的,欢迎订阅这个专栏欧,后面代码
会放到专栏中,并在这个专栏里进行更新讲解
欧!感谢支持:star::heart:
二、功能点:
- [x] ????基本文件查找功能
- [x] :file_folder: 操作搜索结果
- [x] :keyboard: 快捷键与托盘系统
- [x] :mag: 实现模糊查询
- [x] :speak_no_evil: 拼音转换搜索中文
- [x] :mag:目录更新监控
- [x] :mag:文件查找优化---缓存查询
- [x] :mag:文件查找优化---多线程查找
- [ ] 其他(正在开发)
将作业要求分为了七个功能点,每个功能点下会列出需要地知识点,
功能点的实现是这些知识点的最佳实践
这个项目个人觉得非常有意思,所以花了一些时间去设计,也遇到了挺多Bug的,最近在忙着毕业设计,在论文写完后,后面会尝试对这个项目进行优化地,如果有兴趣地小伙伴可以关注一下专栏后期会一直在上面进行更新地,一周至少更新一篇。
功能点一:文件查找与现实
0、知识点
- 打开文件管理器进行目录选择
- 记忆下拉框的内容
filetable
使用- 文件信息显示
- 文件图标的获取
- 自定义排序
1、 确定需求
要想实现文件查找功能大致分为以下几步:
要使用Qt实现一个类似Everything文件搜索工具,可以按照以下步骤进行:
- 创建一个
Qt GUI
应用程序,并添加一个窗口作为搜索工具的主界面。 - 在主界面中添加一个文本框用于输入搜索关键字,以及一个搜索按钮用于触发搜索操作。
- 在搜索按钮的点击事件中,获取用户输入的搜索关键字,然后遍历文件系统中的所有文件和目录,并筛选出符合搜索关键字的文件和目录。
- 使用
Qt
的QFileSystemModel
类来遍历文件系统中的文件和目录。QFileSystemModel
类是一个模型-视图类,可以用来访问本地文件系统中的文件和目录。 - 在遍历文件系统的过程中,可以使用
QRegExp
类来匹配搜索关键字。QRegExp
类是Qt中的正则表达式类,可以用来进行高级字符串匹配操作。 - 使用Qt的
QTableView
类来显示搜索结果。QTableView
类是一个表格视图类,可以用来显示数据。可以将搜索结果显示为表格,并在表格中显示文件名、文件路径、文件大小和文件修改日期等信息。
关于显示地结果:
- 在搜索栏输入关键字,快速搜索系统桌面目录里的文件和文件夹,展示在列表中,包含程序图标、名称、路径、大小信息。
注:
a. 文件夹无需展示大小
b. 搜索结果无需包含桌面文件夹中的子文件和子文件夹;
文件夹无需显示大小:
// 大小
QTableWidgetItem *sizeItem = nullptr;
// 如果是目录则sizeItem值为空
QFileInfo fileInfo(filePath);
if(fileInfo.isDir()){
// 如果是目录,直接设置为nullptr, 不显示大小
sizeItem = nullptr;
}else{
// 如果是目录,计算文件大小并设置为sizeItem
sizeItem = new QTableWidgetItem(tr("%1 KB")
.arg(int((size + 1023) / 1024)));
sizeItem->setData(absoluteFileNameRole, QVariant(filePath));
sizeItem->setToolTip(toolTip);
sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable);
}
2、 查找和文件目录选择
如果是只通过编写代码的话,就直接通过connect
函数进行槽的绑定就可以啦。
这里主要尝试ui下的槽函数,可视化添加槽。
具体的槽函数实现功能:
// 当查找的按钮被触碰时触发的槽函数
void MainWindow::on_findButton_clicked()
{
// 注意这里用的filesTable 是wiget类型的
ui->filesTable->setRowCount(0);
QString fileName = ui->fileComboBox->currentText();
QString text = ui->textComboBox->currentText();
QString path = QDir::cleanPath(ui->directoryCombox->currentText());
// 查找并显示文件
currentDir = QDir(path);
// 更新目录
watcher.removePath(currentDir.path());
watcher.addPath(path);
findFilesInDirectory(path, ui->fileComboBox->currentText(), ui->textComboBox->currentText());
QStringList files;
// 文件名进行查找文件
findRecursion(path, fileName.isEmpty() ? QStringLiteral("*") : fileName, &files);
// 在已经查找出的文件进一步查找哪些包含了text
if(!text.isEmpty())
files = findFiles(files, text);
files.sort();
showFiles(files);
}
目录选择
void MainWindow::on_broweButton_clicked()
{
QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this,
tr("Find Files"),
QDir::currentPath()));
if(!directory.isEmpty()){
if(ui->directoryCombox->findText(directory) == -1)
ui->directoryCombox->addItem(directory);
ui->directoryCombox->setCurrentIndex(ui->directoryCombox->findText(directory));
}
}
3、自定义排序
// 表头排序指示器
ui->filesTable->setSortingEnabled(true); // 允许排序
ui->filesTable->horizontalHeader()->setSortIndicatorShown(true); // 显示排序指示器
具体实现:
void MainWindow::createFilesTable()
{
ui->filesTable = new QTableWidget(0,3);
ui->filesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
QStringList labels;
labels << tr("Filename") <<tr("Path")<< tr("Size");
ui->filesTable->setHorizontalHeaderLabels(labels);
ui->filesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->filesTable->verticalHeader()->hide();
ui->filesTable->setShowGrid(false);
// 表头排序指示器
// ui->filesTable->setSortingEnabled(true); // 允许排序
// ui->filesTable->horizontalHeader()->setSortIndicatorShown(true); // 显示排序指示器
// 菜单
ui->filesTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->filesTable, &QTableWidget::customContextMenuRequested,
this, &MainWindow::contextMenu);
connect(ui->filesTable, &QTableWidget::cellActivated,
this, &MainWindow::openFileOfItem);
}
不过有点问题:
因为size
字段带了单位后是字符,字符的大小比较会有点问题,因此在实现中先关闭。
解决办法:
1、通过文本排序QTextListFormat
的style
和index
进行排序。
2、lamb表达式对排序规则进行自定义。
4、文件图标获取
QIcon icon = iconProvider.icon(filePath);
具体实现:
void MainWindow::showFiles(const QStringList &paths)
{
QFileIconProvider iconProvider;
for(const QString &filePath : paths){
// 获取图标
const QString toolTip = QDir::toNativeSeparators(filePath);
const QString relativePath = QDir::toNativeSeparators(currentDir.relativeFilePath((filePath)));
const qint64 size = QFileInfo(filePath).size();
QIcon icon = iconProvider.icon(filePath);
QTableWidgetItem *fileNameItem = new QTableWidgetItem(relativePath);
fileNameItem->setData(absoluteFileNameRole, QVariant(filePath));
fileNameItem->setToolTip(toolTip);
fileNameItem->setFlags(fileNameItem->flags() ^ Qt::ItemIsEditable);
// 添加图标进入名称的位置
fileNameItem->setIcon(icon);
// 添加路径
QTableWidgetItem *pathItem = new QTableWidgetItem(filePath);
pathItem->setData(absoluteFileNameRole, QVariant(filePath));
pathItem->setToolTip(toolTip);
pathItem->setFlags(fileNameItem->flags() ^ Qt::ItemIsEditable);
// 大小
QTableWidgetItem *sizeItem = nullptr;
// 如果是目录则sizeItem值为空
QFileInfo fileInfo(filePath);
if(fileInfo.isDir()){
// 如果是目录,直接设置为nullptr, 不显示大小
sizeItem = nullptr;
}else{
// 如果是目录,计算文件大小并设置为sizeItem
sizeItem = new QTableWidgetItem(tr("%1 KB")
.arg(int((size + 1023) / 1024)));
sizeItem->setData(absoluteFileNameRole, QVariant(filePath));
sizeItem->setToolTip(toolTip);
sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable);
}
int row = ui->filesTable->rowCount();
ui->filesTable->insertRow(row);
ui->filesTable->setItem(row, 0, fileNameItem);
ui->filesTable->setItem(row, 1, pathItem);
ui->filesTable->setItem(row, 2, sizeItem);
}
ui->fileFoundLabel->setText(tr("%n file(s) found (Double click on a file to open it)", nullptr, paths.size()));
ui->fileFoundLabel->setWordWrap(true);
}
4、 功能实现
功能点二: 为搜索结果增加右键菜单
- [x] 功能点二: 为搜索结果增加右键菜单,右键中菜单中包含启动和删除功能;
0、 知识点
- 表格激活状态下选中右键
- 右键菜单
- 删除功能实现
- 启动文件
- 复制文件名以及检测剪切板
1、 菜单栏功能实现
在createFilesTable
函数中实现
// 菜单
ui->filesTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->filesTable, &QTableWidget::customContextMenuRequested,
this, &MainWindow::contextMenu);
connect(ui->filesTable, &QTableWidget::cellActivated,
this, &MainWindow::openFileOfItem);
void MainWindow::contextMenu(const QPoint &pos)
{
const QTableWidgetItem *item = ui->filesTable->itemAt(pos);
if(!item)
return;
QMenu menu;
#ifndef QT_NO_CLIPBOARD
QAction *copyAction = menu.addAction("Copy Name");
#endif
QAction *openAction = menu.addAction("Open");
deleteAction = menu.addAction("delete");
QAction *action = menu.exec(ui->filesTable->mapToGlobal(pos));
if(!action)
return;
const QString fileName = fileNameOfItem(item);
if(action == openAction)
openFile(fileName);
else if (action == deleteAction){
qDebug()<<"deleteAction"<<endl;
deleteFileFromContextMenu();
// connect(deleteAction, &QAction::triggered, this, &MainWindow::deleteFileFromContextMenu);
}
#ifndef QT_NO_CLIPBOARD
else if (action == copyAction)
QGuiApplication::clipboard()->setText(QDir::toNativeSeparators(fileName));
#endif
}
具体的实现思路:
- 需要定义一个
QAction
- 根据QAction 唤醒对应的功能
2、删除文件名实现函数
void MainWindow::deleteFileFromContextMenu()
{
QList<QTableWidgetItem *> selectedItems = ui->filesTable->selectedItems();
QMessageBox::StandardButton reply = QMessageBox::question(this, tr("Delete"), tr("Are you sure to delete the selected files?"), QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
for (QTableWidgetItem *item : selectedItems) {
QString filePath = item->data(absoluteFileNameRole).toString();
QFile file(filePath);
file.remove();
ui->filesTable->removeRow(item->row());
}
}
}
3、打开文件实现函数
oid MainWindow::openFile(const QString &fileName)
{
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
4、功能实现
功能点三:快捷键与托盘
- [x] 功能点三:程序启动后默认隐藏,通过alt+空格 唤出,再次按下 alt+空格 隐藏;
0、知识点
- 快捷键使用
- 快捷键冲突
- 窗口焦点捕获
- 托盘
- 系统消息设置
详细内容将更新在专栏中,有兴趣的小伙伴可以关注这个专栏
:smile::smile::smile:
功能点四:实现模糊查询。
0、知识点:
- 正则化表达式的书写
- 正则化表达式的应用
- 模糊匹配中按照顺序进行匹配
- 模糊匹配中遇到空格的处理
- 模糊匹配算法的优化
- 如何提高模糊查询的效率(QtConcurrent)
1、 确定需求
要实现模糊查询,您可以使用正则化表达式来匹配文件名。正则表达式可以匹配类似于通配符的模式,并可以通过捕获组提取有用的信息。
这个功能真是饶了太久了,一开始没弄清楚需求。
先说一下需求是这样的:
支持模糊匹配搜索,例如输入“wps”,能够搜到 “wps.exe”也能搜到 “windows powsershell.exe";
需要定位到需要修改的函数。
详细内容将更新在专栏中,有兴趣的小伙伴可以关注这个专栏
:smile::smile::smile:
功能五 :中文搜索
- [x] 中文名称的程序支持通过拼音首字母进行搜索,例如输入"wx", 能够搜到 "微信”程序;
支持模糊匹配搜索,例如输入“wps”,能够搜到 “wps.exe”也能搜到 “windows powsershell.exe"; 中文搜索问题。
可以按照以下步骤进行实现:
0、 知识点:
- 库的制作
- qmake使用
- makefile生成外部库后引入到项目中
- Qt调用第三方库的方法
1、第三方库地问题
pinyin4cpp 有点坑啊。。。 很多词都对不上的!!!
比如微信
生成的居然是wei wei
早知道这么垃圾就不用了。。。
留下一个坑待修复:
详细内容将更新在专栏中,有兴趣的小伙伴可以关注这个专栏
:smile::smile::smile:
// some bug in here
HanyuPinyinOutputFormat *outputFormat;
PinyinHelper::toHanyuPinyinStringArray(firstChar, outputFormat, &pinyinList); // 获取文件名对应的汉语拼音
QString firstLetters; // 存储汉语拼音的首字母
foreach(const QString& pinyin, pinyinList) {
qDebug()<<"pinyin "<<pinyin<<endl;
if (!pinyin.isEmpty() && pinyin.at(0).isLower()) {
firstLetters += pinyin.at(0);
qDebug()<<"firstLetters "<<firstLetters<<endl;
if (fileName.contains(firstLetters, Qt::CaseInsensitive))
{
result->append(filePath);
}
break;
}
}
只匹配到第一个字母就返回中文的。
功能点六: 目录更新问题
0、知识点
QFileSystemWatch
监视文件目录- 变化后重新加载目录
1、实现步骤
2、功能实现
详细内容将更新在专栏中,有兴趣的小伙伴可以关注这个专栏
:smile::smile::smile:s
接下来测试一下:
当我们选定了需要监控的目录,在改目录下更新的所有操作都会被
功能点七:其他
项目Ui优化
知识点:
- qss的使用
- 多线程机制
- 运用设计模式对代码进行封装
简单地为每个目录创建多个线程,容易因为迭代深度太高二导致性能问题。 糟糕的设计方法
QList<QFuture<void>> futures;
foreach (const QString& dir, currentDir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot))
{
SearchParam param;
param.path = prefix + dir;
param.fileName = fileName;
futures.append(QtConcurrent::run(this, &MainWindow::searchDirectory, param, result));
}
QFutureSynchronizer<void> sync;
sync.waitForFinished();
解决思路:用线程池进行管理。
详细内容将更新在专栏中,有兴趣的小伙伴可以关注这个专栏
:smile::smile::smile:
三、总结
本文将对Qt文件查找系统进行技术总结。该系统包含了许多技术细节,包括文件管理器的打开、记忆下拉框内容、filetable
的使用、文件信息显示、文件图标获取、自定义排序、QFileSystemWatch
监视文件目录、变化后重新加载目录、库的制作、qmake使用、makefile生成外部库后引入到项目中、Qt调用第三方库的方法、表格激活状态下选中右键、右键菜单、删除功能实现、启动文件、复制文件名以及检测剪切板、快捷键使用、快捷键冲突、窗口焦点捕获、托盘、系统消息设置、正则化表达式的书写、正则化表达式的应用、模糊匹配中按照顺序进行匹配、模糊匹配中遇到空格的处理、模糊匹配算法的优化、以及如何提高模糊查询的效率(QtConcurrent)。
其中,打开文件管理器进行目录选择是该系统的一个基础功能。通过使用记忆下拉框,系统可以记忆用户的历史输入,并且提供方便快捷的选项。filetable
作为文件管理的核心组件,可以用于显示文件信息以及进行自定义排序。通过获取文件图标,系统可以使用户对文件进行更加直观的辨认。QFileSystemWatch
可以帮助系统监视文件目录的变化,从而能够及时地更新文件列表。库的制作和使用是一个重要的话题,qmake和makefile是实现库制作和使用的关键工具。Qt调用第三方库的方法,可以帮助系统实现更加丰富的功能。
在文件查找系统中,右键菜单和删除功能的实现都是非常重要的。启动文件、复制文件名以及检测剪切板都是与用户体验密切相关的功能,快捷键的使用和冲突也需要特别关注。窗口焦点捕获和托盘是增强系统交互性的重要特性。系统消息设置和正则化表达式的应用可以提供更加个性化的设置。模糊匹配是文件查找系统中常见的查询方式,按照顺序进行匹配和遇到空格的处理都需要特别注意。同时,对于模糊匹配算法的优化以及如何提高模糊查询的效率也需要仔细思考。
四、讨论
这个项目仅仅是作为新手学习的时候的学习笔记,在实现的地方可能会存在一些不合理的地方,烦请大佬批评与指正,:star:::star: :star:
在实现这个系统地时候,总有些疑惑就是Everything这种本地文件搜索器是如何这么快检索到需要地文件?
非常期待各位大佬留言解惑!
最后,最后 如果觉得有用,麻烦三连????:star::heart:支持一下呀,希望这篇文章可以帮到你,你的点赞是我持续更新的动力