python_everything——UI版


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, 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_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

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 win32api
import win32clipboard  # 文件操作-剪贴板操作相关
from win32com.shell import shell, shellcon

import scan_module
import sysTrayIcon


class UI:
    '''
    绘制界面
    '''

    def __init__(self):
        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("", func=self.__table_cloumnSize)
        self.__root.protocol("WM_DELETE_WINDOW", self.Hidden_window)  # 重定义关闭事件
        # 绑定快捷键Ctrl+f,捕捉到这个事件后,使搜索框得到焦点
        self.__root.bind("", 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_module.scan_module()  # 扫描模块对象
        self.__scan_num = 0  # 扫描次数
        self.__scan_console()  # 启动扫描子进程

        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.__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("", self.__search_console)
        self.__searchBar.pack(fill=tkinter.X)

        # 事件
        self.__searchBar.bind("", 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(1000)]
        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(
            "", self.__tableView_doubleClike)  # 双击事件
        self.__tableView.bind(
            "", self.__tableView_enterEvent)  # 回车键盘事件
        self.__tableView.bind(
            "", self.__tableView_deleteEvent)  # 回车键盘事件
        self.__tableView.bind(
            "", self.__tableView_rightClick)  # 右键事件
        self.__tableView.bind("", self.__tableView_Click)  # 单击事件
        self.__tableView.bind("",self.__copyFiles_toClip)    #复制选中的文件到剪贴板
        self.__tableView.bind("",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
            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)

    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 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行
        """
        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 > 1000:
                    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))
            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'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()
        '''
        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 > 1000:
                self.__statusBar.config(text="筛选出{}个结果,此处只显示前1000个结果。".format(num),fg="green")
            else:
                self.__statusBar.config(text="筛选出{}个结果。".format(num),fg="green")
        else:
            self.__statusBar.config(text="没有此类型文件。",fg="red")
    def __filter_zip(self):
        '''只显示压缩文件'''

    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()

效果示例:

双击文件名称打开文件,双击路径打开所在文件夹