Python Tkinter 可折叠树状浏览器,及其在类和函数、文件浏览器的应用(由idlelib tree模块修改)

本文主要是介绍Python Tkinter 可折叠树状浏览器,及其在类和函数、文件浏览器的应用(由idlelib tree模块修改),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

模块由idlelib tree模块修改,完善一些问题,重写了获取类和函数的方法,便于获取正在编辑代码的类和函数。重写了文件浏览模块,支持添加收藏,双击py(pyw)文件会打开函数浏览器,文件浏览器支持很多文件的图标。代码基本都有注释,方便新手学习,注释不一定完全正确。
完整代码和需要的图标已经上传,类和函数更新了新的获取方式,以文章为准

模块效果图

在这里插入图片描述

效果图

导入模块,设置好需要的参数

import stat, os, sys, re, tkinter as tk
from tkinter import ttkicon_path = os.path.join(os.path.dirname(__file__), "Icons") # 图标文件夹 os.path.join(os.path.dirname(__file__), "Icons")
module_path = __file__ # 默认函数浏览文件路径
bg_color = '#ffffff' # 常规项背景颜色
st_color = '#d9d9d9' # 选中项背景颜色

创建带滚动条的画布

class ScrolledCanvas:"带有滚动条和快捷键绑定的画布小部件"def __init__(s, master, frame, **opts):if 'yscrollincrement' not in opts:opts['yscrollincrement'] = 17s.master = masters.frame = frames.frame.rowconfigure(0, weight=1)     # 行自动适应窗口大小s.frame.columnconfigure(0, weight=1)  # 列自动适应窗口大小s.canvas = tk.Canvas(s.frame, **opts) # Canvas绘图窗口s.canvas.grid(row=0, column=0, sticky="nsew")# 右侧滚动条s.vbar = tk.Scrollbar(s.frame, name="vbar")s.vbar.grid(row=0, column=1, sticky="nse")s.canvas['yscrollcommand'] = s.vbar.sets.vbar['command'] = s.canvas.yview# 下方滚动条"horizontal" 水平显示s.hbar = tk.Scrollbar(s.frame, name="hbar", orient="horizontal")s.hbar.grid(row=1, column=0, sticky="ews")s.canvas['xscrollcommand'] = s.hbar.sets.hbar['command'] = s.canvas.xviews.canvas.bind("<MouseWheel>", lambda event: s.unit_up(event) if event.delta > 0 else s.unit_down(event))s.canvas.bind("<Key-Prior>", s.page_up)   # PageUp键s.canvas.bind("<Key-Next>" , s.page_down) # PageDown键s.canvas.bind("<Key-Up>"   , s.unit_up)     # 上键s.canvas.bind("<Key-Down>" , s.unit_down)   # 下键s.canvas.bind("<Alt-Key-2>", s.zoom_height) # Alt+2s.canvas.focus_set()def page_up(s, event):# s.vbar.get()[0]为'0.0'时禁止向上if s.vbar.get()[0]:s.canvas.yview_scroll(-1, "page")return "break"def page_down(s, event):s.canvas.yview_scroll(1, "page")return "break"def unit_up(s, event):# s.vbar.get()[0]为'0.0'时禁止向上if s.vbar.get()[0]: s.canvas.yview_scroll(-1, "unit")return "break"def unit_down(s, event):s.canvas.yview_scroll(1, "unit")return "break"def zoom_height(s, event): # 窗口上下满屏import regeom = s.master.wm_geometry()m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)if not m:s.master.bell()returnwidth, height, x, y = map(int, m.groups())newheight = s.master.winfo_screenheight()if sys.platform == 'win32':newy = 0newheight = newheight - 72else:newy = 0newheight = newheight - 88if height >= newheight:newgeom = ""else:newgeom = "%dx%d+%d+%d" % (width, newheight, x, newy)s.master.wm_geometry(newgeom)return "break"

创建节点结构获取的父类-TreeItem

class TreeItem:"""表示树项的父类。方法通常应被重写,否则将使用默认操作。"""expandable = Nonedef __init__(s):"做任何你需要做的事。"def _IsExpandable(s):"不要覆盖!由TreeNode调用。"if s.expandable is None:s.expandable = s.IsExpandable()return s.expandabledef IsExpandable(s):"返回是否有子项。"return 1def _GetSubList(s):"不要覆盖!由TreeNode调用。"if not s.IsExpandable():return []sublist = s.GetSubList()if not sublist:s.expandable = 0return sublistdef GetText(s):"返回要显示在标签前的字符串(如果有)。"def GetLabelText(s):"返回要显示的标签文本字符串。"def GetSubList(s):"返回组成子列表的项目列表。"def IsEditable(s):"返回是否可以编辑项目的文本。"def SetLabelText(s, text):"更改项目的文本(如果可编辑)。"def GetIconName(s):"返回要正常显示的图标的名称。"def GetSelectedIconName(s):"返回选定时要显示的图标的名称。"def OnDoubleClick(s):"在双击该项时调用。"

函数浏览页面节点获取的TreeItem子类-ModuleBrowserTreeItem

class ModuleBrowserTreeItem(TreeItem):"""模块中子节点的浏览器树。使用TreeItem作为树结构的基础。"""def __init__(s, name, tree):# name 要显示的名称# tree 该函数/类的信息(位置,图标标记,子集字典)s.position = tree[0]s.isfunction = tree[1]s.obj = tree[2]s.name = namedef GetText(s):"返回要显示在函数名前的文字。"#if s.isfunction in ['def','class']:#    return s.isfunctiondef GetLabelText(s):"返回要显示的函数/类的名称。"return s.namedef GetIconName(s):"返回要显示的图标的名称。"if s.isfunction == 'def':return "form1"elif s.isfunction == 'class':return "form2"elif s.isfunction in ['.py','.pyw']:return "Function2"else:return "blank"def IsExpandable(s):"判断s.obj是否有子集。"return len(s.obj)def GetSubList(s):"返回子级的ModuleBrowserTreeItem。"return [ModuleBrowserTreeItem(key, s.obj[key]) for key in s.obj.keys()]def OnDoubleClick(s):"双击返回类或函数所在的位置。"print (s.position)return s.position

文件浏览页面节点获取的TreeItem子类-FileBrowserTreeItem

class FileBrowserTreeItem(TreeItem):"""模块中子节点的浏览器树。使用TreeItem作为树结构的基础。"""def __init__(s, tree):# tree 文件夹信息,格式:(名称, 属性, 完整路径, [子集...., (名称,属性,完整路径,[子集...]) ])# s.root = treebrowser.root # 可以做弹窗提示,比如改名的时候s.name = tree[0]s.attr = tree[1]s.dir  = tree[2]s.obj  = tree[3]def GetLabelText(s):"返回要显示的名称。"return s.namedef SetLabelText(s, text):"更改项目的文本(如果可编辑)。"dir = s.dir[:s.dir.rfind(s.name)] # 上级文件夹if os.path.exists(dir):try:os.renames(dir + s.name, dir + text)s.name = texts.dir  = dir + textexcept:passdef GetIconName(s):"返回要显示的图标的名称。"if s.attr == 'dir': return 'dir'elif s.attr == 'root' : return 'pc' # 根"计算机"elif s.attr == 'piece' : return 'piece' # 盘符elif s.attr == 'collect': return 'collect' # 收藏文件夹图标elif s.attr in ['.txt']: return 'txt'elif s.attr in ['.dll', '.bin', '.cab']: return "dll"elif s.attr in ['.apk']: return "apk"elif s.attr in ['.reg']: return "tree"elif s.attr in ['.lnk']: return "lnk"elif s.attr in ['.chm']: return "help"elif s.attr in ['.gba','.nes','.chd','.swf']: return "game"elif s.attr in ['.py', '.pyw']: return "py"elif s.attr in ['.ini', '.db', '.bat', '.dat', '.sav', '.tag']: return "db"elif s.attr in ['.zip', '.rar', '.7z', '.gz']: return "zip"elif s.attr in ['.exe', '.iso', '.msi']: return "exe"elif s.attr in ['.mp3', '.flac', '.ape', '.wav']: return "mp3"elif s.attr in ['.ttf', '.ttc', '.fon']: return "font"elif s.attr in ['.pdf']: return "pdf"elif s.attr in ['.xml','.html','.htm']: return "html"elif s.attr in ['.doc', '.docx', '.rtf']: return "docx"elif s.attr in ['.xls', '.xlsx', '.cav']: return "xlsx"elif s.attr in ['.jpg', '.png', '.gif', '.bmp', '.ico', '.raw']: return "jpg"elif s.attr in ['.scr', '.avi', '.rmvb', '.mp4', '.flv']: return "video"else: return "blank"def IsEditable(s):"判断是否可以编辑节点"Protect = ['C:\\Windows\\','C:\\ProgramData\\','C:\\Program Files\\','C:\\Documents and Settings\\']for dir in Protect:if s.dir.count(dir): return False # 上面列出的文件夹及其子文件禁止编辑if s.attr == 'dir':if len(s.dir) == 3: return False # 禁止编辑盘符else: return Trueelif s.attr not in ['root','piece','collect']: # 判断文件是否可编辑return Trueelse:return Falsedef IsExpandable(s, skip_dir=True): "判断是否有子集。"if len(s.obj): return Trueelif s.attr == 'root': return Trueelif s.attr in ['dir','piece','collect']:if skip_dir: # skip_dir是否跳过文件夹的判断return True # 直接返回True可以大幅提升子文件夹多的文件夹打开速度,空文件夹点击后会再识别为空else:try: len(os.listdir(s.dir))   #  可能无权限查看except: return Falseelse:return Falsedef GetSubList(s):"返回子级的FileBrowserTreeItem对象列表,文件和文件夹区分开,直接排序是按拼音排序会比较混乱"dirlist  = [] # 文件夹列表filelist = [] # 文件列表collect  = [] # 收藏文件夹,直接显示在一级目录if not s.obj: # 尝试获取文件夹子集,无权限则跳过,放在这里获取而不是后面添加到文件夹子集里是为了减少不必要的运算,增加上级文件夹的打开效率try : s.obj = os.listdir(s.dir) except: passfor child in s.obj:if isinstance(child, tuple): # 判断child是不是元组,是的话为磁盘或收藏文件夹collect.append(FileBrowserTreeItem(child))continuepath = s.dir + childif not os.path.exists(path): continue # 文件不存在则跳过,主要是规避收藏夹子集传入不存在文件(夹)if (len(path) > 3) and s.FileStat(path): # 判断文件属性,不判断盘符,跳过隐藏文件和受保护文件continueif os.path.isdir(path): # 先处理文件夹path += '\\' # 文件夹尾加上'\\'dirlist.append(FileBrowserTreeItem((child, 'dir', path, []))) else: # 处理文件filelist.append(FileBrowserTreeItem((child, os.path.splitext(child)[1].lower(), path, []))) # 先把文件信息存储到列表中,放文件夹列表后return dirlist + filelist + collect # 文件夹排在文件前面,最后是收藏夹def OnDoubleClick(s):"双击返回完整路径。"if s.attr in ['.py','.pyw']: # 如果文件是py文件就打开模块浏览器treebrowser.new_Module_node(s.dir)print (s.dir)return s.dirdef FileStat(s, file_path):"判断文件是否是隐藏文件及受保护文件"file_stat = os.stat(file_path) # 函数获取文件的状态信息# st_file_attributes获取属性值if file_stat.st_file_attributes & 2: # 隐藏文件的属性值为2,因此我们可以通过与运算('&')来判断文件的属性值中是否包含2来判断文件是否是隐藏文件return Trueif stat.S_ISREG(file_stat.st_mode):  # S_ISREG判断文件是否是普通文件,判断文件是否是受保护文件mode = stat.S_IMODE(file_stat.st_mode) # 获取文件的权限模式if (mode & stat.S_IRUSR) and (mode & stat.S_IWUSR) and (mode & stat.S_IXUSR):   # 判断文件的用户权限return Trueelif (mode & stat.S_IRGRP) and (mode & stat.S_IWGRP) and (mode & stat.S_IXGRP): # 判断文件的组权限return Trueelif (mode & stat.S_IROTH) and (mode & stat.S_IWOTH) and (mode & stat.S_IXOTH): # 判断文件的其他用户权限return Truereturn False

获取代码类和函数结构的函数cscope

def cscope(file = None, retarn_args=False, line_num=True, skip_comment=True, text=''):'''file         需要获取类和函数结构的文件路径retarn_args  函数名是否包含argsline_num     True返回行数,False返回位置skip_comment 是否跳过三引号注释,要考虑多种情况,会增加运算;最好的办法是关闭,然后规范代码,将注释内缩进设为一致text         需要获取类和函数的字符串'''if file:dir, base = os.path.split(file)name, ext = os.path.splitext(base)if os.path.normcase(ext) not in [".py",".pyw"]:return {base:(0,ext,{})}text = open(file, 'r', encoding='utf-8').read()else:base = '类和函数'ext  = '.py'flines = []idx = lpos = 0lst = text.splitlines() #获取文件行列表if  not (lst) : lst.append(u'')for index, line in enumerate(lst):lnum = index+1    # 行数ln = line.strip() # 去除空格后的字符串if not (ln) : # 跳过空行lpos += len(line) + 1continue ind = line.find(ln[0]) # 缩进flines.append((idx, lnum, ind, lpos, ln)) # (序号,行数,字符串缩进,行首的位置,去除空格后的字符串)idx += 1 # 序号lpos += len(line) + 1 # 下一行行首的位置last = root = {}end = {u'class' : u'', u'def' : u'()'}lev = [(0, root)] # 用来临时存储同一缩进的函数comment = [False, (), # 用来存储注释信息[("'''", '"', r'\"(.*?)\"'), ('"""', "'", r"\'(.*?)\'")]] # 这里要换行,否则12两种三引号跳过方式无法正常判断for idx, lnum, ind, lpos, ln in flines: # 序号,行数,字符串缩进,行的位置,去除空格后的字符串if skip_comment: # 判断是否处理三引号注释if comment[0]: # 本行在注释范围内flines[idx] = (idx, lnum, flines[(idx - 1)][2], lpos, ln) # 将本行缩进改为上一行的缩进if ln.count(comment[1][0]): # 结束跳过comment[0] = False # 本行为注释行最后一行,结束跳过注释continueelse:for mark in comment[2]:Mnum = ln.count(mark[0])if Mnum: # 存在三引号sign = 3 # 使用哪种跳过方式,这里用第3种相对完美if sign == 1: # 正则表达式处理-不完美pattern = mark[2]  # 指定引号内的内容获取-正则表达式,无法判断复杂情况,比如需要判断的引号分别在不同字符串里面matches = re.findall(pattern, ln) # 获取在指定引号内的字符串for string in matches:Mnum -= string.count(mark[0]) # 减去在引号内的三引号数量if divmod(Mnum ,2)[1]: # 如果还存在引号,且为奇数个comment[:2] = [True, mark]elif sign == 2: # 引号数量判断方法-不完美Lnum = ln.find(mark[0])  # 最左侧三引号位置Rnum = ln.rfind(mark[0]) # 最右侧三引号位置Ynum = divmod(ln[:Lnum].count(mark[1]) ,2)[1] # 判断三引号前有几个不同引号,如果为奇数则三引号在字符串内Jnum = ln[:Rnum].rfind('#') # 三引号前'#'的位置if Jnum < 0 or (Jnum >= 0 and (ln[Jnum:Rnum].count('"') or ln[Jnum:Rnum].count("'"))): # 不存在'#' 或 三引号和'#'之间存在引号。只是三引号出现的其中一种情况,都处理会增加计算if Lnum == Rnum and not Ynum: # 只存在一个三引号,且不是存在字符串内comment[:2] = [True, mark]                         elif Lnum < Rnum and Ynum and divmod(ln[Lnum:Rnum].count(mark[1]) ,2)[1]: # 存在多个三引号,且最左侧的在字符串里面comment[:2] = [True, mark]elif sign == 3: # 转换成列表切片判断,相对完美text = lnindex = idx - 1while flines[index][4][-1] == '\\': # 上一行结尾是'\'text = flines[index][4] + '>>>' + text # 合并上一行一起判断, 加'>>>'是为了后面协助判断跳过'#'注释index -= 1strlist = list(text) # 转为列表marks = [False, False, '', 0, 0] # [是否在引号内, 是否在三引号内, 什么引号, 引号位置, 连续相同引号的数量]index = 0while True: # 这里用 whlie 不是 for 是为了方便修改 strlistif index == len(strlist): breakstring = strlist[index]if string in ["'",'"']: # 如果存在引号if marks[0]: # 在引号内if index-marks[3] == 1 and string == marks[2]: # 前一个是相同引号的marks[3] = index  # 更新引号位置marks[4] += 1     # 连续引号数量加1if marks[4] == 3: # 连续3个引号if marks[1]: marks = [False, False, '', 0, 0]  # 原来在3引号内,重置markselse: marks[1] = True # 不在3引号内,将后续的设为在3引号内elif marks[4] == 6: marks = [False, False, '', 0, 0] # 连续6个引号,重置markselif index-marks[3] > 1 and string == marks[2]: # 两相同引号中间有间隔marks[3] = index # 更新引号位置if marks[1]: marks[4] = 1 # 三引号内部else: marks[0] = Falseelse: marks = [True, marks[1], string, index, 1] # 在引号外,新记录引号信息elif marks[0]: # 引号内部,且不是引号if marks[4] == 2: marks = [False, False, '', 0, 0] # 连续两个引号后不是引号的, 说明是空字符串, 重置markselse: marks[4] = 0 # 重置连续引号if string == '#' and not marks[0]: # 跳过#注释text = text[index:]if text.count('\\>>>'): # '#'号注释结尾有'\',且后面右三引号注释的情况text = text[text.find('\\>>>')+4:] # 重置text从下一行开始strlist = list(text)index = 0else:breakindex += 1if marks[1]: # 开始跳过comment[:2] = [True, (marks[2]*3, '', '')] # 记录三引号信息,跳过后面注释break # 跳过本行下一个三引号的判断# 根据缩进级别更新lev列表,用于存储同一缩进级别的函数if ind < lev[-1][0]: # 缩进 < lev最后标记的缩进if idx > 0 :     # 序号 > 0rastrow = flines[(idx - 1)] # 上一行列表if rastrow[-1][-1] in (u',', u'\\') : # 上一行是以','或'\'结尾,跳过元组、列表、字典的换行缩进,以及长字符串中间的'\'换行flines[idx] = (idx, lnum, rastrow[2], lpos, ln) # 将本行缩进改为上一行的缩进,方便下一行比对continueif ln[0] == '#': # '#'注释flines[idx] = (idx, lnum, rastrow[2], lpos, ln) # 将本行缩进改为上一行的缩进,方便下一行比对continuetry :while ind < lev[-1][0] : # 缩进 < 上一个缩进记录lev.pop()   # 弹出lev最后一项,直至当前缩进和存储的最后一个缩进同级except IndexError : return Noneelif ind > lev[-1][0] : # 缩进 > lev最后标记的缩进lev.append((ind, last)) # lev添加(缩进,字典)t = ln.split() # 以空格分割成列表if t[0] in end.keys() : # 字符串列表第一位是'class'或'def'try:if retarn_args: # 保留argst = ln.split(' ', 1)tok = t[1].split(u':')[0] # 剪切'('及':'之前的部分name = tokelse:tok  = t[1].split(u'(')[0].split(u':')[0] # 剪切'('及':'之前的部分name = tok + end[t[0]]  # 加上end设置好的结尾if line_num: num = lnum # line_num 返回行数else: num = lpos + ind  # 返回位置if name in lev[-1][1] : # name和lev最后一项的字典里的函数重名name = (u'%s%s*%d' % (tok, end[t[0]], num)) # name = tok:+字符串位置+end设置好的结尾last = {} # 重置lastlev[-1][1][name] = (num, t[0], last) # lev最后一个元组标记的字典加入{name:(字符串位置,{})}except:passroot = {base:(0, ext, root)} # 以文件名做树状图的根return root

鼠标滚动事件函数

def wheel_event(event, widget=None):"""处理滚轮事件。在Windows上,滚轮向上滚动时,event.delta = 120*n。参数widget是必需的,以便浏览器标签绑定可以将底层画布传递过去。此函数依赖于widget.yview不会被子类覆盖。"""# 判断滚轮是否向上滚动up = {tk.EventType.MouseWheel: event.delta > 0,tk.EventType.ButtonPress: event.num == 4}# 如果没有传入widget参数,则使用event中的widget属性widget = event.widget if widget is None else widgetlines = 5if up[event.type]: # 如果滚轮向上滚动lines = -5 if widget.canvasy(0) else 0 # lines = -5,如果可见区域的顶部纵坐标为0则lines = 0 防止过度向下widget.yview(tk.SCROLL, lines, 'units') # 调用widget的yview方法进行滚动# 返回'break'以阻止事件继续传播return 'break'

绘制树状图的主要类-TreeNode

class TreeNode:# 初始化方法,传入画布、父节点、项对象def __init__(s, canvas, parent, item):s.canvas = canvass.parent = parent # 接收父节点TreeNode对象,只在TreeNode内部传递,外部传入Nones.item = item     # TreeItem子类对象s.state = 'collapsed' # 选中标记为未选中s.selected = False    # 判断是否选中s.children = []       # 存储子节点TreeNode对象s.x = s.y = Nones.iconimages = {} # 图标的PhotoImage实例缓存def destroy(s): "退出"for c in s.children[:]: s.children.remove(c) # 删除所有子节点c.destroy()s.parent = None # 将父节点设置为Nonedef geticonimage(s, name):"根据名称获取图标文件,生成tkImage对象返回"try: return s.iconimages[name] # 如果存在同名图标,返回已经生成的tkImage对象except KeyError: passfile, ext = os.path.splitext(name) # 获取文件名和后缀ext = ext or ".gif" # 没有后缀的以".gif"为后缀fullname = os.path.join(icon_path, file + ext) # 连接文件路径image = tk.PhotoImage(master=s.canvas, file=fullname) # 生成tkImage对象s.iconimages[name] = image # 将tkImage对象缓存在s.iconimagesreturn imagedef select(s, event=None):"选中节点的方法,单击图标也可调动"if s.selected:returns.deselectall()   # 全不选s.selected = True # 指示是否有选中项s.canvas.delete(s.image_id) # 删除旧的图标对象s.drawicon() # 绘制新的图标s.drawtext() # 绘入文本内容def deselect(s, event=None):"取消选择节点的方法"if not s.selected:returns.selected = Falses.canvas.delete(s.image_id) # 删除图标对象s.drawicon() s.drawtext()def deselectall(s):"全不选所有节点的方法,并调用 s.parent.deselectall 方法递归操作父节点"if s.parent: s.parent.deselectall() # 操作父节点递归操作全不选else:s.deselecttree() def deselecttree(s):"全不选所有节点的方法, 并调用 s.children-deselecttree() 方法递归操作子节点"if s.selected:s.deselect()for child in s.children:child.deselecttree() # 操作子节点递归操作全不选def flip(s, event=None): "双击节点"if s.state == 'expanded': # 选中节点为展开状态s.collapse() # 收起节点else:s.expand()   # 张开节点s.item.OnDoubleClick() # 执行s.item的双击函数return "break"def expand(s, event=None):"判断当前节点是否可展开"if not s.item._IsExpandable(): # 判断有无子项returnif s.state != 'expanded': # 选中节点未展开s.state = 'expanded'  # 选中节点设置为展开s.update() s.view() # 更新视野def collapse(s, event=None):"收起选中的节点"if s.state != 'collapsed': # 选中节点未关闭s.state = 'collapsed'  # 选中节点设置为关闭s.update()def view(s):"更新视野内容"top = s.y - 2bottom = s.lastvisiblechild().y + 17height = bottom - topvisible_top = s.canvas.canvasy(0)        # 获取可见区域的顶部纵坐标visible_height = s.canvas.winfo_height() # 获取画布的可见高度visible_bottom = s.canvas.canvasy(visible_height)   # 获取画布可见区域底部的y坐标if visible_top <= top and bottom <= visible_bottom: # 如果画布的顶部在可见区域内,则直接返回returnx0, y0, x1, y1 = s.canvas._getints(s.canvas['scrollregion']) # 获取滚动区域的坐标信息if top >= visible_top and height <= visible_height: # 如果当前可见区域在画布顶部和底部之间, 则计算滚动比例fraction = top + height - visible_heightelse: # 否则,只滚动到可见区域的顶部fraction = topfraction = float(fraction) / y1 # 将滚动比例转换为浮点数,并除以y1得到最终的滚动比例s.canvas.yview_moveto(fraction) # 将画布滚动到指定的比例位置def lastvisiblechild(s):"返回节点的最后一个可见子节点。"if s.children and s.state == 'expanded':     # 如果当前节点有子节点且当前节点状态为展开return s.children[-1].lastvisiblechild() # 返回最后一个可见且未展开子节点的对象else:return s # 返回当前节点对象def update(s): "刷新画布"if s.parent: # 存在父节点s.parent.update() # 刷新父节点else:oldcursor = s.canvas['cursor'] # 保存当前光标样式s.canvas['cursor'] = "watch"   # 转圈光标s.canvas.update() #更新画布s.canvas.delete(tk.ALL) # 删除画布上的所有对象s.draw(7, 5) # 在画布上绘制新的图形,左顶点坐标(7, 5)x0, y0, x1, y1 = s.canvas.bbox(tk.ALL) # 获取包含内容的画布边界框的位置和大小s.canvas.configure(scrollregion=(0, 0, x1, y1)) # 设置画布的滚动区域s.canvas['cursor'] = oldcursor # 恢复光标样式def draw(s, x, y):# XXX 这个硬编码的几何常数太多了!dy = 20 # 设置默认的间距s.x, s.y = x, y # 更新对象的位置s.drawicon()    # 绘制图标s.drawtext()    # 绘制文本if s.state != 'expanded': # 如果状态不是展开,则返回当前y值加上间距return y + dy# 画子节点if not s.children: # 如果子节点对象列表为空sublist = s.item._GetSubList() # 获取组成子元素的项目列表if not sublist:# _IsExpandable() 方法错误地允许了这种情况return y+17for item in sublist:# s代表当前实例对象,通过s.XXXX()可以访问当前实例对象的方法或属性# s.__class__代表当前实例对象所属的类, s.__class__.XXXX()可以访问类的方法或属性。 这种方式可以用于在实例方法中调用类方法, 或者在实例方法中访问类的属性child = s.__class__(s.canvas, s, item) # 为每个子元素创建一个新的对象s.children.append(child)# 计算子节点的起始位置和上一个子节点的结束位置cx = x+20cy = y + dycylast = 0# 遍历子节点对象,绘制连接线并递归调用draw方法for child in s.children:cylast = cys.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50") # 树状图的横线,"gray50" 灰色值50,"gray100"则为白色,值越小颜色越深cy = child.draw(cx, cy)if child.item._IsExpandable(): # 判断是否有子项if child.state == 'expanded':iconname = "minusnode" # 展开后显示的减号图标callback = child.collapseelse:iconname = "plusnode"  # 收起后显示的加号图标callback = child.expandimage = s.geticonimage(iconname) # 获取图标tkImage对象id = s.canvas.create_image(x+9, cylast+7, image=image) # 绘制图标# 在画布上绑定单击和双击事件,直到画布被删除:s.canvas.tag_bind(id, "<1>", callback)s.canvas.tag_bind(id, "<Double-1>", lambda x: None)id = s.canvas.create_line(x+9, y+10, x+9, cylast+7, fill="gray50") # 绘制竖向连接线并调整其位置s.canvas.tag_lower(id) # 将连接线置于其他元素之下return cy # 返回最后一个子节点的结束位置作为最终结果def drawicon(s):"绘制图标"if s.selected: # 如果选中imagename = (s.item.GetSelectedIconName() or # 选中时图标s.item.GetIconName() or         # 设定好的图标"openfolder")else:imagename = s.item.GetIconName() or "folder" # 设定好的图标image = s.geticonimage(imagename) # 获取图标的tk对象id = s.canvas.create_image(s.x, s.y, anchor="nw", image=image) # 将图标绘入画布s.image_id = id # 将图像ID存储在对象的属性中s.canvas.tag_bind(id, "<1>", s.select)      # 鼠标左键单击s.canvas.tag_bind(id, "<Double-1>", s.flip) # 鼠标左键双击def drawtext(s):"绘入文字"# 计算文本的x和y坐标textx = s.x+20-1texty = s.y-4text = s.item.GetText() # 获取显示在标签前的文字,可以用来注释if text: id = s.canvas.create_text(textx, texty, # 如果上下有偏移这里+-调整anchor="nw",text=text)s.canvas.tag_bind(id, "<1>", s.select)s.canvas.tag_bind(id, "<Double-1>", s.flip)x0, y0, x1, y1 = s.canvas.bbox(id)textx = max(x1, 10) + 10 # 标签和文字间的间隙宽度labeltext = s.item.GetLabelText()  or "<no text>" # 获取标签文字# 如果存在entry属性,调用edit_finish方法try:s.entryexcept AttributeError:passelse:s.edit_finish() # 保存编辑后的内容# 如果不存在label属性,则创建新的Labeltry:s.labelexcept AttributeError: # label显示文字内容, label主要是为了放置编辑框Entry, 不需要编辑框的可以使用create_text替代, 减少画布上的窗口部件s.label = tk.Label(s.canvas, text=labeltext, bg=bg_color, # 背景颜色,这里设置为和画布一样bd=0, padx=2, pady=2)if s.selected:s.label['bg'] = st_color # 更改背景颜色为选中背景颜色else:s.label['bg'] = bg_color # 更改背景颜色为默认背景颜色# 在画布上创建一个窗口来显示标签,并绑定事件id = s.canvas.create_window(textx, texty,anchor="nw", window=s.label)s.label.bind("<1>", s.select_or_edit)s.label.bind("<Double-1>", s.flip)s.label.bind("<MouseWheel>", lambda e: wheel_event(e, s.canvas))s.text_id = iddef select_or_edit(s, event=None):"单击label"if s.selected and s.item.IsEditable(): # 为已选中节点且是可编辑节点s.edit(event)   # 生成输入框else:s.select(event) # 选中节点def edit(s, event=None):"生成输入框"s.entry = tk.Entry(s.label, bd=0, highlightthickness=1, width=0) # Entry输入框s.entry.insert(0, s.label['text']) # 输入框内插入项目文本s.entry.selection_range(0, tk.END) # 选中所有文本s.entry.pack(ipadx=5)s.entry.focus_set() # 设置焦点,focus_get() 获取焦点部件名称s.entry.bind("<Return>", s.edit_finish) # 回车键保存s.entry.bind("<Escape>", s.edit_cancel) # Esc取消,关闭Entrydef edit_finish(s, event=None):"保存编辑后的内容"try:entry = s.entrydel s.entryexcept AttributeError:returntext = entry.get() # 获取编辑框内文本entry.destroy()if text and text != s.item.GetLabelText():s.item.SetLabelText(text) # 更改节点的文本text = s.item.GetLabelText()  # 重新获取节点文本s.label['text'] = texts.drawtext() # 重绘选项s.canvas.focus_set() # 设置焦点def edit_cancel(s, event=None):"取消保存"try:entry = s.entrydel s.entryexcept AttributeError:returnentry.destroy()s.drawtext() # 重绘选项s.canvas.focus_set() # 设置焦点

Tk窗口和操作类-TreeBrowser

class TreeBrowser:"""创建一个树状结构的窗口。"""def __init__(s):s.node_list = [] # 存储node窗口def close(s, event=None):"关闭窗口和树节点。"s.root.destroy()for node in s.node_list: node.destroy()os._exit(0)def main(s):"创建浏览器tkinter部件,包括树。"s.root = tk.Tk()s.root.title("树状浏览器")s.root.protocol("WM_DELETE_WINDOW", s.close)s.root.wm_iconname("Module Browser")s.root.focus_set()#s.tab = CustomNotebook(s.root)s.tab = ttk.Notebook(s.root)          # 创建Notebook选项卡控件s.tab.pack(expand = 1, fill = "both") # 让Notebook控件显示出来s.tab.enable_traversal()              # 为s.tab启用键盘快捷方式,Control-Tab向后切换,Shift-Control-Tab向前切换# collect收藏文件夹,格式[(标题,图标标记,完整路径,子集列表),....],子集列表为空的话浏览下面所有文件(夹),也可指定显示的子集文件夹collect = [('收藏1','collect','C:\\',['Program Files','Users']),('收藏2','collect','C:\\',[])] s.new_Module_node(path=module_path) # 函数浏览器-浏览文件s.new_file_node(collect) # 文件浏览器lah = '''
def 函数():def a1():def a11():''def a12():''def a13():''def a2():''
class 类:def b1(s):''def b2(s):'' '''s.new_Module_node(text=lah) # 函数浏览器-浏览字符串s.root.mainloop()def new_node(s, text, item):"新建tab,并绘入node界面"new_tab = tk.Frame(s.tab) # 添加tab选项卡s.tab.add(new_tab, text = text)new_Frame = tk.Frame(new_tab)new_Frame.pack(expand=1, fill="both")# 创建带滚动画布,canvas画布尽量和mainloop在同一函数下否则可能快捷键会失效new_Canvas = ScrolledCanvas(s.root, new_Frame, bg=bg_color, highlightthickness=0, takefocus=1).canvasnew_node = TreeNode(new_Canvas, None, item) # 将画布及内容传入TreeNode,绘制树状图new_node.update() # 刷新nodenew_node.expand() # 显示内容s.node_list.append(new_node)def new_Module_node(s, path=None,text=''):"新建模块浏览器窗口"# tree获取需要显示的类和函数tree = cscope(file=path, retarn_args=False, line_num=True, skip_comment=True, text=text) #函数名不包含args,返回行数,跳过三引号注释key  = next(iter(tree.keys())) # 返回第一个key文件名,作为树状图的根item = ModuleBrowserTreeItem(key, tree[key])s.new_node('函数浏览器', item)def new_file_node(s, collect=[]):"新建文件浏览器窗口"# tree 需要显示树根"计算机"以及一级结构"硬盘分区"和"收藏文件夹";chr()将整数转为字符串,再通过os.path.isdir判断A-Z是否有硬盘分区tree = ("计算机", "root", "",[( chr(i)+':', 'piece', chr(i)+':\\', []) for i in range(65,91) if os.path.isdir(chr(i)+':\\')] + collect)item = FileBrowserTreeItem(tree)s.new_node('文件浏览器', item)

最后试下效果

treebrowser = TreeBrowser()
treebrowser.main()

这篇关于Python Tkinter 可折叠树状浏览器,及其在类和函数、文件浏览器的应用(由idlelib tree模块修改)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/284848

相关文章

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

python 字典d[k]中key不存在的解决方案

《python字典d[k]中key不存在的解决方案》本文主要介绍了在Python中处理字典键不存在时获取默认值的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录defaultdict:处理找不到的键的一个选择特殊方法__missing__有时候为了方便起见,

使用Python绘制可爱的招财猫

《使用Python绘制可爱的招财猫》招财猫,也被称为“幸运猫”,是一种象征财富和好运的吉祥物,经常出现在亚洲文化的商店、餐厅和家庭中,今天,我将带你用Python和matplotlib库从零开始绘制一... 目录1. 为什么选择用 python 绘制?2. 绘图的基本概念3. 实现代码解析3.1 设置绘图画

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小