Tkinter主题控件的Treeview除了体统树状视图之外,还可以提供列表视图。但是由于控件本身的复杂性,如果想正确的使用这个控件,或多或少都需要一些时间来调查和学习。本文提供一个通用的ListView类以简化这个过程。
这个类除了提供一般的列表功能之外,还提供一个简单的就地编辑功能:当用户双击文件名时,可以触发一个文本输入框直接编辑文件名。
class ListView(Treeview): def __init__(self, master, **kwargs): columns = kwargs.get('columns') kwargs.pop('columns') Treeview.__init__(self, master) if columns: cols = [] for c in range(1, columns): cols.append('#{}'.format(c)) self['columns'] = cols self.editable = {} self.bind('<Double-1>', self.item_double_clicked) # 双击左键进入编辑 # 设定允许编辑的列 def enable_edit(self, col, editable): self.editable[col] = editable # 双击处理 def item_double_clicked(self, event): column = self.identify_column(event.x) # 列 row = self.identify_row(event.y) # 行 editable = self.editable.get(column) if editable: ItemEdit(self, row, column) # 编辑结果确认处理 def entry_confirmed(self, row, col, text): self.set_text(row, col, text) # 指定行列获取内容 def get_text(self, row, col): if col == '#0': return self.item(row, 'text') else: return self.set(row, col) # 指定行列修改内容 def set_text(self, row, col, text): if col == '#0': self.item(row, text=text) else: self.set(row, col, text)
这个类继承自Tkinter的Treeview类,除了提供了get_text和set_text两个函数之外,主要是实现的就地编辑功能。这是ListView需要配合ItemEdit类使用:
class ItemEdit(Entry): def __init__(self, view, row, col): Entry.__init__(self, view) bbox = view.bbox(row, col) cur_width = self['width'] req_width = self.winfo_reqwidth() width = int(cur_width * bbox[2] / req_width) self.configure(width=width) cur_width = self['width'] req_width = self.winfo_reqwidth() width = int(cur_width * bbox[2] / req_width) self.configure(width=width) req_width = self.winfo_reqwidth() self.insert(INSERT, view.get_text(row, col)) self.view = view self.row = row self.col = col self.place(x=bbox[0]+(bbox[2]-req_width)/2, y=bbox[1]-1) self.bind('<FocusOut>', self.confirm) self.bind('<Return>', self.confirm) self.focus_set() self.update() self.bind('<Expose>', self.confirm)
def confirm(self, event): self.view.entry_confirmed(self.row, self.col, self.get()) self.destroy()
ItemEdit类继承自Entry类,当用户按下编辑控件内容并按下回车键是,将变更内容通知给ListView并销毁自身。除了按下回车键之外,Entry失去焦点或者更换主题时也会触发同样的处理。
有了这个ListView类之后,FileBroswer通过一个FileList类来实现目录内容显示和文件操作功能。
class FileList(ListView): def __init__(self, master, **kwargs): ListView.__init__(self, master, columns=5, size_grip=True) self.column("#0", width=150, minwidth=150, stretch=YES) self.column("#1", width=150, minwidth=150, stretch=YES) self.column("#2", width=100, minwidth=100, stretch=YES) self.column("#3", width=100, minwidth=100, stretch=NO) self.column("#4", width=100, minwidth=50, stretch=YES)
self.heading("#0", text="Name", anchor=W) self.heading("#1", text="Date modified", anchor=W) self.heading("#2", text="Type", anchor=W) self.heading("#3", text="Size", anchor=W) self.enable_edit('#0', True) self.tree_view = kwargs.get('tree_view') # 绑定鼠标右键 self.bind('<ButtonPress-3>', self.popup_menu)
def entry_confirmed(self, row, col, text): prev = self.get_text(row, col) if text != prev: path = self.tree_view.current_path() if path: try: src = os.path.join(path, prev) des = os.path.join(path, text) os.rename(src, des) self.set_text(row, col, text) except Exception as e: showerror('Error', str(e))
def selected_files(self): try: dir_node = self.tree_view.focus() if dir_node: path = self.tree_view.node_path(dir_node) return path, map(lambda x: self.item(x, 'text'), self.selection()) except Exception as e: showerror('Error', str(e)) return None
def open_current(self): path, selections = self.selected_files() if path: for fn in selections: os.startfile(os.path.join(path, fn))
def rename_current(self): path, selections = self.selected_files() if path: for fn in selections: dlg = RenameDialog(self, path, fn) self.select_node(None)
def delete_current(self): path, selections = self.selected_files() if path and askokcancel('askokcancel', 'Delete the file has been selected?'): try: for fn in selections: os.remove(os.path.join(path, fn)) except Exception as e: showerror('Delete file Error', str(e)) self.select_node(None)
def popup_menu(self, event): iid = self.identify_row(event.y) if iid: if iid not in self.selection(): self.selection_set(iid) self.focus(iid) path, selections = self.selected_files() if path: menu = Menu(self, tearoff=False) menu.add_command(label='Open', command=self.open_current) if len(list(selections)) == 1: menu.add_command(label='Rename', command=self.rename_current) menu.add_command(label='Delete', command=self.delete_current) menu.post(event.x_root, event.y_root)
def select_node(self, event): children = self.get_children('') for c in children: self.delete(c) focus = self.tree_view.focus() path = self.tree_view.node_path(focus) if os.path.isdir(path): try: dir_items = os.scandir(path) iid = 0 for item in dir_items: if item.is_file() and ('.$'.find(item.name[0]) < 0): stat_info = os.stat(item.path) m_time = time.strftime("%Y/%m/%d %H:%M", time.localtime(stat_info.st_mtime)) type = ' File' dot_pos = item.name.rfind('.') if dot_pos > 0: type = item.name[dot_pos + 1:].upper() + type self.insert('', 'end', str(iid), text=item.name, values=(m_time, type, stat_info.st_size)) iid += 1 except Exception as e: print(e)
代码的内容和前面文章中说明的没有区别,只是将相关功能集中到一个类中,这样代码的内聚性更好。接下来使用这个类的代码就简单了:
detail_area, list_view = scrollable(paned_window, FileList, tree_view=tree_view)paned_window.add(detail_area)tree_view.bind('<<TreeviewSelect>>', list_view.select_node)
以下为本例的动作视频:
视频链接
完整代码可以从以下地址下载:
https://github.com/xueweiguo/TkinterPrimer/blob/master/FileBrowser
觉得本文有帮助?请分享给更多人。
阅读更多更新文章,请关注微信公众号【面向对象思考】