在任意目录下查找需要的文件如何操作呢?
其实很简单, WIN+E 【桌面计算机】- 右上角“搜索 计算机”
这个就是Windows自带的文件搜索功能。自己做一个文件搜索的应该应该也挺好玩的。
知识要点:
os.walk 函数
方法用于通过在目录树种游走输出在目录中的文件名,向上或者向下深度遍历目录。
os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
参数
-
top -- 根目录下的每一个文件夹(包含它自己), 产生3-元组 (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名】。
-
topdown --可选,为True或者没有指定, 一个目录的的3-元组将比它的任何子文件夹的3-元组先产生 (目录自上而下)。如果topdown为 False, 一个目录的3-元组将比它的任何子文件夹的3-元组后产生 (目录自下而上)。
-
onerror -- 可选,是一个函数; 它调用时有一个参数, 一个OSError实例。报告这错误后,继续walk,或者抛出exception终止walk。
-
followlinks -- 设置为 true,则通过软链接访问目录。
例子:
import os for root, dirs, files in os.walk(".", topdown=False): for name in files: print(os.path.join(root, name)) for name in dirs: print(os.path.join(root, name))
先从根目录进行遍历,读取跟目录的文件夹和文件。以根目录第一个子目录为新的根目录,读取其文件夹和文件。第一个子文件夹为根目录,读取文件夹和文件,直到结束。
下面,我们开始编写,新的应用
一.UI
对样式或审美不是很敏感(主要是功能),所以对于UI随心所遇的设计,一下为UI代码:
class MainWidgetUI(QDialog): def __init__(self, parent=None): super(MainWidgetUI, self).__init__(parent) self.setWindowIcon(QtGui.QIcon("favicon.ico")) self.setWindowOpacity(0.85) # 透明度 self.setWindowTitle('查询文件') self.directory = '' # 跟目录 self.mainLayout = QVBoxLayout() # 水平布局 # Find 文件布局 self.topgroupBox = QGroupBox("任意右键选择查询目录") self.topLayout = QHBoxLayout(self.topgroupBox) self.lineEdit = QLineEdit('', self.topgroupBox) self.lineEdit.setPlaceholderText('如:chrome.exe, 多个 ; 分割') self.searchBtn = QPushButton(QtGui.QIcon("favicon.ico"), '查询', self.topgroupBox) self.topLayout.addWidget(self.lineEdit) self.topLayout.addWidget(self.searchBtn) # 输出文件路径布局 self.bottgroupBox = QGroupBox('文件路径') self.bottLayout = QVBoxLayout(self.bottgroupBox) # 水平布局 self.ListWidget = QListWidget(self.bottgroupBox) self.bottLayout.addWidget(self.ListWidget) # mainLayout 布局 self.mainLayout.addWidget(self.topgroupBox) self.mainLayout.addWidget(self.bottgroupBox) self.setLayout(self.mainLayout) self.lineEdit.setFocus() # 得到焦点 # 搜索框样式 self.lineEdit.setStyleSheet( "QLineEdit{background-color:green;color:menubar;font-size:12px;background-image:url(search.png);}") if __name__ == "__main__": app = QApplication(sys.argv) main_widget = MainWidgetUI() main_widget.show() sys.exit(app.exec_())
UI效果:
当UI整完了后,发现个问题,如何选择要搜索目录呢,Windows自带的是整个硬盘搜索,这样目录太大,搜索时间太长,我们要的效果是将搜索的目录指定到某个盘或某个盘里面的某个文件夹。
想到了个办法,在UI中任意右键选择要搜索的目录:
self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.rightHandButton) # 任意地方右键 选择跟目录
# 任意地方右键 def rightHandButton(self): self.directory = QFileDialog.getExistingDirectory(self, "请选择根目录", 'c:\') self.topgroupBox.setTitle("查询根目录:" + str(self.directory))
二.SearchIndex
这一步就用os.walk查询文件,我们把这个功能封装正一个方法:
def search_files(self, directory, patterns='*', single_level=False, yield_folders=False): # 将模式从字符串中取出放入列表中 patterns = patterns.split(';') for path, subdirs, files in os.walk(directory): if yield_folders: files.extend(subdirs) files.sort() for name in files: for pattern in patterns: if fnmatch.fnmatch(name, pattern): yield os.path.join(path, name) break if single_level: break
问题又来了,当点击查询按钮执行查找文件时,UI直接都卡死了
恍然也!查询文件的方法堵塞了UI主进程,所以我们用开启一个线程
# 线程查询类 class TheadingFindFile(QThread): resultSearchSignal = pyqtSignal(list) # 声明一个带列表结果的参数信号 def __init__(self, tuple): super(TheadingFindFile, self).__init__() self.searchTuple = tuple # 元组? def run(self): result = [] # 列表 files = self.search_files(self.searchTuple[0], self.searchTuple[1], False, False) for file in files: result.append(file) if not result: result = ['无查询结果'] try: self.resultSearchSignal.emit(result) # 发射信号 except Exception as e: print(e) # 检查一个目录,后者某个包含子目录的目录树,并根据某种模式迭代所有文件 # patterns如:*.html,若大小写敏感可写*.[Hh][Tt][Mm][Ll] # single_level 为True表示只检查第一层 # yield_folders 表示是否显示子目录,为False只遍历子目录中的文件, # 但不返回字母名 def search_files(self, directory, patterns='*', single_level=False, yield_folders=False): # 将模式从字符串中取出放入列表中 patterns = patterns.split(';') for path, subdirs, files in os.walk(directory): if yield_folders: files.extend(subdirs) files.sort() for name in files: for pattern in patterns: if fnmatch.fnmatch(name, pattern): yield os.path.join(path, name) break if single_level: break
三.完善应用
在上一步中,尽管堵塞了UI进程,但文件少的时候也是可以完成搜索呈现在QListWidget中,作为一个严谨的***, 我们要将产品做到尽量的完美!
所以我们要做提交验证、开启查询线程防止UI堵塞、查询中禁止再次点击查询按钮、QListWidget的中的数据要能打开改目录,所以第三步,就是完善应用。
在查询按钮点击后,我们判断数据是否填写
self.ListWidget.clear() # 清空ListWidget数据 inputs = self.lineEdit.text() if inputs == '': QMessageBox.warning(self, '查询提示', '输入的查询文件名关键字不能为空', QMessageBox.Yes) return False if self.directory == '': QMessageBox.warning(self, '查询提示', '请任意右键选择查询的跟目录', QMessageBox.Yes) return False
验证完成后,开启查询线程:
try: # 在 实例化类与connect、start 直接不能打印任何东西,不然会报错 senderData = (self.directory, self.lineEdit.text()) self.Theading = TheadingFindFile(senderData) self.Theading.resultSearchSignal.connect(self.updateResult) # 连接信号。 TheadingFindFile在线程状态结果后emit发射信号 self.Theading.start() # 线程开始 except Exception as e: print(e)
别急!我们应该在加一个查询按钮按下后的特效,因为查询会堵塞UI进程,相同的我想做一个每秒显示一个点点点的效果也会堵塞进程。【不用问,我已经试过的】,所以也开启了一个点点点的效果进程:
# 线程查询效果类 class TheadingSearchBtnNet(QThread): resultSearchBtnNetSignal = pyqtSignal(str) # 声明一个带列表结果的参数信号 def __init__(self, str): super(TheadingSearchBtnNet, self).__init__() self.searchTuple = str # 设置status 的状态值, 1 为可执行,0:停止执行 def setVal(self, st): self.st = st self.start() def run(self): i = 5 while True: Dot = '' if i <= 5: for l in range(0, 5): time.sleep(1) Dot += str('.') if self.st == 1: # 可执行 self.resultSearchBtnNetSignal.emit(str(Dot)) # 发射信号 i += 1 else: i = 0
查询中点点点效果:
还有一个就是在ListWidget中呈现的路径,做了一个双击打开文件所在目录的功能,
# List路径列表双击事件 def doubleClickListPath(self): text = self.ListWidget.currentItem().text() # 获取当前Item的text text = text.replace('/', '\') if text != '无查询结果': os.system(r'explorer /select,' + str(text)) # 打开文件并选中文件
使用subprocess.Popen(file) 只能打开文件,使用explorer /select , file 可以用Windows自带的文件管理器打开文件的所在目录,并选中该文件。其实这个explorer直接可以在命令行CMD直接打开。
四.完整代码及效果
完整代码:
# -*- coding: utf-8 -*- from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5 import QtGui import os, sys, fnmatch import time class MainWidgetUI(QDialog): def __init__(self, parent=None): super(MainWidgetUI, self).__init__(parent) self.setWindowIcon(QtGui.QIcon("favicon.ico")) self.setWindowOpacity(0.85) # 透明度 self.setWindowTitle('查询文件') self.directory = '' # 跟目录 self.mainLayout = QVBoxLayout() # 水平布局 # Find 文件布局 self.topgroupBox = QGroupBox("任意右键选择查询目录") self.topLayout = QHBoxLayout(self.topgroupBox) self.lineEdit = QLineEdit('', self.topgroupBox) self.lineEdit.setPlaceholderText('如:chrome.exe, 多个 ; 分割') self.searchBtn = QPushButton(QtGui.QIcon("favicon.ico"), '查询', self.topgroupBox) self.topLayout.addWidget(self.lineEdit) self.topLayout.addWidget(self.searchBtn) # 输出文件路径布局 self.bottgroupBox = QGroupBox('文件路径') self.bottLayout = QVBoxLayout(self.bottgroupBox) # 水平布局 self.ListWidget = QListWidget(self.bottgroupBox) self.bottLayout.addWidget(self.ListWidget) # mainLayout 布局 self.mainLayout.addWidget(self.topgroupBox) self.mainLayout.addWidget(self.bottgroupBox) self.setLayout(self.mainLayout) self.searchBtn.clicked.connect(self.searchDef) # 查询事件 self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.rightHandButton) # 任意地方右键 选择跟目录 self.ListWidget.itemDoubleClicked.connect(self.doubleClickListPath) # List路径列表双击事件 self.lineEdit.setFocus() # 得到焦点 # 搜索框样式 self.lineEdit.setStyleSheet( "QLineEdit{background-color:green;color:menubar;font-size:12px;background-image:url(search.png);}") self.Theading2 = TheadingSearchBtnNet("查询") self.Theading2.resultSearchBtnNetSignal.connect(self.searchBtnText) # 连接信号。 TheadingFindFile在线程状态结果后emit发射信号 # self.lineEdit.setText('chrome.exe;readme.txt') # self.ListWidget.addItem("C:\Program Files\7-Zip/7z.exe") # self.ListWidget.addItem("bb") # self.ListWidget.addItem("E:\Program Files/java") # 显示查询按钮点点点效果 def searchBtnText(self, strings): self.searchBtn.setText("查询" + str(strings)) # List路径列表双击事件 def doubleClickListPath(self): text = self.ListWidget.currentItem().text() # 获取当前Item的text text = text.replace('/', '\') if text != '无查询结果': os.system(r'explorer /select,' + str(text)) # 打开文件并选中文件 # 任意地方右键 def rightHandButton(self): self.directory = QFileDialog.getExistingDirectory(self, "请选择根目录", 'c:\') self.topgroupBox.setTitle("查询根目录:" + str(self.directory)) # 查询BTN 按钮 def searchDef(self): self.ListWidget.clear() # 清空ListWidget数据 inputs = self.lineEdit.text() if inputs == '': QMessageBox.warning(self, '查询提示', '输入的查询文件名关键字不能为空', QMessageBox.Yes) return False if self.directory == '': QMessageBox.warning(self, '查询提示', '请任意右键选择查询的跟目录', QMessageBox.Yes) return False self.searchBtn.setDisabled(True) self.Theading2.setVal(1) # 查询点点点效果线程开始 try: # 在 实例化类与connect、start 直接不能打印任何东西,不然会报错 senderData = (self.directory, self.lineEdit.text()) self.Theading = TheadingFindFile(senderData) self.Theading.resultSearchSignal.connect(self.updateResult) # 连接信号。 TheadingFindFile在线程状态结果后emit发射信号 self.Theading.start() # 线程开始 except Exception as e: print(e) # 返回响应的参数 def updateResult(self, resultData): self.searchBtn.setDisabled(False) self.searchBtn.setText('查询') self.Theading2.setVal(0) # 停止执行 for data in resultData: self.ListWidget.addItem(data) # 线程查询类 class TheadingFindFile(QThread): resultSearchSignal = pyqtSignal(list) # 声明一个带列表结果的参数信号 def __init__(self, tuple): super(TheadingFindFile, self).__init__() self.searchTuple = tuple # 元组? def run(self): result = [] # 列表 files = self.search_files(self.searchTuple[0], self.searchTuple[1], False, False) for file in files: result.append(file) if not result: result = ['无查询结果'] try: self.resultSearchSignal.emit(result) # 发射信号 except Exception as e: print(e) # 检查一个目录,后者某个包含子目录的目录树,并根据某种模式迭代所有文件 # patterns如:*.html,若大小写敏感可写*.[Hh][Tt][Mm][Ll] # single_level 为True表示只检查第一层 # yield_folders 表示是否显示子目录,为False只遍历子目录中的文件, # 但不返回字母名 def search_files(self, directory, patterns='*', single_level=False, yield_folders=False): # 将模式从字符串中取出放入列表中 patterns = patterns.split(';') for path, subdirs, files in os.walk(directory): if yield_folders: files.extend(subdirs) files.sort() for name in files: for pattern in patterns: if fnmatch.fnmatch(name, pattern): yield os.path.join(path, name) break if single_level: break # 线程查询效果类 class TheadingSearchBtnNet(QThread): resultSearchBtnNetSignal = pyqtSignal(str) # 声明一个带列表结果的参数信号 def __init__(self, str): super(TheadingSearchBtnNet, self).__init__() self.searchTuple = str # 设置status 的状态值, 1 为可执行,0:停止执行 def setVal(self, st): self.st = st self.start() def run(self): i = 5 while True: Dot = '' if i <= 5: for l in range(0, 5): time.sleep(1) Dot += str('.') if self.st == 1: # 可执行 self.resultSearchBtnNetSignal.emit(str(Dot)) # 发射信号 i += 1 else: i = 0 if __name__ == "__main__": app = QApplication(sys.argv) main_widget = MainWidgetUI() main_widget.show() sys.exit(app.exec_())
打包程序为exe:
if __name__ == '__main__': from PyInstaller import __main__ params = ['-F','-w', '--icon=favicon.ico','--noupx', 'mainFindFiles.py'] __main__.run(params)
效果: