python_everything——UI版
单线程扫描模式
因为是io型程序,所以用线程是可以的
scan_module.py
''' 本类需要用到的模块信息 ''' import os import string import time from copy import _reconstruct ''' scan_module.py 本文件内容是扫描模块,作用是用于扫描和保存扫描结果。 代码不再使用或大幅度减少函数式编程代码,改为使用对象编程的方法。 本代码只适用于Windows系统 ''' class scan_module: ''' 扫描模块,实现扫描的核心代码和存储扫描出来的数据。 ''' __all_file_dict = {} def __init__(self): ''' 实例化对象的时候就初始化出需要扫描的根路径 ''' self.__paths_list = [] # 存储盘符,变量类型为列表 self.__get_DriveLetter() # 初始化盘符列表变量。 def __get_DriveLetter(self): ''' 获取盘符列表,是通过猜测验证其是否存在。 私有函数 ''' for i in string.ascii_uppercase: if os.path.exists(i + ':\\'): self.__paths_list.append(i + ':\\') # else的情况不考虑停止,实际上,用户的磁盘盘符完全可以不连续。 def __get_fileList(self, initial_path): ''' 扫描,私有函数 ''' try: child_file_list = os.scandir(initial_path) except: # 遇到异常则直接终止函数 # print(initial_path) return for file_ in child_file_list: file_stat = file_.stat(follow_symlinks=False) time_local = time.localtime(file_stat.st_ctime) # Ctiem,创建时间 file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # Mtime,最近的修改时间 time_local = time.localtime(file_stat.st_mtime) file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # atime,最近的访问时间 time_local = time.localtime(file_stat.st_atime) file_atime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # size,文件大小(以字节为单位),文件可以是常规文件或符号链接。符号链接的大小是它包含的路径的长度,不包括末尾的空字节。 file_size = int(file_stat.st_size) isDir = 0 if file_.is_dir(): isDir = 1 else: isDir = 0 # isDir应该永远位于列表的最后一位 if file_.name in self.__all_file_dict.keys(): self.__all_file_dict[file_.name].append( [initial_path, file_ctime, file_mtime, file_atime, file_size, file_stat.st_file_attributes , ]) else: self.__all_file_dict[file_.name] = [ [initial_path, file_ctime, file_mtime, file_atime, file_size, file_stat.st_file_attributes , ], ] # 是否是目录 if file_.is_dir(): self.__get_fileList(file_.path) def get_fileList_v2(self): ''' 听说了一个os.walk库,专用于遍历文件存在的 比我自己写的慢一倍。 ''' start_time = time.time() for path_i in self.__paths_list: for dirpath, dirnames, filenames in os.walk(path_i): for name in dirnames: absPath = os.path.join(dirpath, name) if name in self.__all_file_dict.keys(): self.__all_file_dict[name].append([absPath, ]) else: self.__all_file_dict[name] = [[absPath, ], ] for name in filenames: absPath = os.path.join(dirpath, name) if name in self.__all_file_dict.keys(): self.__all_file_dict[name].append([absPath, ]) else: self.__all_file_dict[name] = [[absPath, ], ] print("V2版本的扫描时间:{}".format(time.time() - start_time)) def scan_console(self): ''' 调用扫描函数 ''' start_time = time.time() for path_i in self.__paths_list: self.__get_fileList(path_i) return time.time() - start_time #print("扫描花费时间:{}".format(time.time() - start_time)) def formatted_output(self): ''' 将结果字典格式化输出到文件中存储。 输出到用户家目录下存放,名称叫python_class_eveything_data.data ''' out_file_path_str = os.environ['USERPROFILE'] + \ '\\python_class_eveything_data.data' with open(out_file_path_str, 'w', encoding='utf-8')as fd: # 偶尔会发生在迭代期间字典大小发生变化的运行时错误 # 之前在迭代期间字典大小发生变化的bug解决了,但是这里就这样吧。 data = self.__my_deepcopy(self.__all_file_dict) for key, value in data.items(): for value_i in value: line_str = key for value_i_i in value_i: line_str += "\t{}".format(value_i_i) fd.write(line_str + '\n') del data def __my_deepcopy(self, x): ''' 深拷贝,copy库的deepcopy性能不太行,所以在deepcopy的基础上改了一下 这个深拷贝函数仅用于本脚本中,copy库中的deepcopy函数慢是有原因的,了解之后再使用。 ''' reductor = getattr(x, "__reduce_ex__", None) if reductor is not None: rv = reductor(4) y = _reconstruct(x, None, *rv) return y def get_data(self): """ 返回数据的副本,同时删除源数据 """ y = self.__my_deepcopy(self.__all_file_dict) self.__all_file_dict = {} return y def get_file_num(self): ''' 获取扫描的文件数量 ''' num = len(self.__all_file_dict) print("文件数量(只统计key):{}。".format(num)) return num if __name__ == "__main__": a = scan_module() a.scan_console() a.formatted_output()
UI.py
import ctypes # 文件操作-剪贴板操作相关 import os import stat import re import sys import threading import time import tkinter import signal import tkinter.messagebox import tkinter.ttk import traceback import subprocess import pyperclip # 字符串-剪贴板相关 import win32api import win32clipboard # 文件操作-剪贴板操作相关 from win32com.shell import shell, shellcon from copy import _reconstruct #深拷贝 import scan_module import sysTrayIcon class UI: ''' 绘制界面 ''' def __init__(self): self.__root = tkinter.Tk() self.__root.title("python_everything") self.__root.iconbitmap(r"python_everything.ico") self.__size_long = 1200 #长 self.__size_width = 500 #宽 self.__root.geometry("{}x{}".format(self.__size_long,self.__size_width)) self.__SysTrayIcon = None # 值在Hidden_window函数中初始化 # 根窗口大小改变的事件 self.__root.bind("<Configure>", func=self.__table_cloumnSize) self.__root.protocol("WM_DELETE_WINDOW", self.Hidden_window) # 重定义关闭事件 # 绑定快捷键Ctrl+f,捕捉到这个事件后,使搜索框得到焦点 self.__root.bind("<Control-f>", func=self.__searchBar_focus) # 数据 self.__data = self.__get_data_fromFile() # 初始化一些变量 self.__is_caseSensitive = False # 是否区分大小写,FALSE表示不区分 self.__max_showLine = 1000 # 最大显示的行数 self.__sortFlag_col_size = False # 列“大小”如何排序(False表示从大到小,True表示从小到大) self.__search_result = self.__data # 搜索的结果 self.__show_data = [] # 最终显示的结果 self.__filter_condition = "all" # 筛选条件,在显示数据前的最后一层条件 self.__stratTime = time.time() #记录操作开始的时间 self.__map_dict__ = {"all":"所有文件","zip":"压缩文件","audio":"音频文件","video":"视频文件","picture":"图片文件","document":"文档文件","execute":"可执行文件","folder":"文件夹","lnk":"快捷方式",} self.__threading_Operating_condition = "" # 存储子进程对象 self.__scan = scan_module.scan_module() # 扫描模块对象 self.__scan_num = 0 # 扫描次数 self.__scan_console() # 启动扫描子进程 self.__statusBar_right_TEXT = '不区分大小写;显示所有文件;' self.__pid = os.getpid() # print(self.__pid) self.__menu() self.__search_bar() self.__status_bar() self.__content() def __scan_console(self): """扫描以及数据获取""" def get_data_dict(): """获取扫描数据的实际代码""" time_ = self.__scan.scan_console() self.__scan.formatted_output() self.__data = self.__scan.get_data() self.__scan_num += 1 # 提示扫描完毕 self.__statusBar.config( fg="black", text="扫描完毕,用时:{}秒。".format(time_)) if self.__threading_Operating_condition == '' or self.__threading_Operating_condition.is_alive() == False: self.__threading_Operating_condition = threading.Thread( target=get_data_dict) self.__threading_Operating_condition.start() def __get_data_fromFile(self): """#从文件中获取数据""" in_file_path_str = os.environ['USERPROFILE'] + \ '\\python_class_eveything_data.data' if(os.path.exists(in_file_path_str)): with open(in_file_path_str, "r", encoding='utf-8-sig') as in_fd: lines_list = in_fd.read().splitlines() lines_list_ = [i.split('\t') for i in lines_list] return lines_list_ else: tkinter.messagebox.showinfo( title="提示", message="数据文件不存在,请等待扫描结果出来之后再进行搜索。") return [] def __menu(self): """#建立菜单""" # 顶层菜单 self.__ui_menu_top = tkinter.Menu(master=self.__root) # 文件菜单,且取消虚线和分离功能 self.__ui_menu_file = tkinter.Menu( master=self.__ui_menu_top, tearoff=False) # 搜索菜单 self.__ui_menu_search = tkinter.Menu( master=self.__ui_menu_top, tearoff=False) # 关联顶层菜单和文件菜单 self.__ui_menu_top.add_cascade(label="文件", menu=self.__ui_menu_file) self.__ui_menu_top.add_cascade(label="搜索", menu=self.__ui_menu_search) # 文件菜单的菜单列表 # self.ui_menu_file.add_command() #self.__ui_menu_file.add_command(label="新建窗口", command=lambda: print("ok")) self.__ui_menu_file.add_command( label="退出", command=self.exit_off) # 创建具有复选框的子项 self.__demoStatus = tkinter.BooleanVar() self.__demoStatus.set(False) # 初始状态为不区分大小写,__demoStatus是一个bool值记录器。 self.__ui_menu_search.add_checkbutton(label="区分大小写", command=self.__case_sensitive, variable=self.__demoStatus) self.__ui_menu_search.add_separator() # 添加分隔线 self.__filterStatus_list = [] [self.__filterStatus_list.append( tkinter.BooleanVar()) for i in range(9)] [self.__filterStatus_list[i].set(False) for i in range(9)] self.__filterStatus_list[0].set(True) # 所有文件这个选择时默认勾选上的 self.__ui_menu_search.add_checkbutton( label="所有文件", command=lambda cond="all", index=0: self.__filter_console(cond, index), variable=self.__filterStatus_list[0]) self.__ui_menu_search.add_checkbutton( label="压缩文件", command=lambda cond="zip", index=1: self.__filter_console(cond, index), variable=self.__filterStatus_list[1]) self.__ui_menu_search.add_checkbutton( label="音频文件", command=lambda cond="audio", index=2: self.__filter_console(cond, index), variable=self.__filterStatus_list[2]) self.__ui_menu_search.add_checkbutton( label="视频文件", command=lambda cond="video", index=3: self.__filter_console(cond, index), variable=self.__filterStatus_list[3]) self.__ui_menu_search.add_checkbutton( label="图片文件", command=lambda cond="picture", index=4: self.__filter_console(cond, index), variable=self.__filterStatus_list[4]) self.__ui_menu_search.add_checkbutton( label="文档文件", command=lambda cond="document", index=5: self.__filter_console(cond, index), variable=self.__filterStatus_list[5]) self.__ui_menu_search.add_checkbutton( label="可执行文件", command=lambda cond="execute", index=6: self.__filter_console(cond, index), variable=self.__filterStatus_list[6]) self.__ui_menu_search.add_checkbutton( label="快捷方式", command=lambda cond="lnk", index=7: self.__filter_console(cond, index), variable=self.__filterStatus_list[7]) self.__ui_menu_search.add_checkbutton( label="文件夹", command=lambda cond="folder", index=8: self.__filter_console(cond, index), variable=self.__filterStatus_list[8]) #路径筛选 self.__ui_menu_search.add_separator() # 添加分隔线 self.__path_filter_Status = tkinter.BooleanVar() self.__path_filter_Status_bool = False self.__path_filter_Status.set(False) self.__ui_menu_search.add_checkbutton(label="路径筛选", command=self.__pathFilter, variable=self.__path_filter_Status) # 显示菜单 self.__root.config(menu=self.__ui_menu_top) def __case_sensitive(self): '''使得搜索区分大小写''' if self.__is_caseSensitive: # 已经勾选了要区分大小写,再次点击就是不区分大小写了 self.__demoStatus.set(False) self.__is_caseSensitive = False self.__statusBar_right.config(text=self.__statusBar_right.cget(key="text").replace("区分","不区分")) else: self.__demoStatus.set(True) self.__is_caseSensitive = True self.__statusBar_right.config(text=self.__statusBar_right.cget(key="text").replace("不区分","区分")) def __status_bar(self): """状态栏,构建两个标签控件,左边的显示常规的信息,右边的显示当前的一些控制选项;""" # self.__statusBar_frame = tkinter.Frame(master=self.__root) self.__statusBar_frame.pack(side=tkinter.BOTTOM,fill=tkinter.X) self.__statusBar = tkinter.Label(master=self.__statusBar_frame, text="{}个对象".format(len(self.__data)), anchor=tkinter.W, relief=tkinter.RIDGE) self.__statusBar_right = tkinter.Label(master=self.__statusBar_frame, text=self.__statusBar_right_TEXT, anchor=tkinter.W, relief=tkinter.SOLID) self.__statusBar.pack(side=tkinter.LEFT, anchor=tkinter.W,expand=True,fill=tkinter.X) self.__statusBar_right.pack(side=tkinter.RIGHT, anchor=tkinter.SE) def __search_bar(self): """#搜索框""" self.__entryFrame = tkinter.Frame(master=self.__root) self.__entryFrame.pack(fill=tkinter.X) # exportselection=0:禁止自动复制选取的字符串,当任何击键更改小组件的内容时进行回调函数 self.__searchBar = tkinter.Entry(master=self.__entryFrame, exportselection=0) self.__searchBar.bind("<Return>", self.__search_console) self.__searchBar.pack(side=tkinter.LEFT,fill=tkinter.X,expand=True) self.__pathBar = tkinter.Entry(master=self.__entryFrame, exportselection=0,width=60) #默认情况下不显示该控件 self.__pathBar.bind("<Return>", self.__search_console) # 事件 self.__searchBar.bind("<Button-3>", self.__searchBar_rightClik) def __searchBar_rightClik(self, event): '''搜索框右键,目前支持,复制,粘贴,剪切''' e = event.widget # 获取发出事件的控件 # 获取搜索框中的字符串 searcStr = e.get() # 是否有选中字符 is_checked = e.select_present() checked_str = "" start_index = 0 end_index = 0 if is_checked: # checked_str = e.select_get() #获取选中的文本 start_index = e.index(tkinter.ANCHOR) # 开始选择时的index end_index = e.index(tkinter.INSERT) # 结束选择时的index if start_index < end_index: # 从左向右选择 checked_str = searcStr[start_index: end_index] elif start_index > end_index: # 从右向左选择 checked_str = searcStr[end_index: start_index] else: checked_str = "" def copy_Str(Str): pyperclip.copy(Str) # 将字符串发送到剪贴板上 def cut_selectStr(Str, e,is_cut=True): if is_cut: pyperclip.copy(Str) # 将字符串发送到剪贴板上 # e.select_clear() #清除所选字符串,妈的,竟然不管用 new_str = "" if start_index < end_index: new_str = searcStr[0:start_index] + searcStr[end_index:] e.delete(0, tkinter.END) e.insert(0, new_str) e.icursor(end_index - len(checked_str)) # 设置光标位置 elif start_index > end_index: # 从右向左选择 new_str = searcStr[0:end_index] + searcStr[start_index:] e.delete(0, tkinter.END) e.insert(0, new_str) e.icursor(start_index - len(checked_str)) # 设置光标位置 else: new_str = "" def cut_allStr(Str, e): pyperclip.copy(Str) # 将字符串发送到剪贴板上 e.delete(0, tkinter.END) # 清空整个字符串 def paste(e): #检查一下有没有选择字符 if is_checked: cut_selectStr(cut_selectStr,e=e,is_cut=False) #删除所选择的字符串 str_ = pyperclip.paste() # 获取剪贴板上的文字内容 index_ = e.index(tkinter.INSERT) # 获取光标当前的位置 e.insert(index_, str_) def select_all(e): e.select_range(0, tkinter.END) def del_allStr(e): e.delete(0, tkinter.END) # 清空整个字符串 # 建立弹出式菜单 popupMenu = tkinter.Menu(master=self.__root, tearoff=False) # 隐藏虚线分割线 if is_checked: popupMenu.add_command( label="复制所选文字", command=lambda: copy_Str(checked_str)) popupMenu.add_command( label="复制全部文字", command=lambda: copy_Str(searcStr)) if is_checked: popupMenu.add_command( label="剪切所选文字", command=lambda: cut_selectStr(checked_str, e,True)) popupMenu.add_command( label="剪切全部文字", command=lambda: cut_allStr(searcStr, e)) popupMenu.add_command(label="粘贴", command=lambda: paste(e)) popupMenu.add_command(label="全选", command=lambda: select_all(e)) if is_checked: popupMenu.add_command( label="删除所选文字", command=lambda: cut_selectStr(checked_str,e,False)) popupMenu.add_command(label="删除全部文字", command=lambda: del_allStr(e)) popupMenu.post(event.x_root, event.y_root) # 显示菜单; def __my_deepcopy(self, x): ''' 深拷贝,copy库的deepcopy性能不太行,所以在deepcopy的基础上改了一下 这个深拷贝函数仅用于本脚本中,copy库中的deepcopy函数慢是有原因的,了解之后再使用。 ''' reductor = getattr(x, "__reduce_ex__", None) if reductor is not None: rv = reductor(4) y = _reconstruct(x, None, *rv) return y def __search_console(self, event): """ 搜索行为控制,是从文件读取的内容中进行搜索还是从字典中进行搜索; 每次搜索,都将调用扫描函数进行扫描获取数据 从搜索框中获取文件名表达式和路径表达式:搜索的时候,同时要满足文件名表达式和路径表达式 """ self.__stratTime = time.time() self.__scan_console() #扫描数据 path_str = self.__pathBar.get() #不使用正则 filename_regex = self.__searchBar.get() result_list = '' if filename_regex == "" or filename_regex == r'.*' or filename_regex == r'.+' or filename_regex == r'.': if self.__scan_num > 0: result_list = self.__dict_Conversion_list(self.__data) else: result_list = self.__data else: if self.__scan_num > 0: # 此时self.__data是字典类型 result_list = self.__find_match_dict() else: # 此时self.__data是列表类型 result_list = self.__find_match() #self.__search_result = result_list self.__search_result = [] #存在路径字符串,且路径输入框显示出来的情况下 if (len(path_str)>0 and (self.__path_filter_Status_bool)): if(path_str[-1]=='\\'): path_str = path_str[:-1] #极端情况下有点影响性能啊,比如查询出来的结果特别多的时候 num = 0 #记录匹配到了多少个,不必查询出全部的值 for result_list_i in result_list: if path_str.upper() in result_list_i[1].upper(): self.__search_result.append(result_list_i) num += 1 if num >= self.__max_showLine: break else: self.__search_result = result_list self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) def __path_filter(self,event): '''路径筛选,是从搜索出来的结果中进行筛选, 没有使用该函数 ''' self.__stratTime = time.time() path_str = self.__pathBar.get() #不适用正则,但是也不区分大小写 if(path_str[-1]=='\\'): path_str = path_str[:-1] path_filter_result = [] if len(path_str) > 0: for result_list_i in self.__show_data: if path_str.upper() in result_list_i[1].upper(): path_filter_result.append(result_list_i) self.__filling_init(path_filter_result) self.__statusBar.config(text = "筛选出{}个结果。所用时间:{};当前显示文件类型:{}".format(len(path_filter_result),time.time()-self.__stratTime,self.__map_dict__[self.__filter_condition]),fg='green') else: self.__filling_init(self.__show_data) self.__statusBar.config(text = "取消筛选。",fg='block') def __find_match(self): ''' 查询函数,从列表中读取,没有进行性能优化,即没有聚合成大字符串,再一次性匹配出所有符合条件的值得处理。 ''' #print("regex_str = {}".format(regex_str)) regex_str = self.__searchBar.get() if regex_str == "": self.__statusBar.config(text="搜索框中没有字符哦。", foreground="red") return [self.__data[i] for i in range(self.__max_showLine)] data = self.__data result_list = [] for i in data: if self.__is_caseSensitive: result_regex = re.findall(regex_str, i[0]) else: result_regex = re.findall(regex_str, i[0], re.I) if len(result_regex) != 0: result_list.append(i) return result_list def __regex_translate(self, regex_str): """表达式翻译处理""" if regex_str == '': return "" else: # 需要判断输入的正则表达式是否合法,不合法的话,就需要提示用户重新输入,之所以不纠正是因为不要猜测用户的意图。 try: re.compile(regex_str) except: self.__statusBar.config(text="输入的正则表达式有误!", fg='red') return '' if regex_str[0] == '^': regex_str = regex_str[1:] replace_result_str = r"(?<=\t)" elif regex_str[0] == '.': # 提升数倍的性能(6倍) replace_result_str = r"(?<=\t)" else: replace_result_str = r'(?<=\t)[^\t]*?' for index in range(len(regex_str)): if regex_str[index] == '.': if index != 0 and regex_str[index - 1] == '\\': replace_result_str += regex_str[index] else: replace_result_str += r'[^\t]' else: replace_result_str += regex_str[index] if replace_result_str[-1] == "$": replace_result_str = replace_result_str[0:-1] replace_result_str += r'(?=\t)' else: replace_result_str += r'[^\t]*?(?=\t)' # print(replace_result_str) return replace_result_str def __find_match_dict(self): ''' 查询函数,从字典中读取,并生成结果列表类型变量,先将可以聚合成大字符串,在一次性匹配出所有符合条件的值。 ''' data = self.__data regex_str = self.__searchBar.get() regex_str = self.__regex_translate(regex_str) if regex_str == "": #self.__statusBar.config(text="搜索框中没有字符哦。", foreground="red") #这一步已经在上层检查过了,不会出现这样的情况,只是为了保险起见这样做 return [] result_list = [] if type({}) == type(data): keys = data.keys() keys_str = "\t" keys_str += '\t'.join(keys) keys_str += '\t' if self.__is_caseSensitive: result_key = re.findall(regex_str, keys_str) else: result_key = re.findall(regex_str, keys_str, re.I) for key in result_key: temp_ = {} temp_[key] = data[key] result_list += self.__dict_Conversion_list(temp_) else: tkinter.messagebox.showinfo(title="异常", message="未传入字典类型变量!函数:__find_match_dict") return [] return result_list def __dict_Conversion_list(self, dict_data): ''' 将字典键和值转变为列表 ''' if type({}) != type(dict_data): return [] return_result = [] for key, value in dict_data.items(): for value_i in value: temp_list = [] temp_list.append(key) for value_i_i in value_i: temp_list.append(value_i_i) return_result.append(temp_list) return return_result def __content(self): """数据表格""" self.__tableView = tkinter.ttk.Treeview(master=self.__root, columns=( "路径", "创建时间", "修改时间", "访问时间", "大小","文件属性"), selectmode=tkinter.EXTENDED) # 建立栏标题 self.__tableView.heading("#0", text="名称") self.__tableView.heading("#1", text="路径") self.__tableView.heading("#2", text="创建时间") self.__tableView.heading("#3", text="修改时间") self.__tableView.heading("#4", text="访问时间") self.__tableView.heading("#5", text="大小", command=lambda c=5: self.__tableView_sortColumn(c)) self.__tableView.heading("#6", text="文件属性") # 固定列宽 self.__table_cloumnSize(None) # 右对齐 self.__tableView.column("大小", anchor=tkinter.E) # 滚动条 self.__scroll_bar() # 实例化滚动条对象 self.__tableView.config(yscrollcommand=self.__scrollBar_y.set, xscrollcommand=self.__scrollBar_x.set) # 数据表格与滚动条做联动 # 填充内容 self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) # self.__filling_init(self.__search_result) # 绑定函数 self.__tableView.bind( "<Double-1>", self.__tableView_doubleClike) # 双击事件 self.__tableView.bind( "<Return>", self.__tableView_enterEvent) # 回车键盘事件 self.__tableView.bind( "<Delete>", self.__tableView_deleteEvent) # 回车键盘事件 self.__tableView.bind( "<Button-3>", self.__tableView_rightClick) # 右键事件 self.__tableView.bind("<Button-1>", self.__tableView_Click) # 单击事件 self.__tableView.bind( "<Control-c>", self.__copyFiles_toClip) # 复制选中的文件到剪贴板 self.__tableView.bind( "<Control-x>", self.__cutFiles_toClip) # 剪切选中的文件到剪贴板 # 显示 self.__tableView.pack(fill=tkinter.BOTH, expand=1) def __content_copy(self, event): '''复制选中行和鼠标所在列交集处的内容''' event_widget = event.widget # 获取发出事件的控件 selec_lins_ids = event_widget.selection() # 返回选中的行ID元组 column = event_widget.identify("column", event.x, event.y) # 获取右击所在的列 content_str = "" index = int(column[1:]) - 1 if index < 0: for i in selec_lins_ids: content_str += "{}\n".format(event_widget.item(i, "text")) else: for i in selec_lins_ids: content_str += "{}\n".format(event_widget.item(i, "values")[index]) content_str = content_str.strip() pyperclip.copy(content_str) # 将字符串发送到剪贴板上 def __tableView_Click(self, event): '''鼠标单击事件,主要是在状态栏上显示点击的文件信息''' e = event.widget # 获取发出事件的控件 column = e.identify("row", event.x, event.y) # 返回单击所在的行ID values = e.item(column, "values") filePath = '' if len(values) > 0: filePath = values[0] else: return fileName = e.item(column, "text") fileABSpath = "{}\\{}".format(filePath, fileName) if fileABSpath == "\\": self.__statusBar.config(text="", fg="green") return file_info = "文件 {}".format(fileName) if os.path.exists(fileABSpath): file_info += "目前存在;{}".format(values[5]) self.__statusBar.config(text=file_info, fg="green") else: file_info += "不存在;" self.__statusBar.config(text=file_info, fg="red") def __tableView_rightClick(self, event): '''右键事件''' def popUpMenu(openfile_name=None,copyfile_name=None,cutfile_name=None,copy_content_name=None,openfile_localtion_name=None): ''' openfile_name:打开文件菜单的名称,用于显示 copyfile_name:复制文件菜单的名称,用于显示 cutfile_name:剪切文件菜单的名称,用于显示 copy_content_name:复制点击处内容菜单的名称,用于显示 openfile_localtion_name:打开文件所在位置菜单的名称,用于显示 ''' popupMenu = tkinter.Menu( master=self.__root, tearoff=False) # 隐藏虚线分割线 popupMenu.add_command(label= (lambda x: "打开文件" if x == None else x)(openfile_name) , command=lambda: self.__open_files(event)) popupMenu.add_command(label=(lambda x: "复制文件" if x == None else x)(copyfile_name), command=lambda: self.__copyFiles_toClip(event)) popupMenu.add_command(label=(lambda x: "剪切文件" if x == None else x)(cutfile_name), command=lambda: self.__cutFiles_toClip(event)) popupMenu.add_command(label=(lambda x: "复制名称" if x == None else x)(copy_content_name), command=lambda: self.__content_copy(event)) popupMenu.add_command(label=(lambda x: "打开文件所在位置" if x == None else x)(openfile_localtion_name), command=lambda: self.__open_fileLocation(event)) popupMenu.post(event.x_root, event.y_root) # 显示菜单; e = event.widget # 获取发出事件的控件 if len(e.get_children()) == 0: # 若是没有子项就不做处理 return line_x = column = e.identify("row", event.x, event.y) # 返回右键所在的行ID selec_x = e.selection() # 返回选中的行ID元组 if len(selec_x) == 0 or line_x not in selec_x: # 刷新 def refresh(event): ''' 显示弹出式菜单:刷新 刷新作用有两个,一个是根据搜索框内容重新显示结果,二是触发扫描 ''' self.__search_console(event) refresh_popupMenu = tkinter.Menu( master=self.__root, tearoff=False) # 隐藏虚线分割线 refresh_popupMenu.add_command( label="刷新", command=lambda: refresh(event)) refresh_popupMenu.post(event.x_root, event.y_root) # 显示菜单; return column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_x] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if(column == "#0"): # 建立弹出式菜单 # 打开,复制,剪切,重命名,复制名称 popUpMenu() elif (column == "#1"): popUpMenu(copy_content_name='复制路径') elif (column == "#2"): popUpMenu(copy_content_name='复制创建时间') elif (column == "#3"): popUpMenu(copy_content_name='复制修改时间') elif (column == "#4"): popUpMenu(copy_content_name='复制访问时间') elif (column == "#5"): popUpMenu(copy_content_name='复制大小') else: pass def __tableView_deleteEvent(self, event): """delete事件,删除选择的项目""" event_widget = event.widget ids = event_widget.selection() for id in ids: path = event_widget.item(id, "values")[0] name = event_widget.item(id, "text") file_absPath = "{}\\{}".format(path, name) if file_absPath == '\\': self.__statusBar.config(text="非法路径", fg="red") continue if self.__del_file(file_absPath): event_widget.delete(id) self.__statusBar.config( text="删除{}成功!".format(name), fg="green") # 源数据也需要删除的 self.__sourceData_del(name, path) else: self.__statusBar.config(text="删除{}失败!".format(name), fg="red") def __tableView_enterEvent(self, event): '''数据区域内的回车事件,回车事件的动作为打开选中的文件''' event_widget = event.widget ids = event_widget.selection() if len(ids) > 5: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开数量超过5个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开!", fg="black") return for id in ids: file_absPath = "{}\\{}".format(event_widget.item(id, "values")[ 0], event_widget.item(id, "text")) if file_absPath == '\\': self.__statusBar.config(text="非法路径", fg="red") continue self.__open_file(file_absPath) label_str = self.__statusBar.cget(key="text") # 获取text的值 # if ret: # self.__statusBar.config( # text="{};打开成功!".format(label_str), fg="green") # else: # self.__statusBar.config( # text="{};打开失败!".format(label_str), fg="red") def __table_cloumnSize(self, event): '''调整__tableView的列宽''' # 固定列宽 self.__tableView.column("#0", width=350) self.__tableView.column("#1", width=350) self.__tableView.column("#2", width=130) self.__tableView.column("#3", width=130) self.__tableView.column("#4", width=130) self.__tableView.column("#5", width=50) self.__tableView.column("#6", width=130) def __sourceData_del(self, key, value): '''删除__data变量里面的相关数据''' if type({"a": 1}) == type(self.__data): for data_i in self.__data[key]: index = -1 # 存储下标 is_flag = False # 标志是否找到 for list_i in data_i: index += 1 if list_i[0] == value: is_flag = True break if is_flag: if len(self.__data[key]) == 1: del self.__data[key] else: del self.__data[key][index] else: tkinter.messagebox.showinfo( title="异常", message="没有在源数据中找到对应的需要删除的数据:{}\\{}".format(value, key)) def __del_file(self, path_str): '''删除文件(夹),这里会做确认操作;但是若只是删除一个项目还好说,若是删除文件夹,连带着会删除很多项目,这样去显示的时候就比较麻烦 考虑了一下,若是文件夹下面是空的,那么可以删除,若是文件夹下面有东西,那么不允许删除。 ''' if(os.path.exists(path_str)): if(os.path.isdir(path_str)): # 是文件夹 try: # os.rmdir(path_str) shell.SHFileOperation((0, shellcon.FO_DELETE, path_str, None, shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION, None, None)) # 删除文件到回收站 except OSError: tkinter.messagebox.showinfo( title="失败", message="文件夹:{} 删除失败,原因是:\n目标文件夹非空".format(path_str)) return False return True else: try: # os.remove(path_str) shell.SHFileOperation((0, shellcon.FO_DELETE, path_str, None, shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION, None, None)) # 删除文件到回收站 except Exception as e: tkinter.messagebox.showinfo( title="失败", message="文件:{} 删除失败,原因是:\n{}".format(path_str, str(e))) return False return True else: # 文件不存在了,这时候需要更新一下显示,所以仍然返回True。同时也做一个提示 tkinter.messagebox.showinfo( title="提示", message="文件(夹):{} 不存在".format(path_str)) return True def __scroll_bar(self): """滚动条""" # Y轴 self.__scrollBar_y = tkinter.Scrollbar( master=self.__tableView, command=self.__tableView.yview) self.__scrollBar_y.pack(side=tkinter.RIGHT, fill=tkinter.Y) # X轴 self.__scrollBar_x = tkinter.Scrollbar( orient=tkinter.HORIZONTAL, master=self.__tableView, command=self.__tableView.xview) self.__scrollBar_x.pack(side=tkinter.BOTTOM, fill=tkinter.X) def __filling_init(self, data): """ 将扫描的数据填充到控件中,仅填充前1000行(由self.__max_showLine控制) """ def size_show(size): '''友好化文件大小字符串''' unit_dict = {0: "B", 1: "KB", 2: "MB", 3: "GB", 4: "TB", 5: "PB"} size = int(size) size_ = size unit_flag_int = 0 while size_ > 1024: size_ = size_ / 1024 unit_flag_int += 1 ret_size = "{}{}".format(round(size_, 2), unit_dict[unit_flag_int]) return ret_size if data != None and len(data) != 0: num = 0 # 清空 [self.__tableView.delete(id) for id in self.__tableView.get_children()] for line in data: num += 1 try: self.__tableView.insert("", index=tkinter.END, text=line[0], value=( line[1], line[2], line[3], line[4], size_show(line[5]),self.__attributes_analysis(line[6]))) except tkinter.TclError: #这里因为tk的缺陷,导致有些utf8字符是不能再tk里面显示的,比如:“Dusk DJ REMIX �🔥 女”,这个火的符号,是不能显示的。 print(line[0]) if num > self.__max_showLine: break self.__statusBar.config(text="查询出{}个结果;查询花费:{}秒。当前显示文件类型:{}".format(len(data), time.time() - self.__stratTime,self.__map_dict__[self.__filter_condition]), fg="black") else: # 清空 [self.__tableView.delete(id) for id in self.__tableView.get_children()] self.__statusBar.config(text="查询出{}个结果;查询花费:{}秒。当前显示文件类型:{}".format(len(data), time.time() - self.__stratTime,self.__map_dict__[self.__filter_condition]), fg="red") text_result = re.findall(r"(?<=结果数量:)\d+",self.__statusBar_right_TEXT) if len(text_result) == 0: self.__statusBar_right_TEXT += "结果数量:{};".format(len(data)) else: self.__statusBar_right_TEXT = re.sub(r'\d+','{}'.format(len(data)),self.__statusBar_right_TEXT) self.__statusBar_right.config(text = self.__statusBar_right_TEXT) # 填充一行空行,使得原本不能被显示完全的最后一行显示完全。同是避免搜索结果为空时,不可刷新 self.__tableView.insert("", index=tkinter.END,text="", value=("", "", "", "", "")) def __attributes_analysis(self,attributes): ''' 解析文件属性,返回字符串 ''' attributes = int(attributes) ret_str = "" if attributes & stat.FILE_ATTRIBUTE_READONLY == stat.FILE_ATTRIBUTE_READONLY: ret_str += '只读、' if attributes & stat.FILE_ATTRIBUTE_HIDDEN == stat.FILE_ATTRIBUTE_HIDDEN: ret_str += '隐藏、' if attributes & stat.FILE_ATTRIBUTE_SYSTEM == stat.FILE_ATTRIBUTE_SYSTEM: ret_str += '操作系统占用、' if attributes & stat.FILE_ATTRIBUTE_NORMAL == stat.FILE_ATTRIBUTE_NORMAL: ret_str += '无属性、' if attributes & stat.FILE_ATTRIBUTE_TEMPORARY == stat.FILE_ATTRIBUTE_TEMPORARY: ret_str += '临时、' if attributes & stat.FILE_ATTRIBUTE_SPARSE_FILE == stat.FILE_ATTRIBUTE_SPARSE_FILE: #稀疏文件:其中很多数据为零的文件被称为包含 稀疏数据集。 例如,文件通常非常大,如包含要处理的图像数据的文件,或高速数据库内的矩阵。 包含稀疏数据集的文件的问题在于,大多数文件都不包含有用的数据,因此,它们对磁盘空间的使用效率低下。 #https://docs.microsoft.com/zh-cn/windows/win32/fileio/sparse-files ret_str += '稀疏、' if attributes & stat.FILE_ATTRIBUTE_COMPRESSED == stat.FILE_ATTRIBUTE_COMPRESSED: ret_str += '压缩、' if attributes & stat.FILE_ATTRIBUTE_OFFLINE == stat.FILE_ATTRIBUTE_OFFLINE: ret_str += '脱机、' #文件的数据无法立即使用。 此属性指示文件数据以物理方式移动到脱机存储。 此属性由远程存储存储,即分层存储管理软件。 应用程序不应任意更改此属性。 if attributes & stat.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED == stat.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: ret_str += '不被索引的、' if attributes & stat.FILE_ATTRIBUTE_ENCRYPTED == stat.FILE_ATTRIBUTE_ENCRYPTED: ret_str += '加密、' if attributes & stat.FILE_ATTRIBUTE_VIRTUAL == stat.FILE_ATTRIBUTE_VIRTUAL: ret_str += '系统保留、' if attributes & stat.FILE_ATTRIBUTE_INTEGRITY_STREAM == stat.FILE_ATTRIBUTE_INTEGRITY_STREAM: ret_str += '完整性保护、' #该属性值只在refs文件系统上受支持,这是苹果设备上的东西(为了方便将Windows移植到苹果上),ntfs文件系统不会有这玩意。 if attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT == stat.FILE_ATTRIBUTE_REPARSE_POINT: ret_str += '符号链接、' #非快捷方式 if attributes & stat.FILE_ATTRIBUTE_DEVICE == stat.FILE_ATTRIBUTE_DEVICE: ret_str += '系统设备文件、' if attributes & stat.FILE_ATTRIBUTE_DIRECTORY == stat.FILE_ATTRIBUTE_DIRECTORY: ret_str += '目录、' if attributes & stat.FILE_ATTRIBUTE_ARCHIVE == stat.FILE_ATTRIBUTE_ARCHIVE: ret_str += '普通文件、' if ret_str[-1] == '、': return ret_str[0:-1] def __tableView_doubleClike(self, event): """数据区域的双击事件,根据双击的位置做出不同的响应""" e = event.widget # 获取发出事件的控件 if len(e.get_children()) == 0: # 若是没有子项就不做处理 return column = e.identify("column", event.x, event.y) # 获取双击所在的列 id_ = e.identify("item", event.x, event.y) ret = True # 是否打开成功 if(column == "#1"): # 双击的路径,打开文件资源管理器,定位到这个路径下 path_str = e.item(id_, "values")[0] ret = self.__open_fileLocation_run(path_str) else: # 其他地方 path_str = "{}\\{}".format(e.item(id_, "values")[ 0], e.item(id_, "text")) if path_str == '\\': self.__statusBar.config(text="非法路径", fg="red") return ret = self.__open_file(path_str) label_str = self.__statusBar.cget(key="text") # 获取text的值 if ret: self.__statusBar.config( text="{};打开成功!".format(label_str), fg="green") else: self.__statusBar.config( text="{};打开失败!".format(label_str), fg="red") def __open_file(self, filepath): ''' 打开指定的文件,使用系统默认方式; 打开之前会检查文件/目录是否存在。 成功返回True,不成功返回False ''' if os.path.exists(filepath): try: os.startfile(filepath) except Exception as e: errInfo = str(e) self.__statusBar.config(text=errInfo) return False return True else: self.__statusBar.config(text="该文件已经不存在了。", fg="red") return False def __open_fileLocation_run(self, filepath): '''打开文件所在的位置,执行代码,主要是使得目标文件在资源管理器中被选定。''' if os.path.exists(filepath): try: # os.startfile(filepath) subprocess.run("Explorer.exe /select,{}".format(filepath)) except OSError as msg: traceback.print_exc() return False return True else: self.__statusBar.config(text="该文件已经不存在了。", fg="red") return False def __open_files(self, event): '''多文件打开''' e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 5: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开数量超过5个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开!", fg="black") return for filepath in filepaths_list: ret = self.__open_file(filepath) label_str = self.__statusBar.cget(key="text") # 获取text的值 if ret: self.__statusBar.config( text="{};打开成功!".format(label_str), fg="green") else: self.__statusBar.config( text="{};打开失败!".format(label_str), fg="red") def __copyFiles_toClip(self, event): '''复制文件到Windows剪贴板''' # 参考:https://chowdera.com/2021/10/20211031055535475l.html # 这其实是一个结构体,用以记录文件的各种信息。 class DROPFILES(ctypes.Structure): _fields_ = [ ("pFiles", ctypes.c_uint), ("x", ctypes.c_long), ("y", ctypes.c_long), ("fNC", ctypes.c_int), # 指示文件是否包含 ANSI 或 Unicode 字符。如果值为零,则文件包含 ANSI 字符。否则,它包含 Unicode 字符。 ("fWide", ctypes.c_bool), ] pDropFiles = DROPFILES() pDropFiles.pFiles = ctypes.sizeof(DROPFILES) pDropFiles.fWide = True #a = bytes(pDropFiles) # 获取文件绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 500: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要复制数量超过500个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消复制!", fg="black") return files = ("\0".join(filepaths_list)).replace("/", "\\") # 结尾一定要两个\0\0字符,这是规定!就是构造NULL字符 data = files.encode("U16")[2:] + b"\0\0" ''' 对于多个文本路径,我们如何将其转换为我们需要的Unicode 双字节形式呢? 首先,我们要知道Unicode编码采用UCS-2格式直接存储,而UTF-16恰好对应于UCS-2的,即UCS-2指定的码位通过大端或小端的方式直接保存。UTF-16 有三种类型:UTF-16,UTF-16BE(大端序),UTF-16LE(小端序).UTF-16 通过以名称BOM(字节顺序标记,U + FEFF)启动文件来指示该文件仍然是小端序。 我们只需要把python String使用UTF-16编码后,去掉前两个字节,得到相应的Unicode双字节。 ''' win32clipboard.OpenClipboard() # 打开剪贴板(独占) try: # 若要将信息放在剪贴板上,首先需要使用 EmptyClipboard 函数清除任何以前的剪贴板内容 win32clipboard.EmptyClipboard() # 清空当前的剪贴板信息,否则不能写入 #uDropEffect = win32clipboard.RegisterClipboardFormat("Preferred DropEffect") win32clipboard.SetClipboardData( win32clipboard.CF_HDROP, bytes(pDropFiles)+data) # 设置当前剪贴板数据 # win32clipboard.SetClipboardData(uDropEffect,b'\x02\x00\x00\x00') #print("error= ",ctypes.windll.kernel32.GetLastError()) self.__statusBar.config(text="复制成功!", fg="green") except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_tb(exc_traceback_obj) self.__statusBar.config(text="复制失败!", fg="red") finally: win32clipboard.CloseClipboard() # 无论什么情况,都关闭剪贴板 def __cutFiles_toClip(self, event): '''剪切文件到剪贴板''' # 这其实是一个结构体,用以记录文件的各种信息。 class DROPFILES(ctypes.Structure): _fields_ = [ ("pFiles", ctypes.c_uint), ("x", ctypes.c_long), ("y", ctypes.c_long), ("fNC", ctypes.c_int), # 指示文件是否包含 ANSI 或 Unicode 字符。如果值为零,则文件包含 ANSI 字符。否则,它包含 Unicode 字符。 ("fWide", ctypes.c_bool), ] pDropFiles = DROPFILES() pDropFiles.pFiles = ctypes.sizeof(DROPFILES) pDropFiles.fWide = True # 获取文件绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 50: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要剪切数量超过50个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消剪切!", fg="black") return files = ("\0".join(filepaths_list)).replace("/", "\\") # 结尾一定要两个\0\0字符,这是规定!就是构造NULL字符 data = files.encode("U16")[2:] + b"\0\0" ''' 对于多个文本路径,我们如何将其转换为我们需要的Unicode 双字节形式呢? 首先,我们要知道Unicode编码采用UCS-2格式直接存储,而UTF-16恰好对应于UCS-2的,即UCS-2指定的码位通过大端或小端的方式直接保存。UTF-16 有三种类型:UTF-16,UTF-16BE(大端序),UTF-16LE(小端序).UTF-16 通过以名称BOM(字节顺序标记,U + FEFF)启动文件来指示该文件仍然是小端序。 我们只需要把python String使用UTF-16编码后,去掉前两个字节,得到相应的Unicode双字节。 ''' win32clipboard.OpenClipboard() # 打开剪贴板(独占) try: # 若要将信息放在剪贴板上,首先需要使用 EmptyClipboard 函数清除任何以前的剪贴板内容 win32clipboard.EmptyClipboard() # 清空当前的剪贴板信息,否则不能写入 uDropEffect = win32clipboard.RegisterClipboardFormat( "Preferred DropEffect") win32clipboard.SetClipboardData( win32clipboard.CF_HDROP, bytes(pDropFiles)+data) # 设置当前剪贴板数据 win32clipboard.SetClipboardData(uDropEffect, b'\x02\x00\x00\x00') err = ctypes.windll.kernel32.GetLastError() if err == 0: self.__statusBar.config(text="剪切成功!", fg="green") else: self.__statusBar.config( text="剪切失败!errorCode={}".format(err), fg="red") except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_tb(exc_traceback_obj) self.__statusBar.config(text="剪切失败!", fg="red") finally: win32clipboard.CloseClipboard() # 无论什么情况,都关闭剪贴板 def __open_fileLocation(self, event): '''打开文件所在的位置''' # 获取文件的绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 1: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开{}个文件的所在位置,确定吗?".format(len(filepaths_list))) if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开文件所在位置!", fg="black") return for path in filepaths_list: self.__open_fileLocation_run(path) def __close_to_missionBoard(self): '''关闭,到任务栏''' self.__root.iconify() # 实际上是最小化了 def Hidden_window(self, icon=r'python_everything.ico', hover_text="python everything"): # ico文件一定要是合法的,不能使用改后缀的方式 '''隐藏窗口至托盘区,调用SysTrayIcon的重要函数''' # 托盘图标右键菜单, 格式: ('name', None, callback),下面也是二级菜单的例子 # 24行有自动添加‘退出’,不需要的可删除 menu_options = ( # ('显示窗口', None, lambda:s.SysTrayIcon.destroy(exit = 0)), #我放到后面去了。 # ('二级 菜单', None, (('更改 图标', None, s.switch_icon), ))) #('退出', None, s.exit) ) self.__root.withdraw() # 隐藏tk窗口 if not self.__SysTrayIcon: self.__SysTrayIcon = sysTrayIcon.SysTrayIcon( icon, # 图标 hover_text, # 光标停留显示文字 menu_options, # 右键菜单 on_quit=self.exit_off, # 退出调用 tk_window=self.__root, # Tk窗口 ) self.__SysTrayIcon.activation() def exit_off(self): '''退出''' ''' if self.__threading_Operating_condition.is_alive(): #若子进程还存活,等待子进程结束 self.__threading_Operating_condition.join() ''' os.kill(self.__pid, signal.SIGBREAK) # self.__root.destroy() def __searchBar_focus(self, event): '''快捷键Ctrl+f,捕捉到这个事件后,使搜索框得到焦点''' self.__searchBar.focus_set() # 使得搜索框获得焦点,便于快速输入搜索表达式 self.__searchBar.selection_range(0,tkinter.END) #选择全部内容 def __tableView_sortColumn(self, col_int): '''排序;col_int表示第几列(从0开始算)''' self.__search_result.sort(key=lambda x: int( x[col_int]), reverse=self.__sortFlag_col_size) self.__sortFlag_col_size = not self.__sortFlag_col_size self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) if not self.__sortFlag_col_size: self.__statusBar.config(text="从大到小排序", fg="green") else: self.__statusBar.config(text="从小到大排序", fg="green") def __type_filter(self, type_flag): '''筛选指定的类型文件 type_flag取值: all:所有文件 zip:压缩文件 audio:音频文件 video:视频文件 picture:图片文件 document:文档文件 execute:可执行文件 folder:文件夹 lnk:快捷方式 ''' type_dict = {'zip': ['rar', 'zip', 'cab', 'arj', 'lzh', 'ace', '7z', 'tar', 'gzip', 'uue', 'bz2', 'jar', 'iso', 'z'], 'audio': ['mp3', 'wma', 'wav', 'mid', 'ape', 'flac', 'weba', 'webm', 'cda', 'aif', 'aiff', 'ra', 'vqf', ], 'video': ['mpg', 'mpeg', 'avi', 'rmvb', 'mov', 'wmv', 'asf', 'asx', 'wvx', 'mpe', 'mpa', 'mp4', 'rm', 'navi', 'mkv', 'ogg', 'mod', ], 'picture': ['bmp', 'jpeg', 'png', 'tiff', 'gif', 'pcx', 'tga', 'exif', 'fpx', 'svg', 'psd', 'ico', 'jpg', 'wmf', 'tif', 'webp', ], 'document': ['pptx', 'pptm', 'ppt', 'pdf', 'xps', 'potx', 'potm', 'pot', 'thmx', 'ppsx', 'pps', 'ppam', 'ppa', 'xml', 'json', 'log', 'config', 'rtf', 'odp', 'wps', 'doc', 'docx', 'docm', 'dotx', 'dotm', 'dot', 'mht', 'mhtml', 'htm', 'html', 'odt', 'xlsx', 'xlsm', 'xlsb', 'xls', 'csv', 'xltx', 'xltm', 'xlt', 'prn', 'dif', 'slk', 'xlam', 'xla', 'ods', 'java', 'class', 'jav', 'bat', 'cmd', 'bib', 'c', 'cs', 'csx', 'cake', 'cpp', 'cc', 'cxx', 'c++', 'hpp', 'hh', 'h++', 'h', 'ii', 'clj', 'cljs', 'cljc', 'cljx', 'clojure', 'edn', 'cmake', 'coffee', 'cson', 'iced', 'css', 'cu', 'cuh', 'dart', 'i', 'diff', 'patch', 'rej', 'dockerfile', 'containerfile', 'fs', 'fsi', 'fsx', 'fsscript', 'go', 'groovy', 'gvy', 'gradle', 'jenkinsfile', 'nf', 'handlebars', 'hbs', 'hjs', 'hlsl', 'hlsli', 'fx', 'fxh', 'vsh', 'psh', 'cginc', 'compute', 'shtml', 'xhtml', 'xht', 'mdoc', 'jsp', 'asp', 'aspx', 'jshtm', 'js', 'gitgnore_global', 'hitigonre', 'npmignore', 'ini', 'es6', 'mjs', 'cjs', 'pac', 'jsx', 'j2', 'jinja2', 'bowerrc', 'jscsrc', 'webmainfest', 'js.map', 'css.map', 'ts.map', 'har', 'jslintrc', 'jsonld', 'jsonc', 'eslintrc', 'eslintrc.json', 'jsfmtrc', 'swcrc', 'hintrc', 'babelrc', 'code-workspace', 'language-configuration.json', 'jl', 'jmd', 'ipynb', 'tex', 'ltx', 'ctx', 'less', 'lua', 'mak', 'mk', 'md', 'mkd', 'mdwn', 'mdown', 'markdown', 'markdn', 'mdtxt', 'mdtext', 'workbook', 'm', 'mm', 'pl', 'pm', 'pod', 't', 'psgi', 'p6', 'pl6', 'pm6', 'nqp', 'php', 'php4', 'php5', 'phtml', 'ctp', 'ps1', 'psm1', 'psd1', 'pssc', 'psrc', 'properties', 'cfg', 'conf', 'directory', 'giattributes', 'gitconfig', 'gitmodules', 'editorconfig', 'npmrc', 'pug', 'jade', 'py', 'rpy', 'pyw', 'cpy', 'gyp', 'gypi', 'pyi', 'ipy', 'pyt', 'r', 'rhistory', 'rprofile', 'rt', 'cshtml', 'rb', 'rbx', 'rjs', 'gemspec', 'rake', 'ru', 'erb', 'podspec', 'rbi', 'rs', 'scss', 'code-search', 'shader', 'sh', 'bash', 'bashrc', 'bash_aliases', 'bash_profile', 'bash_login', 'ebuild', 'profile', 'bash_logout', 'xprofile', 'sql', 'dsql', 'swift', 'sty', 'cls', 'bbx', 'cbx', 'cts', 'mts', 'tsx', 'vb', 'brs', 'vbs', 'bas', 'vba', 'xsd', 'ascx', 'atom', 'axml', 'axaml', 'bpmn', 'cpt', 'csl', 'csproj', 'xsl', 'xslt', 'yml', 'eyaml', 'eyml', 'yaml', 'cff', 'txt', ], 'execute': ['exe', 'msi', 'bat', 'scr', 'cmd', 'bat', 'com', ], 'lnk': ['lnk'], } self.__statusBar_right_TEXT = re.sub(r'(?<=显示).+?(?=;)', self.__map_dict__[type_flag], self.__statusBar_right_TEXT) self.__statusBar_right.config(text=self.__statusBar_right_TEXT) # 初始化显示数据 self.__show_data = [] if type_flag == 'all': self.__show_data = self.__search_result elif type_flag == 'folder': for i_list in self.__search_result: if int(i_list[-1]) & stat.FILE_ATTRIBUTE_DIRECTORY == stat.FILE_ATTRIBUTE_DIRECTORY: self.__show_data.append(i_list) elif type_flag == 'zip': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['zip']: self.__show_data.append(i_list) elif type_flag == 'audio': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['audio']: self.__show_data.append(i_list) elif type_flag == 'video': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['video']: self.__show_data.append(i_list) elif type_flag == 'picture': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['picture']: self.__show_data.append(i_list) elif type_flag == 'document': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['document']: self.__show_data.append(i_list) elif type_flag == 'execute': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['execute']: self.__show_data.append(i_list) elif type_flag == 'lnk': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['lnk']: self.__show_data.append(i_list) else: tkinter.messagebox.showinfo(title="异常", message="参数有误:{}".format(type_flag)) def __filter_console(self, condition, index): '''显示condition所指定类型的文件 condition:字符串 index:下标,int ''' self.__stratTime = time.time() [self.__filterStatus_list[i].set(False) for i in range(9)] if index < len(self.__filterStatus_list): self.__filterStatus_list[index].set(True) else: tkinter.messagebox.showinfo(title="异常", message="筛选文件类型时,传入的下标发生越界。函数:__filter_console") return self.__filter_condition = condition self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) num = len(self.__show_data) if num > 0: if num > self.__max_showLine: self.__statusBar.config( text="筛选出{}个结果,此处只显示前{}个结果。所用时间:{};当前显示文件类型:{}".format(num,self.__max_showLine,time.time()-self.__stratTime,self.__map_dict__[self.__filter_condition]), fg="green") else: self.__statusBar.config( text="筛选出{}个结果。所用时间:{};当前显示文件类型:{}".format(num,time.time()-self.__stratTime,self.__map_dict__[self.__filter_condition]), fg="green") else: self.__statusBar.config(text="没有此类型文件。当前显示文件类型:{}".format(self.__map_dict__[self.__filter_condition]), fg="red") def __pathFilter(self): '''开启路径筛选 显示路径输入框,位于搜索框的右侧 路径匹配从左到右严格匹配 ''' if self.__path_filter_Status_bool: self.__path_filter_Status.set(False) self.__path_filter_Status_bool = False self.__pathBar.forget() #隐藏控件 self.__statusBar_right_TEXT = self.__statusBar_right_TEXT.replace("开启路径筛选","") self.__statusBar_right.config(text=self.__statusBar_right_TEXT) else: #显示 self.__path_filter_Status.set(True) self.__path_filter_Status_bool = True self.__pathBar.config() self.__pathBar.pack(side=tkinter.RIGHT,anchor=tkinter.E) self.__statusBar_right_TEXT = self.__statusBar_right_TEXT + "开启路径筛选" self.__statusBar_right.config(text = self.__statusBar_right_TEXT) def show(self): """#显示界面""" self.__root.mainloop()
sysTrayIcon.py
import win32api import win32con import win32gui_struct import win32gui import os class SysTrayIcon (object): '''SysTrayIcon类用于显示任务栏图标''' QUIT = 'QUIT' SPECIAL_ACTIONS = [QUIT] FIRST_ID = 5320 def __init__(s, icon, hover_text, menu_options, on_quit, tk_window=None, default_menu_index=None, window_class_name=None): ''' icon 需要显示的图标文件路径 hover_text 鼠标停留在图标上方时显示的文字 menu_options 右键菜单,格式: (('a', None, callback), ('b', None, (('b1', None, callback),))) on_quit 传递退出函数,在执行退出时一并运行 tk_window 传递Tk窗口,s.root,用于单击图标显示窗口 default_menu_index 不显示的右键菜单序号 window_class_name 窗口类名 ''' s.icon = icon s.hover_text = hover_text s.on_quit = on_quit s.root = tk_window #菜单是由上往下显示的,比如下面这个,显示在退出的上面 menu_options = menu_options + (("显示", None, lambda x: s.destroy(exit=0)) # 这里回调的时候,系统会传递一个参数,所以lambda表达式需要指明可以接受一个参数 , ('退出', None, s.destroy)) s._next_action_id = s.FIRST_ID s.menu_actions_by_id = set() s.menu_options = s._add_ids_to_menu_options(list(menu_options)) s.menu_actions_by_id = dict(s.menu_actions_by_id) del s._next_action_id s.default_menu_index = (default_menu_index or 0) s.window_class_name = window_class_name or "SysTrayIconPy" message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): s.restart, win32con.WM_DESTROY: s.destroy, win32con.WM_COMMAND: s.command, win32con.WM_USER+20: s.notify, } # 注册窗口类。 wc = win32gui.WNDCLASS() wc.hInstance = win32gui.GetModuleHandle(None) wc.lpszClassName = s.window_class_name wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) wc.hbrBackground = win32con.COLOR_WINDOW wc.lpfnWndProc = message_map # 也可以指定wndproc. s.classAtom = win32gui.RegisterClass(wc) def activation(s): '''激活任务栏图标,不用每次都重新创建新的托盘图标''' hinst = win32gui.GetModuleHandle(None) # 创建窗口。 style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU s.hwnd = win32gui.CreateWindow(s.classAtom, s.window_class_name, style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None) win32gui.UpdateWindow(s.hwnd) s.notify_id = None s.refresh(title='软件已后台!', msg='点击图标重新打开', time=500) # 发送气泡通知 win32gui.PumpMessages() def refresh(s, title='', msg='', time=500): '''刷新托盘图标 title 标题 msg 内容,为空的话就不显示提示 time 提示显示时间''' hinst = win32gui.GetModuleHandle(None) if os.path.isfile(s.icon): icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE hicon = win32gui.LoadImage(hinst, s.icon, win32con.IMAGE_ICON, 0, 0, icon_flags) else: # 找不到图标文件 - 使用默认值 hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) if s.notify_id: message = win32gui.NIM_MODIFY else: message = win32gui.NIM_ADD s.notify_id = (s.hwnd, 0, # 句柄、托盘图标ID # 托盘图标可以使用的功能的标识 win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP | win32gui.NIF_INFO, win32con.WM_USER + 20, hicon, s.hover_text, # 回调消息ID、托盘图标句柄、图标字符串 msg, time, title, # 提示内容、提示显示时间、提示标题 win32gui.NIIF_INFO # 提示用到的图标 ) win32gui.Shell_NotifyIcon(message, s.notify_id) def show_menu(s): '''显示右键菜单''' menu = win32gui.CreatePopupMenu() s.create_menu(menu, s.menu_options) pos = win32gui.GetCursorPos() win32gui.SetForegroundWindow(s.hwnd) win32gui.TrackPopupMenu(menu, win32con.TPM_LEFTALIGN, pos[0], pos[1], 0, s.hwnd, None) win32gui.PostMessage(s.hwnd, win32con.WM_NULL, 0, 0) def _add_ids_to_menu_options(s, menu_options): result = [] for menu_option in menu_options: option_text, option_icon, option_action = menu_option if callable(option_action) or option_action in s.SPECIAL_ACTIONS: s.menu_actions_by_id.add((s._next_action_id, option_action)) result.append(menu_option + (s._next_action_id,)) else: result.append((option_text, option_icon, s._add_ids_to_menu_options(option_action), s._next_action_id)) s._next_action_id += 1 return result def restart(s, hwnd, msg, wparam, lparam): s.refresh() def destroy(s, hwnd=None, msg=None, wparam=None, lparam=None, exit=1): if exit and s.on_quit: s.on_quit() # 需要传递自身过去时用 s.on_quit(s) else: s.root.deiconify() # 显示tk窗口 nid = (s.hwnd, 0) win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid) win32gui.PostQuitMessage(0) # 终止应用程序。 def notify(s, hwnd, msg, wparam, lparam): '''鼠标事件''' if lparam == win32con.WM_LBUTTONDBLCLK: # 双击左键 pass elif lparam == win32con.WM_RBUTTONUP: # 右键弹起 s.show_menu() elif lparam == win32con.WM_LBUTTONUP: # 左键弹起 s.destroy(exit=0) return True """ 可能的鼠标事件: WM_MOUSEMOVE #光标经过图标 WM_LBUTTONDOWN #左键按下 WM_LBUTTONUP #左键弹起 WM_LBUTTONDBLCLK #双击左键 WM_RBUTTONDOWN #右键按下 WM_RBUTTONUP #右键弹起 WM_RBUTTONDBLCLK #双击右键 WM_MBUTTONDOWN #滚轮按下 WM_MBUTTONUP #滚轮弹起 WM_MBUTTONDBLCLK #双击滚轮 """ def create_menu(s, menu, menu_options): for option_text, option_icon, option_action, option_id in menu_options[::-1]: if option_icon: option_icon = s.prep_menu_icon(option_icon) if option_id in s.menu_actions_by_id: item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text, hbmpItem=option_icon, wID=option_id) win32gui.InsertMenuItem(menu, 0, 1, item) else: submenu = win32gui.CreatePopupMenu() s.create_menu(submenu, option_action) item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text, hbmpItem=option_icon, hSubMenu=submenu) win32gui.InsertMenuItem(menu, 0, 1, item) def prep_menu_icon(s, icon): # 加载图标。 ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON) ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON) hicon = win32gui.LoadImage( 0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE) hdcBitmap = win32gui.CreateCompatibleDC(0) hdcScreen = win32gui.GetDC(0) hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y) hbmOld = win32gui.SelectObject(hdcBitmap, hbm) brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU) win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush) win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL) win32gui.SelectObject(hdcBitmap, hbmOld) win32gui.DeleteDC(hdcBitmap) return hbm def command(s, hwnd, msg, wparam, lparam): id = win32gui.LOWORD(wparam) s.execute_menu_option(id) def execute_menu_option(s, id): menu_action = s.menu_actions_by_id[id] if menu_action == s.QUIT: win32gui.DestroyWindow(s.hwnd) else: menu_action(s)
python_everything.py
import os import UI import time import scan_module a=UI.UI() a.show()
效果示例:
双击文件名称打开文件,双击路径打开所在文件夹
python_everything.ico:
多进程多线程模式(打包的时候有严重的bug)
python_eveything.py
只是变了一点点
import UI import scan_module if __name__ == "__main__": ''' 这个版本是多进程的,总体运行流程是:ui界面起来时,会生成子线程(使用threading),然后子线程使用Process生成扫描进程,扫描进程在根据盘符数量来生成子扫描进程,子扫描进程再根据根文件夹数量来生成对应数量的子线程来扫描。最终子扫描进程汇总数据,扫描进程处理子扫描进程回传的数据,至此扫描结束 启动时会启动扫描进程,在搜索时也会启动扫描进程,如果已存在扫描进程,就不会再启动(判断ui子进程是否存活来实现) 多进程多线程扫描会快一些(大概一倍多),但是相应的cpu占用也高。 ''' scan = scan_module.scan_module() # 扫描模块对象 ui = UI.UI(scan) ui.show()
scan_module.py
变得比较多,也存在许多实际上没有使用的代码,没有删除。
''' 本类需要用到的模块信息 ''' import os import string import time from copy import _reconstruct from multiprocessing import Process, Queue,Manager import threading import signal from unicodedata import name ''' scan_module.py 本文件内容是扫描模块,作用是用于扫描和保存扫描结果。 代码不再使用或大幅度减少函数式编程代码,改为使用对象编程的方法。 本代码只适用于Windows系统 ''' class scan_module: ''' 扫描模块,实现扫描的核心代码和存储扫描出来的数据。 ''' __all_file_dict = {} #最终的数据存放 Process_scan = '' #存放进程对象 selfnum = 0 #扫描次数 selftime = 0 #本次扫描用时 scan_childPro = [] #扫描子进程对象地址 selfcompleteSign = False#扫描完成的标志,False表示未完成,True表示已完成 def __init__(self): ''' 实例化对象的时候就初始化出需要扫描的根路径 ''' self.__paths_list = ['C:\\Windows\\'] # 存储盘符,变量类型为列表,C:\Windows目录太庞大,做特殊处理,但是这个是针对进程版本的扫描,而非普通单线程扫描。 self.__get_DriveLetter() # 初始化盘符列表变量。 def __get_DriveLetter(self): ''' 获取盘符列表,是通过猜测验证其是否存在。 私有函数 ''' self.__paths_list = ['C:\\Windows\\'] #每次都进行初始化,不然会越来越多的进程 for i in string.ascii_uppercase: if os.path.exists(i + ':\\'): self.__paths_list.append(i + ':\\') # else的情况不考虑停止,实际上,用户的磁盘盘符完全可以不连续。 def __get_fileList(self, initial_path): ''' 扫描,私有函数 ''' try: child_file_list = os.scandir(initial_path) except: # 遇到异常则直接终止函数 # print(initial_path) return for file_ in child_file_list: file_stat = file_.stat(follow_symlinks=False) time_local = time.localtime(file_stat.st_ctime) # Ctiem,创建时间 file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # Mtime,最近的修改时间 time_local = time.localtime(file_stat.st_mtime) file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # atime,最近的访问时间 time_local = time.localtime(file_stat.st_atime) file_atime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # size,文件大小(以字节为单位),文件可以是常规文件或符号链接。符号链接的大小是它包含的路径的长度,不包括末尾的空字节。 file_size = int(file_stat.st_size) isDir = 0 if file_.is_dir(): isDir = 1 else: isDir = 0 #isDir应该永远位于列表的最后一位 if file_.name in self.__all_file_dict.keys(): self.__all_file_dict[file_.name].append( [initial_path, file_ctime, file_mtime, file_atime, file_size, isDir,]) else: self.__all_file_dict[file_.name] = [ [initial_path, file_ctime, file_mtime, file_atime, file_size, isDir,], ] # 是否是目录 if file_.is_dir(): self.__get_fileList(file_.path) def get_fileList_thread(self,initial_path,in_data): '''扫描功能,但是是线程版本的''' if initial_path == "C:\\Windows": return try: child_file_list = os.scandir(initial_path) except: # 遇到异常则直接终止函数 # print(initial_path) return for file_ in child_file_list: file_stat = file_.stat(follow_symlinks=False) time_local = time.localtime(file_stat.st_ctime) # Ctiem,创建时间 file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # Mtime,最近的修改时间 time_local = time.localtime(file_stat.st_mtime) file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # atime,最近的访问时间 time_local = time.localtime(file_stat.st_atime) file_atime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # size,文件大小(以字节为单位),文件可以是常规文件或符号链接。符号链接的大小是它包含的路径的长度,不包括末尾的空字节。 file_size = int(file_stat.st_size) isDir = 0 if file_.is_dir(): isDir = 1 else: isDir = 0 #isDir应该永远位于列表的最后一位 if file_.name in in_data.keys(): in_data[file_.name].append( [initial_path, file_ctime, file_mtime, file_atime, file_size, isDir,]) else: in_data[file_.name] = [ [initial_path, file_ctime, file_mtime, file_atime, file_size, isDir,], ] # 是否是目录 if isDir: self.get_fileList_thread(file_.path,in_data) def get_fileList_v2(self): ''' 听说了一个os.walk库,专用于遍历文件存在的 比我自己写的慢一倍。 ''' start_time = time.time() for path_i in self.__paths_list: for dirpath, dirnames, filenames in os.walk(path_i): for name in dirnames: absPath = os.path.join(dirpath, name) if name in self.__all_file_dict.keys(): self.__all_file_dict[name].append([absPath, ]) else: self.__all_file_dict[name] = [[absPath, ], ] for name in filenames: absPath = os.path.join(dirpath, name) if name in self.__all_file_dict.keys(): self.__all_file_dict[name].append([absPath, ]) else: self.__all_file_dict[name] = [[absPath, ], ] print("V2版本的扫描时间:{}".format(time.time() - start_time)) def get_fileList_thread_console(self,DriveLetter,info): ''' DriveLetter:盘符 本函数是扫描功能(线程)的控制函数,做第一级控制 由于C:\windows目录实在是太庞大,所以这个目录也需要做一级处理 ''' try: child_file_list = os.scandir(DriveLetter) except Exception as e: print("函数get_fileList_thread_console,遇到异常。原因:{}".format(e)) return folder_num = 0 thread_dict = {} data_dict = {} for file_ in child_file_list: #最初一层级的目录和文件也要添加进去 file_stat = file_.stat(follow_symlinks=False) time_local = time.localtime(file_stat.st_ctime) # Ctiem,创建时间 file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # Mtime,最近的修改时间 time_local = time.localtime(file_stat.st_mtime) file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # atime,最近的访问时间 time_local = time.localtime(file_stat.st_atime) file_atime = time.strftime("%Y-%m-%d %H:%M:%S", time_local) # size,文件大小(以字节为单位),文件可以是常规文件或符号链接。符号链接的大小是它包含的路径的长度,不包括末尾的空字节。 file_size = int(file_stat.st_size) isDir = 0 if file_.is_dir(): isDir = 1 else: isDir = 0 #isDir应该永远位于列表的最后一位 if file_.name in data_dict.keys(): data_dict[file_.name].append( [DriveLetter, file_ctime, file_mtime, file_atime, file_size, isDir,]) else: data_dict[file_.name] = [ [DriveLetter, file_ctime, file_mtime, file_atime, file_size, isDir,], ] if file_.is_dir(): folder_num += 1 data_dict[file_.name] = {} thread_dict[file_.name] = threading.Thread(target=self.get_fileList_thread,args=("{}{}".format(DriveLetter,file_.name),data_dict[file_.name],)) thread_dict[file_.name].start() #运行线程 #print(file_.name) Pro_data = {} while True: #检查是否扫描完毕 c = False #是否全部结束 for key in thread_dict: if thread_dict[key].is_alive(): flag = False time.sleep(0.5) break else: if type([]) == type(data_dict[key]): if len(data_dict[key]) > 0: if key in Pro_data.keys(): Pro_data[key] += data_dict[key] else: Pro_data[key] = data_dict[key] elif type({}) == type(data_dict[key]): for data_i_i in data_dict[key]: if data_i_i in Pro_data.keys(): Pro_data[data_i_i] += data_dict[key][data_i_i] else: try: Pro_data[data_i_i] = data_dict[key][data_i_i] except KeyError as e: exit() data_dict[key] = [] flag = True if flag: break info.put(Pro_data) def scanProcess_console(self,info): '''扫描进程控制 但是这个不能用在tkinter中,哎,可惜 info:是向上一级传递 ''' start_time = time.time() scanProcess_Dict = {}; #扫描进程字典,以盘符为key manager = Manager() infos = manager.Queue() self.__get_DriveLetter() #扫描盘符 self.scan_childPro = [] #初始化子进程pid列表 for DriveLetter_i in self.__paths_list: scanProcess_Dict[DriveLetter_i] = Process(target = self.get_fileList_thread_console,args = (DriveLetter_i,infos,),name=DriveLetter_i) scanProcess_Dict[DriveLetter_i].start() #运行进程 self.scan_childPro.append(scanProcess_Dict[DriveLetter_i].pid) info.put(self.scan_childPro) #[] while True: flag = False scanProcess_Dict_copy = self.__my_deepcopy(scanProcess_Dict) for key in scanProcess_Dict_copy: if scanProcess_Dict[key].is_alive(): time.sleep(1) else: '''汇总数据''' valueNumber = infos.qsize(); #print("返回值的数量:{}".format(valueNumber)) for i in range(valueNumber): data_DriveLetter = infos.get(); for data_i in data_DriveLetter: if data_i in self.__all_file_dict.keys(): self.__all_file_dict[data_i] += data_DriveLetter[data_i] else: self.__all_file_dict[data_i] = data_DriveLetter[data_i] del scanProcess_Dict[key] if len(scanProcess_Dict) == 0: flag = True if flag: info.put(self.__all_file_dict) #{} info.put(time.time() - start_time) #float self.formatted_output() return time.time() - start_time def __check_Process_is_alive(self,scanProcess_Dict): ''' 检查进程是否还有存活的,是就返回true,否返回false ''' for key in scanProcess_Dict: if scanProcess_Dict[key].is_alive(): return True return False def selfconsole(self): ''' 调用扫描函数 ''' start_time = time.time() for path_i in self.__paths_list: self.__get_fileList(path_i) return time.time() - start_time #print("扫描花费时间:{}".format(time.time() - start_time)) def formatted_output(self): ''' 将结果字典格式化输出到文件中存储。 输出到用户家目录下存放,名称叫python_class_eveything_data.data ''' out_file_path_str = os.environ['USERPROFILE'] + \ '\\python_class_eveything_data.data' with open(out_file_path_str, 'w', encoding='utf-8')as fd: # 偶尔会发生在迭代期间字典大小发生变化的运行时错误 # 之前在迭代期间字典大小发生变化的bug解决了,但是这里就这样吧。 data = self.__my_deepcopy(self.__all_file_dict) for key, value in data.items(): for value_i in value: line_str = key for value_i_i in value_i: line_str += "\t{}".format(value_i_i) fd.write(line_str + '\n') del data def __my_deepcopy(self, x): ''' 深拷贝,copy库的deepcopy性能不太行,所以在deepcopy的基础上改了一下 这个深拷贝函数仅用于本脚本中,copy库中的deepcopy函数慢是有原因的,了解之后再使用。 ''' reductor = getattr(x, "__reduce_ex__", None) if reductor is not None: rv = reductor(4) y = _reconstruct(x, None, *rv) return y def get_data(self): """ 返回数据的副本,同时删除源数据 """ y = self.__my_deepcopy(self.__all_file_dict) self.__all_file_dict = {} return y def get_file_num(self): ''' 获取扫描的文件数量,在执行get_data之后此函数失去准确性 ''' num = len(self.__all_file_dict) print("文件数量(只统计key):{}。".format(num)) return num def get_data_dict(self,info): """获取扫描数据的实际代码""" #time_ = self.selfconsole() self.selftime = self.scanProcess_console() self.formatted_output() info.put(self.__all_file_dict) #{} info.put(self.selftime) #float info.put(self.scan_childPro) #[] def selfconsole(self): """控制扫描进程""" manager = Manager() infos = manager.Queue() if self.Process_scan == '' or self.Process_scan.is_alive() == False: #扫描未开始或者扫描已结束才开始再次扫描 self.selfcompleteSign = False self.Process_scan = Process(target = self.scanProcess_console,args=(infos,)) self.Process_scan.start() if self.Process_scan.is_alive() == True: #判断进程是否结束,接受回传的数据 while True: time.sleep(0.5) if self.Process_scan.is_alive() == False: ret_num = infos.qsize() for i in range(ret_num): info_data = infos.get(); if type({}) == type(info_data): #print("数据已传递") #扫描数据 self.__all_file_dict = info_data elif type(1.1) == type(info_data): #print("时间已传递") #扫描时间 self.selftime = info_data else: print("传递了{}类型的数据。".format(type(info_data))) self.selfcompleteSign = True self.selfnum += 1 break else: ret_num = infos.qsize() if ret_num == 1: #获取子进程PID info_data = infos.get(); if type([]) == type(info_data): #print("pid已传递:{}".format(info_data)) #子进程PID self.scan_childPro = info_data else: print("传递了{}类型的数据。".format(type(info_data))) def kill_scan(self): if self.Process_scan != "": if self.Process_scan.is_alive(): if len(self.scan_childPro) > 0: for i in self.scan_childPro: try: os.kill(int(i),signal.SIGBREAK) except OSError: #说明不存在或者访问被拒绝,但是不进行进一步处理 pass self.Process_scan.terminate() #结束扫描进程 if __name__ == "__main__": a = scan_module() while True: print("扫描花费时间:{}".format(a.scanProcess_console())) a.formatted_output() a.get_file_num() a.get_data()
sysTrayIcon.py
这个模块没有发生变化,直接使用前面的代码即可
UI.py
有变化,但是也不太多
import ctypes # 文件操作-剪贴板操作相关 import os import re import sys import threading import time import tkinter import signal import tkinter.messagebox import tkinter.ttk import traceback import subprocess import pyperclip # 字符串-剪贴板相关 import win32clipboard # 文件操作-剪贴板操作相关 from win32com.shell import shell, shellcon import sysTrayIcon class UI: ''' 界面 ''' def __init__(self,scan_): self.__root = tkinter.Tk() self.__root.title("python_everything") self.__root.iconbitmap( r"C:\Program Files\Internet Explorer\images\bing.ico") self.__root.geometry("1200x500") self.__SysTrayIcon = None # 值在Hidden_window函数中初始化 # 根窗口大小改变的事件 self.__root.bind("<Configure>", func=self.__table_cloumnSize) self.__root.protocol("WM_DELETE_WINDOW", self.Hidden_window) # 重定义关闭事件 # 绑定快捷键Ctrl+f,捕捉到这个事件后,使搜索框得到焦点 self.__root.bind("<Control-f>", func=self.__searchBar_focus) # 数据 self.__data = self.__get_data_fromFile() # 初始化一些变量 self.__is_caseSensitive = False # 是否区分大小写,FALSE表示不区分 self.__max_showLine = 1000 #最大显示的行数 self.__sortFlag_col_size = False #列“大小”如何排序(False表示从大到小,True表示从小到大) self.__search_result = self.__data #搜索的结果 self.__show_data = [] #最终显示的结果 self.__filter_condition = "all" #筛选条件,在显示数据前的最后一层条件 self.__threading_Operating_condition = '' self.__scan = scan_ # 扫描模块对象 self.__scan_num = 0 # 扫描次数 self.__pid = os.getpid() #用来结束自己的 self.__scan_console() self.__menu() self.__search_bar() self.__status_bar() self.__content() def __scan_console(self): """扫描以及数据获取""" def get_data_dict(): """获取扫描数据的实际代码""" self.__scan.selfconsole() self.__data = self.__scan.get_data() self.__scan_num = self.__scan.selfnum # 提示扫描完毕 self.__statusBar.config(fg="black", text="扫描完毕,用时:{}秒。".format(self.__scan.selftime)) if self.__threading_Operating_condition == '' or self.__threading_Operating_condition.is_alive() == False: self.__threading_Operating_condition = threading.Thread(target=get_data_dict) self.__threading_Operating_condition.start() def __get_data_fromFile(self): """#从文件中获取数据""" in_file_path_str = os.environ['USERPROFILE'] + \ '\\python_class_eveything_data.data' if(os.path.exists(in_file_path_str)): with open(in_file_path_str, "r", encoding='utf-8-sig') as in_fd: lines_list = in_fd.read().splitlines() lines_list_ = [i.split('\t') for i in lines_list] return lines_list_ else: tkinter.messagebox.showinfo( title="提示", message="数据文件不存在,请等待扫描结果出来之后再进行搜索。") return [] def __menu(self): """#建立菜单""" # 顶层菜单 self.__ui_menu_top = tkinter.Menu(master=self.__root) # 文件菜单,且取消虚线和分离功能 self.__ui_menu_file = tkinter.Menu( master=self.__ui_menu_top, tearoff=False) # 搜索菜单 self.__ui_menu_search = tkinter.Menu( master=self.__ui_menu_top, tearoff=False) # 关联顶层菜单和文件菜单 self.__ui_menu_top.add_cascade(label="文件", menu=self.__ui_menu_file) self.__ui_menu_top.add_cascade(label="搜索", menu=self.__ui_menu_search) # 文件菜单的菜单列表 # self.ui_menu_file.add_command() #self.__ui_menu_file.add_command(label="新建窗口", command=lambda: print("ok")) self.__ui_menu_file.add_command( label="退出", command=self.exit_off) # 创建具有复选框的子项 self.__demoStatus = tkinter.BooleanVar() self.__demoStatus.set(False) # 初始状态为不区分大小写,__demoStatus是一个bool值记录器。 self.__ui_menu_search.add_checkbutton( label="区分大小写", command=self.__case_sensitive, variable=self.__demoStatus) self.__ui_menu_search.add_separator() #添加分隔线 self.__filterStatus_list =[] [self.__filterStatus_list.append(tkinter.BooleanVar()) for i in range(9)] [self.__filterStatus_list[i].set(False) for i in range(9)] self.__filterStatus_list[0].set(True) #所有文件这个选择时默认勾选上的 self.__ui_menu_search.add_checkbutton( label="所有文件", command= lambda cond="all",index=0 : self.__filter_console(cond,index), variable=self.__filterStatus_list[0]) self.__ui_menu_search.add_checkbutton( label="压缩文件", command=lambda cond="zip",index=1 : self.__filter_console(cond,index), variable=self.__filterStatus_list[1]) self.__ui_menu_search.add_checkbutton( label="音频文件", command=lambda cond="audio",index=2 : self.__filter_console(cond,index), variable=self.__filterStatus_list[2]) self.__ui_menu_search.add_checkbutton( label="视频文件", command=lambda cond="video",index=3 : self.__filter_console(cond,index), variable=self.__filterStatus_list[3]) self.__ui_menu_search.add_checkbutton( label="图片文件", command=lambda cond="picture",index=4 : self.__filter_console(cond,index), variable=self.__filterStatus_list[4]) self.__ui_menu_search.add_checkbutton( label="文档文件", command=lambda cond="document",index=5 : self.__filter_console(cond,index), variable=self.__filterStatus_list[5]) self.__ui_menu_search.add_checkbutton( label="可执行文件", command=lambda cond="execute",index=6 : self.__filter_console(cond,index), variable=self.__filterStatus_list[6]) self.__ui_menu_search.add_checkbutton( label="快捷方式", command=lambda cond="lnk",index=7 : self.__filter_console(cond,index), variable=self.__filterStatus_list[7]) self.__ui_menu_search.add_checkbutton( label="文件夹", command=lambda cond="folder",index=8 : self.__filter_console(cond,index), variable=self.__filterStatus_list[8]) # 显示菜单 self.__root.config(menu=self.__ui_menu_top) def __case_sensitive(self): '''使得搜索区分大小写''' if self.__is_caseSensitive: # 已经勾选了要区分大小写,再次点击就是不区分大小写了 self.__demoStatus.set(False) self.__is_caseSensitive = False else: self.__demoStatus.set(True) self.__is_caseSensitive = True def __status_bar(self): """状态栏""" self.__statusBar = tkinter.Label(master=self.__root, text="{}个对象".format( len(self.__data)), anchor=tkinter.W, relief=tkinter.RIDGE) self.__statusBar.pack(side=tkinter.BOTTOM, fill=tkinter.X) def __search_bar(self): """#搜索框""" # exportselection=0:禁止自动复制选取的字符串,当任何击键更改小组件的内容时进行回调函数 self.__searchBar = tkinter.Entry(master=self.__root, exportselection=0) self.__searchBar.bind("<Return>", self.__search_console) self.__searchBar.pack(fill=tkinter.X) # 事件 self.__searchBar.bind("<Button-3>", self.__searchBar_rightClik) def __searchBar_rightClik(self, event): '''搜索框右键,目前支持,复制,粘贴,剪切''' e = event.widget # 获取发出事件的控件 # 获取搜索框中的字符串 searcStr = e.get() # 是否有选中字符 is_checked = e.select_present() checked_str = "" start_index = 0 end_index = 0 if is_checked: # checked_str = e.select_get() #获取选中的文本 start_index = e.index(tkinter.ANCHOR) # 开始选择时的index end_index = e.index(tkinter.INSERT) # 结束选择时的index if start_index < end_index: # 从左向右选择 checked_str = searcStr[start_index: end_index] elif start_index > end_index: # 从右向左选择 checked_str = searcStr[end_index: start_index] else: checked_str = "" def copy_Str(Str): pyperclip.copy(Str) # 将字符串发送到剪贴板上 def cut_selectStr(Str, e): pyperclip.copy(Str) # 将字符串发送到剪贴板上 # e.select_clear() #清除所选字符串,妈的,竟然不管用 new_str = "" if start_index < end_index: new_str = searcStr[0:start_index] + searcStr[end_index:] e.delete(0, tkinter.END) e.insert(0, new_str) e.icursor(end_index - len(checked_str)) # 设置光标位置 elif start_index > end_index: # 从右向左选择 new_str = searcStr[0:end_index] + searcStr[start_index:] e.delete(0, tkinter.END) e.insert(0, new_str) e.icursor(start_index - len(checked_str)) # 设置光标位置 else: new_str = "" def cut_allStr(Str, e): pyperclip.copy(Str) # 将字符串发送到剪贴板上 e.delete(0, tkinter.END) # 清空整个字符串 def paste(e): str_ = pyperclip.paste() # 获取剪贴板上的文字内容 index_ = e.index(tkinter.INSERT) # 获取光标当前的位置 e.insert(index_, str_) def select_all(e): e.select_range(0, tkinter.END) def del_allStr(e): e.delete(0, tkinter.END) # 清空整个字符串 def del_selectStr(e): e.select_clear() # 清除所选字符串 # 建立弹出式菜单 popupMenu = tkinter.Menu(master=self.__root, tearoff=False) # 隐藏虚线分割线 if is_checked: popupMenu.add_command( label="复制所选文字", command=lambda: copy_Str(checked_str)) popupMenu.add_command( label="复制全部文字", command=lambda: copy_Str(searcStr)) if is_checked: popupMenu.add_command( label="剪切所选文字", command=lambda: cut_selectStr(checked_str, e)) popupMenu.add_command( label="剪切全部文字", command=lambda: cut_allStr(searcStr, e)) popupMenu.add_command(label="粘贴", command=lambda: paste(e)) popupMenu.add_command(label="全选", command=lambda: select_all(e)) if is_checked: popupMenu.add_command( label="删除所选文字", command=lambda: del_selectStr(e)) popupMenu.add_command(label="删除全部文字", command=lambda: del_allStr(e)) popupMenu.post(event.x_root, event.y_root) # 显示菜单; def __search_console(self, event): """ 搜索行为控制,是从文件读取的内容中进行搜索还是从字典中进行搜索; 每次搜索,都将调用扫描函数进行扫描获取数据 """ self.__scan_console() #尝试启动扫描 result_list = '' if self.__scan_num > 0: # 此时self.__data是字典类型 result_list = self.__find_match_dict() else: # 此时self.__data是列表类型 result_list = self.__find_match() self.__search_result = result_list self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) def __find_match(self): ''' 查询函数,从列表中读取,没有进行性能优化,即没有聚合成大字符串,再一次性匹配出所有符合条件的值得处理。 ''' #print("regex_str = {}".format(regex_str)) regex_str = self.__searchBar.get() if regex_str == "": self.__statusBar.config(text="搜索框中没有字符哦。", foreground="red") return [self.__data[i] for i in range(self.__max_showLine)] data = self.__data start_time = time.time() result_list = [] for i in data: if self.__is_caseSensitive: result_regex = re.findall(regex_str, i[0]) else: result_regex = re.findall(regex_str, i[0], re.I) if len(result_regex) != 0: result_list.append(i) self.__statusBar.config(text="匹配出{}个结果;查询花费:{}秒。".format( len(result_list), time.time() - start_time), fg="black") return result_list def __regex_translate(self, regex_str): """表达式翻译处理""" if regex_str == '': return "" else: # 需要判断输入的正则表达式是否合法,不合法的话,就需要提示用户重新输入,之所以不纠正是因为不要猜测用户的意图。 try: re.compile(regex_str) except: self.__statusBar.config(text="输入的正则表达式有误!", fg='red') return '' if regex_str[0] == '^': regex_str = regex_str[1:] replace_result_str = r"(?<=\t)" elif regex_str[0] == '.': # 提升数倍的性能(6倍) replace_result_str = r"(?<=\t)" else: replace_result_str = r'(?<=\t)[^\t]*?' for index in range(len(regex_str)): if regex_str[index] == '.': if index != 0 and regex_str[index - 1] == '\\': replace_result_str += regex_str[index] else: replace_result_str += r'[^\t]' else: replace_result_str += regex_str[index] if replace_result_str[-1] == "$": replace_result_str = replace_result_str[0:-1] replace_result_str += r'(?=\t)' else: replace_result_str += r'[^\t]*?(?=\t)' # print(replace_result_str) return replace_result_str def __find_match_dict(self): ''' 查询函数,从字典中读取,并生成结果列表类型变量,先将可以聚合成大字符串,在一次性匹配出所有符合条件的值。 ''' data = self.__data regex_str = self.__searchBar.get() regex_str = self.__regex_translate(regex_str) if regex_str == "": self.__statusBar.config(text="搜索框中没有字符哦。", foreground="red") return [] start_time = time.time() result_list = [] if type({}) == type(data): keys = data.keys() keys_str = "\t" keys_str += '\t'.join(keys) keys_str += '\t' if self.__is_caseSensitive: result_key = re.findall(regex_str, keys_str) else: result_key = re.findall(regex_str, keys_str, re.I) # print("匹配出{}个结果。".format(len(result_key))) #这里输出的是不重复的文件数量 #print("result_key = {}".format(result_key)) for key in result_key: temp_ = {} temp_[key] = data[key] result_list += self.__dict_Conversion_list(temp_) else: # print("未传入字典类型变量!") self.__statusBar.config(text="未传入字典类型变量!", fg='red') return [] #print('查询花费:{}秒。'.format(time.time() - start_time)) #print("result_list = {}".format(result_list)) self.__statusBar.config(text="匹配出{}个结果;查询花费:{}秒。".format( len(result_list), time.time() - start_time), fg="black") return result_list def __dict_Conversion_list(self, dict_data): ''' 将字典键和值转变为列表 ''' if type({}) != type(dict_data): return [] return_result = [] for key, value in dict_data.items(): for value_i in value: temp_list = [] temp_list.append(key) for value_i_i in value_i: temp_list.append(value_i_i) return_result.append(temp_list) return return_result def __content(self): """数据表格""" self.__tableView = tkinter.ttk.Treeview(master=self.__root, columns=( "路径", "创建时间", "修改时间", "访问时间", "大小"), selectmode=tkinter.EXTENDED) # 建立栏标题 self.__tableView.heading("#0", text="名称") self.__tableView.heading("#1", text="路径") self.__tableView.heading("#2", text="创建时间") self.__tableView.heading("#3", text="修改时间") self.__tableView.heading("#4", text="访问时间") self.__tableView.heading("#5", text="大小",command=lambda c=5:self.__tableView_sortColumn(c)) # 固定列宽 self.__table_cloumnSize(None) # 右对齐 self.__tableView.column("大小", anchor=tkinter.E) # 滚动条 self.__scroll_bar() # 实例化滚动条对象 self.__tableView.config(yscrollcommand=self.__scrollBar_y.set, xscrollcommand=self.__scrollBar_x.set) # 数据表格与滚动条做联动 # 填充内容 self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) #self.__filling_init(self.__search_result) # 绑定函数 self.__tableView.bind( "<Double-1>", self.__tableView_doubleClike) # 双击事件 self.__tableView.bind( "<Return>", self.__tableView_enterEvent) # 回车键盘事件 self.__tableView.bind( "<Delete>", self.__tableView_deleteEvent) # 回车键盘事件 self.__tableView.bind( "<Button-3>", self.__tableView_rightClick) # 右键事件 self.__tableView.bind("<Button-1>", self.__tableView_Click) # 单击事件 self.__tableView.bind("<Control-c>",self.__copyFiles_toClip) #复制选中的文件到剪贴板 self.__tableView.bind("<Control-x>",self.__cutFiles_toClip) #剪切选中的文件到剪贴板 # 显示 self.__tableView.pack(fill=tkinter.BOTH, expand=1) def __content_copy(self, event): '''复制选中行和鼠标所在列交集处的内容''' event_widget = event.widget # 获取发出事件的控件 selec_lins_ids = event_widget.selection() # 返回选中的行ID元组 column = event_widget.identify("column", event.x, event.y) # 获取右击所在的列 content_str = "" index = int(column[1:]) - 1 if index < 0: for i in selec_lins_ids: content_str += "{}\n".format(event_widget.item(i, "text")) else: for i in selec_lins_ids: content_str += "{}\n".format(event_widget.item(i, "values")[index]) content_str = content_str.strip() pyperclip.copy(content_str) # 将字符串发送到剪贴板上 def __tableView_Click(self, event): '''鼠标单击事件,主要是在状态栏上显示点击的文件信息''' e = event.widget # 获取发出事件的控件 column = e.identify("row", event.x, event.y) # 返回单击所在的行ID filePath = e.item(column, "values") if len(filePath) > 0: filePath = filePath[0] else: return fileName = e.item(column, "text") fileABSpath = "{}\\{}".format(filePath, fileName) if fileABSpath == "\\": self.__statusBar.config(text="", fg="green") return file_info = "文件 {}".format(fileName) if os.path.exists(fileABSpath): file_info += "存在;" if os.path.isdir(fileABSpath): file_info += "是文件夹;" self.__statusBar.config(text=file_info, fg="yellow") return elif os.path.islink(fileABSpath): # elif fileABSpath[-3:] == "lnk": #妈的,python不支持Windows的快捷方式 file_info += "是快捷方式;" pointTO_path = os.readlink(fileABSpath) file_info += "指向{}".format(pointTO_path) self.__statusBar.config(text=file_info, fg="purple") else: file_info += "是普通文件;" self.__statusBar.config(text=file_info, fg="green") return else: file_info += "不存在;" self.__statusBar.config(text=file_info, fg="red") def __tableView_rightClick(self, event): '''右键事件''' e = event.widget # 获取发出事件的控件 if len(e.get_children()) == 0: # 若是没有子项就不做处理 return line_x = column = e.identify("row", event.x, event.y) # 返回右键所在的行ID selec_x = e.selection() # 返回选中的行ID元组 if len(selec_x) == 0 or line_x not in selec_x: # 刷新 def refresh(event): ''' 显示弹出式菜单:刷新 刷新作用有两个,一个是根据搜索框内容重新显示结果,二是触发扫描 ''' self.__search_console(event) refresh_popupMenu = tkinter.Menu( master=self.__root, tearoff=False) # 隐藏虚线分割线 refresh_popupMenu.add_command( label="刷新", command=lambda: refresh(event)) refresh_popupMenu.post(event.x_root, event.y_root) # 显示菜单; return column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_x] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if(column == "#0"): # 建立弹出式菜单 # 打开,复制,剪切,重命名,复制名称 popupMenu = tkinter.Menu( master=self.__root, tearoff=False) # 隐藏虚线分割线 popupMenu.add_command( label="打开文件", command=lambda: self.__open_files(event)) popupMenu.add_command( label="复制文件", command=lambda: self.__copyFiles_toClip(event)) popupMenu.add_command( label="剪切文件", command=lambda: self.__cutFiles_toClip(event)) popupMenu.add_command( label="复制名称", command=lambda: self.__content_copy(event)) popupMenu.add_command( label="打开文件所在位置", command=lambda: self.__open_fileLocation(event)) popupMenu.post(event.x_root, event.y_root) # 显示菜单; # elif (column == "#1"): else: # 建立弹出式菜单 # 打开,复制,剪切,重命名,复制名称 popupMenu = tkinter.Menu( master=self.__root, tearoff=False) # 隐藏虚线分割线 popupMenu.add_command( label="打开文件", command=lambda: self.__open_files(event)) popupMenu.add_command( label="复制文件", command=lambda: self.__copyFiles_toClip(event)) popupMenu.add_command( label="剪切文件", command=lambda: self.__cutFiles_toClip(event)) popupMenu.add_command( label="复制名称", command=lambda: self.__content_copy(event)) popupMenu.add_command( label="打开文件所在位置", command=lambda: self.__open_fileLocation(event)) popupMenu.post(event.x_root, event.y_root) # 显示菜单; def __tableView_deleteEvent(self, event): """delete事件,删除选择的项目""" event_widget = event.widget ids = event_widget.selection() for id in ids: path = event_widget.item(id, "values")[0] name = event_widget.item(id, "text") file_absPath = "{}\\{}".format(path, name) if file_absPath == '\\': self.__statusBar.config(text="非法路径", fg="red") continue if self.__del_file(file_absPath): event_widget.delete(id) self.__statusBar.config( text="删除{}成功!".format(name), fg="green") # 源数据也需要删除的 self.__sourceData_del(name, path) else: self.__statusBar.config(text="删除{}失败!".format(name), fg="red") def __tableView_enterEvent(self, event): '''数据区域内的回车事件,回车事件的动作为打开选中的文件''' event_widget = event.widget ids = event_widget.selection() if len(ids) > 5: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开数量超过5个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开!", fg="black") return for id in ids: file_absPath = "{}\\{}".format(event_widget.item(id, "values")[ 0], event_widget.item(id, "text")) if file_absPath == '\\': self.__statusBar.config(text="非法路径", fg="red") continue ret = self.__open_file(file_absPath) label_str = self.__statusBar.cget(key="text") # 获取text的值 if ret: self.__statusBar.config( text="{};打开成功!".format(label_str), fg="green") else: self.__statusBar.config( text="{};打开失败!".format(label_str), fg="red") def __table_cloumnSize(self, event): '''调整__tableView的列宽''' # 固定列宽 event_widget = self.__tableView event_widget.column("#0", width=350) event_widget.column("#1", width=350) event_widget.column("#2", width=130) event_widget.column("#3", width=130) event_widget.column("#4", width=130) def __sourceData_del(self, key, value): '''删除__data变量里面的相关数据''' if type({"a": 1}) == type(self.__data): for data_i in self.__data[key]: index = -1 # 存储下标 is_flag = False # 标志是否找到 for list_i in data_i: index += 1 if list_i[0] == value: is_flag = True break if is_flag: if len(self.__data[key]) == 1: del self.__data[key] else: del self.__data[key][index] else: tkinter.messagebox.showinfo( title="异常", message="没有在源数据中找到对应的需要删除的数据:{}\\{}".format(value, key)) def __del_file(self, path_str): '''删除文件(夹),这里会做确认操作;但是若只是删除一个项目还好说,若是删除文件夹,连带着会删除很多项目,这样去显示的时候就比较麻烦 考虑了一下,若是文件夹下面是空的,那么可以删除,若是文件夹下面有东西,那么不允许删除。 ''' if(os.path.exists(path_str)): if(os.path.isdir(path_str)): # 是文件夹 try: # os.rmdir(path_str) shell.SHFileOperation((0, shellcon.FO_DELETE, path_str, None, shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION, None, None)) # 删除文件到回收站 except OSError: tkinter.messagebox.showinfo( title="失败", message="文件夹:{} 删除失败,原因是:\n目标文件夹非空".format(path_str)) return False return True else: try: # os.remove(path_str) shell.SHFileOperation((0, shellcon.FO_DELETE, path_str, None, shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION, None, None)) # 删除文件到回收站 except Exception as e: tkinter.messagebox.showinfo( title="失败", message="文件:{} 删除失败,原因是:\n{}".format(path_str, str(e))) return False return True else: # 文件不存在了,这时候需要更新一下显示,所以仍然返回True。同时也做一个提示 tkinter.messagebox.showinfo( title="提示", message="文件(夹):{} 不存在".format(path_str)) return True def __scroll_bar(self): """滚动条""" # Y轴 self.__scrollBar_y = tkinter.Scrollbar( master=self.__tableView, command=self.__tableView.yview) self.__scrollBar_y.pack(side=tkinter.RIGHT, fill=tkinter.Y) # X轴 self.__scrollBar_x = tkinter.Scrollbar( orient=tkinter.HORIZONTAL, master=self.__tableView, command=self.__tableView.xview) self.__scrollBar_x.pack(side=tkinter.BOTTOM, fill=tkinter.X) def __filling_init(self, data): """ 将扫描的数据填充到控件中,仅填充前self.__max_showLine行 """ def size_show(size): '''友好化文件大小字符串''' unit_dict = {0: "B", 1: "KB", 2: "MB", 3: "GB", 4: "TB", 5: "PB"} size = int(size) size_ = size unit_flag_int = 0 while size_ > 1024: size_ = size_ / 1024 unit_flag_int += 1 ret_size = "{}{}".format(round(size_, 2), unit_dict[unit_flag_int]) return ret_size if data != None and len(data) != 0: num = 0 # 清空 [self.__tableView.delete(id) for id in self.__tableView.get_children()] for line in data: num += 1 self.__tableView.insert("", index=tkinter.END, text=line[0], value=( line[1], line[2], line[3], line[4], size_show(line[5]))) if num > self.__max_showLine: break else: # 清空 [self.__tableView.delete(id) for id in self.__tableView.get_children()] #self.__statusBar.config(text="查询结果为空;type(self.__data)={};len(self.__data)={}".format( # type(self.__data), len(self.__data)), fg="red") self.__statusBar.config(text="查询结果为空", fg="red") # 填充一行空行,使得原本不能被显示完全的最后一行显示完全。同是避免搜索结果为空时,不可刷新 self.__tableView.insert("", index=tkinter.END, text="", value=("", "", "", "", "")) def __tableView_doubleClike(self, event): """数据区域的双击事件,根据双击的位置做出不同的响应""" e = event.widget # 获取发出事件的控件 if len(e.get_children()) == 0: # 若是没有子项就不做处理 return column = e.identify("column", event.x, event.y) # 获取双击所在的列 id_ = e.identify("item", event.x, event.y) ret = True # 是否打开成功 if(column == "#1"): # 双击的路径,打开文件资源管理器,定位到这个路径下 path_str = e.item(id_, "values")[0] ret = self.__open_fileLocation_run(path_str + "\\") else: # 其他地方 path_str = "{}\\{}".format(e.item(id_, "values")[ 0], e.item(id_, "text")) if path_str == '\\': self.__statusBar.config(text="非法路径", fg="red") return ret = self.__open_file(path_str) label_str = self.__statusBar.cget(key="text") # 获取text的值 if ret: self.__statusBar.config( text="{};打开成功!".format(label_str), fg="green") else: self.__statusBar.config( text="{};打开失败!".format(label_str), fg="red") def __open_file(self, filepath): ''' 打开指定的文件,使用系统默认方式; 打开之前会检查文件/目录是否存在。 成功返回True,不成功返回False ''' if os.path.exists(filepath): try: os.startfile(filepath) except Exception as e: errInfo = str(e) self.__statusBar.config(text=errInfo) return False return True else: self.__statusBar.config(text="该文件已经不存在了。", fg="red") return False def __open_fileLocation_run(self, filepath): '''打开文件所在的位置,执行代码,主要是使得目标文件在资源管理器中被选定。''' if os.path.exists(filepath): try: # os.startfile(filepath) subprocess.run('Explorer.exe /select,"{}"'.format(filepath)) #这里的命令,在Windows下,使用cmd来运行更符合系统模式,不要使用PowerShell命令行。cmd里面,空格要使用双引号来引起来,而非单引号。 except OSError as msg: traceback.print_exc() return False return True else: self.__statusBar.config(text="该文件已经不存在了。", fg="red") return False def __open_files(self, event): '''多文件打开''' e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 5: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开数量超过5个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开!", fg="black") return for filepath in filepaths_list: ret = self.__open_file(filepath) label_str = self.__statusBar.cget(key="text") # 获取text的值 if ret: self.__statusBar.config( text="{};打开成功!".format(label_str), fg="green") else: self.__statusBar.config( text="{};打开失败!".format(label_str), fg="red") def __copyFiles_toClip(self, event): '''复制文件到Windows剪贴板''' # 参考:https://chowdera.com/2021/10/20211031055535475l.html # 这其实是一个结构体,用以记录文件的各种信息。 class DROPFILES(ctypes.Structure): _fields_ = [ ("pFiles", ctypes.c_uint), ("x", ctypes.c_long), ("y", ctypes.c_long), ("fNC", ctypes.c_int), # 指示文件是否包含 ANSI 或 Unicode 字符。如果值为零,则文件包含 ANSI 字符。否则,它包含 Unicode 字符。 ("fWide", ctypes.c_bool), ] pDropFiles = DROPFILES() pDropFiles.pFiles = ctypes.sizeof(DROPFILES) pDropFiles.fWide = True #a = bytes(pDropFiles) # 获取文件绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 500: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要复制数量超过500个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消复制!", fg="black") return files = ("\0".join(filepaths_list)).replace("/", "\\") # 结尾一定要两个\0\0字符,这是规定!就是构造NULL字符 data = files.encode("U16")[2:] + b"\0\0" ''' 对于多个文本路径,我们如何将其转换为我们需要的Unicode 双字节形式呢? 首先,我们要知道Unicode编码采用UCS-2格式直接存储,而UTF-16恰好对应于UCS-2的,即UCS-2指定的码位通过大端或小端的方式直接保存。UTF-16 有三种类型:UTF-16,UTF-16BE(大端序),UTF-16LE(小端序).UTF-16 通过以名称BOM(字节顺序标记,U + FEFF)启动文件来指示该文件仍然是小端序。 我们只需要把python String使用UTF-16编码后,去掉前两个字节,得到相应的Unicode双字节。 ''' win32clipboard.OpenClipboard() # 打开剪贴板(独占) try: # 若要将信息放在剪贴板上,首先需要使用 EmptyClipboard 函数清除任何以前的剪贴板内容 win32clipboard.EmptyClipboard() # 清空当前的剪贴板信息,否则不能写入 #uDropEffect = win32clipboard.RegisterClipboardFormat("Preferred DropEffect") win32clipboard.SetClipboardData( win32clipboard.CF_HDROP, bytes(pDropFiles)+data) # 设置当前剪贴板数据 # win32clipboard.SetClipboardData(uDropEffect,b'\x02\x00\x00\x00') #print("error= ",ctypes.windll.kernel32.GetLastError()) self.__statusBar.config(text="复制成功!", fg="green") except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_tb(exc_traceback_obj) self.__statusBar.config(text="复制失败!", fg="red") finally: win32clipboard.CloseClipboard() # 无论什么情况,都关闭剪贴板 def __cutFiles_toClip(self, event): '''剪切文件到剪贴板''' # 这其实是一个结构体,用以记录文件的各种信息。 class DROPFILES(ctypes.Structure): _fields_ = [ ("pFiles", ctypes.c_uint), ("x", ctypes.c_long), ("y", ctypes.c_long), ("fNC", ctypes.c_int), # 指示文件是否包含 ANSI 或 Unicode 字符。如果值为零,则文件包含 ANSI 字符。否则,它包含 Unicode 字符。 ("fWide", ctypes.c_bool), ] pDropFiles = DROPFILES() pDropFiles.pFiles = ctypes.sizeof(DROPFILES) pDropFiles.fWide = True # 获取文件绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 50: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要剪切数量超过50个的文件集合,确定吗?") if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消剪切!", fg="black") return files = ("\0".join(filepaths_list)).replace("/", "\\") # 结尾一定要两个\0\0字符,这是规定!就是构造NULL字符 data = files.encode("U16")[2:] + b"\0\0" ''' 对于多个文本路径,我们如何将其转换为我们需要的Unicode 双字节形式呢? 首先,我们要知道Unicode编码采用UCS-2格式直接存储,而UTF-16恰好对应于UCS-2的,即UCS-2指定的码位通过大端或小端的方式直接保存。UTF-16 有三种类型:UTF-16,UTF-16BE(大端序),UTF-16LE(小端序).UTF-16 通过以名称BOM(字节顺序标记,U + FEFF)启动文件来指示该文件仍然是小端序。 我们只需要把python String使用UTF-16编码后,去掉前两个字节,得到相应的Unicode双字节。 ''' win32clipboard.OpenClipboard() # 打开剪贴板(独占) try: # 若要将信息放在剪贴板上,首先需要使用 EmptyClipboard 函数清除任何以前的剪贴板内容 win32clipboard.EmptyClipboard() # 清空当前的剪贴板信息,否则不能写入 uDropEffect = win32clipboard.RegisterClipboardFormat( "Preferred DropEffect") win32clipboard.SetClipboardData( win32clipboard.CF_HDROP, bytes(pDropFiles)+data) # 设置当前剪贴板数据 win32clipboard.SetClipboardData(uDropEffect, b'\x02\x00\x00\x00') err = ctypes.windll.kernel32.GetLastError() if err == 0: self.__statusBar.config(text="剪切成功!", fg="green") else: self.__statusBar.config( text="剪切失败!errorCode={}".format(err), fg="red") except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_tb(exc_traceback_obj) self.__statusBar.config(text="剪切失败!", fg="red") finally: win32clipboard.CloseClipboard() # 无论什么情况,都关闭剪贴板 def __open_fileLocation(self, event): '''打开文件所在的位置''' # 获取文件的绝对路径 e = event.widget # 获取发出事件的控件 selec_line_id = e.selection() # 返回选中的行ID元组 column = e.identify("column", event.x, event.y) # 获取右击所在的列 filepaths_list = [] [filepaths_list.append("{}\\{}".format(e.item(selec_x_i, "values")[ 0], e.item(selec_x_i, "text"))) for selec_x_i in selec_line_id] if len(filepaths_list) == 1 and filepaths_list[0] == '\\': self.__statusBar.config(text="非法路径", fg="red") return if len(filepaths_list) > 1: choose_ret = tkinter.messagebox.askquestion( title="警告", message="你想要打开{}个文件的所在位置,确定吗?".format(len(filepaths_list))) if choose_ret == 'yes': pass else: self.__statusBar.config(text="取消打开文件所在位置!", fg="black") return for path in filepaths_list: self.__open_fileLocation_run(path) def Hidden_window(self, icon=r'D:\python\python_class_everything\python_everything.ico', hover_text="python everything"): # ico文件一定要是合法的,不能使用改后缀的方式 '''隐藏窗口至托盘区,调用SysTrayIcon的重要函数''' # 托盘图标右键菜单, 格式: ('name', None, callback),下面也是二级菜单的例子 # 24行有自动添加‘退出’,不需要的可删除 menu_options = ( # ('显示窗口', None, lambda:s.SysTrayIcon.destroy(exit = 0)), #我放到后面去了。 # ('二级 菜单', None, (('更改 图标', None, s.switch_icon), ))) #('退出', None, s.exit) ) self.__root.withdraw() # 隐藏tk窗口 if not self.__SysTrayIcon: self.__SysTrayIcon = sysTrayIcon.SysTrayIcon( icon, # 图标 hover_text, # 光标停留显示文字 menu_options, # 右键菜单 on_quit=self.exit_off, # 退出调用 tk_window=self.__root, # Tk窗口 ) self.__SysTrayIcon.activation() def exit_off(self): '''退出''' ''' if self.__threading_Operating_condition.is_alive(): #若子进程还存活,等待子进程结束 self.__threading_Operating_condition.join() ''' self.__scan.kill_scan() #结束扫描进程,如果存在 os.kill(self.__pid, signal.SIGBREAK) # self.__root.destroy() def __searchBar_focus(self, event): '''快捷键Ctrl+f,捕捉到这个事件后,使搜索框得到焦点''' self.__searchBar.focus_set() # 使得搜索框获得焦点,便于快速输入搜索表达式 def __tableView_sortColumn(self,col_int): '''排序;col_int表示第几列(从0开始算)''' self.__search_result.sort(key=lambda x:int(x[col_int]),reverse=self.__sortFlag_col_size) self.__sortFlag_col_size = not self.__sortFlag_col_size self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) if not self.__sortFlag_col_size: self.__statusBar.config(text="从大到小排序",fg="green") else: self.__statusBar.config(text="从小到大排序",fg="green") def __type_filter(self,type_flag): '''筛选指定的类型文件 type_flag取值: all:所有类型 zip:压缩文件 audio:音频文件 video:视频文件 picture:图片文件 document:文档文件 execute:可执行文件 folder:文件夹 lnk:快捷方式 ''' type_dict={'zip':['rar','zip','cab','arj','lzh','ace','7z','tar','gzip','uue','bz2','jar','iso','z'], 'audio':['mp3','wma','wav','mid','ape','flac','weba','webm','cda','aif','aiff','ra','vqf',], 'video':['mpg','mpeg','avi','rmvb','mov','wmv','asf','asx','wvx','mpe','mpa','mp4','rm','navi','mkv','ogg','mod',], 'picture':['bmp','jpeg','png','tiff','gif','pcx','tga','exif','fpx','svg','psd','ico','jpg','wmf','tif','webp',], 'document':['pptx','pptm','ppt','pdf','xps','potx','potm','pot','thmx','ppsx','pps','ppam','ppa','xml','json','log','config','rtf','odp','wps','doc','docx','docm','dotx','dotm','dot','mht','mhtml','htm','html','odt','xlsx','xlsm','xlsb','xls','csv','xltx','xltm','xlt','prn','dif','slk','xlam','xla','ods','java','class','jav','bat','cmd','bib','c','cs','csx','cake','cpp','cc','cxx','c++','hpp','hh','h++','h','ii','clj','cljs','cljc','cljx','clojure','edn','cmake','coffee','cson','iced','css','cu','cuh','dart','i','diff','patch','rej','dockerfile','containerfile','fs','fsi','fsx','fsscript','go','groovy','gvy','gradle','jenkinsfile','nf','handlebars','hbs','hjs','hlsl','hlsli','fx','fxh','vsh','psh','cginc','compute','shtml','xhtml','xht','mdoc','jsp','asp','aspx','jshtm','js','gitgnore_global','hitigonre','npmignore','ini','es6','mjs','cjs','pac','jsx','j2','jinja2','bowerrc','jscsrc','webmainfest','js.map','css.map','ts.map','har','jslintrc','jsonld','jsonc','eslintrc','eslintrc.json','jsfmtrc','swcrc','hintrc','babelrc','code-workspace','language-configuration.json','jl','jmd','ipynb','tex','ltx','ctx','less','lua','mak','mk','md','mkd','mdwn','mdown','markdown','markdn','mdtxt','mdtext','workbook','m','mm','pl','pm','pod','t','psgi','p6','pl6','pm6','nqp','php','php4','php5','phtml','ctp','ps1','psm1','psd1','pssc','psrc','properties','cfg','conf','directory','giattributes','gitconfig','gitmodules','editorconfig','npmrc','pug','jade','py','rpy','pyw','cpy','gyp','gypi','pyi','ipy','pyt','r','rhistory','rprofile','rt','cshtml','rb','rbx','rjs','gemspec','rake','ru','erb','podspec','rbi','rs','scss','code-search','shader','sh','bash','bashrc','bash_aliases','bash_profile','bash_login','ebuild','profile','bash_logout','xprofile','sql','dsql','swift','sty','cls','bbx','cbx','cts','mts','tsx','vb','brs','vbs','bas','vba','xsd','ascx','atom','axml','axaml','bpmn','cpt','csl','csproj','xsl','xslt','yml','eyaml','eyml','yaml','cff','txt',], 'execute':['exe','msi','bat','scr','cmd','bat','com',], 'lnk':['lnk'], } #初始化显示数据 self.__show_data = [] if type_flag == 'all': self.__show_data = self.__search_result elif type_flag == 'folder': for i_list in self.__search_result: if int(i_list[-1]) == 1: self.__show_data.append(i_list) elif type_flag == 'zip': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['zip']: self.__show_data.append(i_list) elif type_flag == 'audio': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['audio']: self.__show_data.append(i_list) elif type_flag == 'video': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['video']: self.__show_data.append(i_list) elif type_flag == 'picture': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['picture']: self.__show_data.append(i_list) elif type_flag == 'document': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['document']: self.__show_data.append(i_list) elif type_flag == 'execute': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['execute']: self.__show_data.append(i_list) elif type_flag == 'lnk': for i_list in self.__search_result: suffix_str = re.findall(r'(?<=\.)[^\.]+$', i_list[0]) if len(suffix_str) > 0: if suffix_str[0] in type_dict['lnk']: self.__show_data.append(i_list) else: self.__statusBar.config(text="参数有误{}".format(type_flag),fg='red') def __filter_console(self,condition,index): '''显示所有文件 condition:字符串 index:下标,int ''' #print("{}".format(condition)) [self.__filterStatus_list[i].set(False) for i in range(9)] if index < len(self.__filterStatus_list): self.__filterStatus_list[index].set(True) else: self.__statusBar.config(text="筛选文件类型时,传入的下标发生越界。",fg="red") return self.__filter_condition = condition self.__type_filter(self.__filter_condition) self.__filling_init(self.__show_data) num = len(self.__show_data) if num > 0: if num > self.__max_showLine: self.__statusBar.config(text="筛选出{}个结果,此处只显示前self.__max_showLine个结果。".format(num),fg="green") else: self.__statusBar.config(text="筛选出{}个结果。".format(num),fg="green") else: self.__statusBar.config(text="没有此类型文件。",fg="red") def show(self): """#显示界面""" self.__root.mainloop()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?