Tkinter主题控件的Treeview除了体统树状视图之外,还可以提供列表视图。但是由于控件本身的复杂性,如果想正确的使用这个控件,或多或少都需要一些时间来调查和学习。本文提供一个通用的ListView类以简化这个过程。

Python GUI编程入门(37)-通用的ListView类_Python

这个类除了提供一般的列表功能之外,还提供一个简单的就地编辑功能:当用户双击文件名时,可以触发一个文本输入框直接编辑文件名。


 


 

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​

 

觉得本文有帮助?请分享给更多人。

阅读更多更新文章,请关注微信公众号【面向对象思考】