首页 > 解决方案 > 如何使用 Python oops 概念开发双窗格文件浏览器?

问题描述

我正在尝试使用python 和 Tkinter GUI创建一个双窗格文件浏览器,我将在单击按钮时添加“比较目录”、“比较文件”等功能。

(最终的 GUI 看起来有点像这个名为Meld https://en.wikipedia.org/wiki/Meld_(software)的工具)

作为第一步,我正在尝试创建一个Tree()类(其脚本是用 Tree_browser.py 编写的)。然后我将它导入import Tree_browserdircmp_utility.py中。(我还将编写另一个名为dircmp.py的目录/文件比较脚本)

现在我的问题是我对类和 oops 概念不熟悉,并且仍在努力学习和理解。因此,在互联网上花费了一些时间(YouTube、StackOverflow、geeks for geeks 等)之后,我尝试了下面的代码,我得到了下面显示的错误。

有人可以告诉我为什么会收到此错误以及我的代码中的错误是什么

即使定义了 self、autoscroll()、populate_roots(),它也会抛出一个错误,说明它们没有定义。为什么是这样??

树浏览器.py

import os
import glob
import tkinter as tk
from tkinter import ttk

class Tree():
    def __init__(self,root):
        self.frame = tk.Frame(root)
        self.frame.pack()

    def populate_tree(self, tree, node):
        if tree.set(node, "type") != 'directory':
            return

        path = tree.set(node, "fullpath")
        tree.delete(*tree.get_children(node))

        parent = tree.parent(node)
        special_dirs = [] if parent else glob.glob('.') + glob.glob('..')

        for p in special_dirs + os.listdir(path):
            ptype = None
            p = os.path.join(path, p).replace('\\', '/')
            if os.path.isdir(p): ptype = "directory"
            elif os.path.isfile(p): ptype = "file"

            fname = os.path.split(p)[1]
            id = tree.insert(node, "end", text=fname, values=[p, ptype])

            if ptype == 'directory':
                if fname not in ('.', '..'):
                    tree.insert(id, 0, text="dummy")
                    tree.item(id, text=fname)
            elif ptype == 'file':
                size = os.stat(p).st_size
                tree.set(id, "size", "%d bytes" % size)


    def populate_roots(self, tree):
        self.dir = os.path.abspath('.').replace('\\', '/')
        self.node = tree.insert('', 'end', text=dir, values=[dir, "directory"])
        populate_tree(tree, node)

    def update_tree(self, event):
        tree = event.widget
        populate_tree(tree, tree.focus())

    def change_dir(self,event):
        tree = event.widget
        node = tree.focus()
        if tree.parent(node):
            path = os.path.abspath(tree.set(node, "fullpath"))
            if os.path.isdir(path):
                os.chdir(path)
                tree.delete(tree.get_children(''))
                populate_roots(tree)

    def autoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        first, last = float(first), float(last)
        if first <= 0 and last >= 1:
            sbar.pack_forget()
        else:
            sbar.pack()
        sbar.set(first, last)

    vsb = ttk.Scrollbar(orient="vertical")
    hsb = ttk.Scrollbar(orient="horizontal")

    tree = ttk.Treeview(self.frame, columns=("fullpath", "type", "size"),
        displaycolumns="size", yscrollcommand=lambda f, l: autoscroll(vsb, f, l),
        xscrollcommand=lambda f, l:autoscroll(hsb, f, l))
    tree.pack()
    vsb['command'] = tree.yview
    hsb['command'] = tree.xview

    tree.heading("#0", text="Directory Structure", anchor='w')
    tree.heading("size", text="File Size", anchor='w')
    tree.column("size", stretch=0, width=100)

    populate_roots(tree)
    tree.bind('<<TreeviewOpen>>', update_tree)
    tree.bind('<Double-Button-1>', change_dir)

    # Arrange the tree and its scrollbars in the toplevel
    tree.pack(expand=1, fill=BOTH)
    vsb.pack(expand=1, fill=Y)
    hsb.pack(expand=1, fill=X)

    
if __name__ == '__main__':
    pass

dircmp_utility.py

import Tree_browser
import dircmp
import tkinter

master = tk()

left_frame = Frame(master)
right_frame = Frame(master)

left_frame.pack()
right_frame.pack()

left_tree = Tree(left_frame)
right_tree = Tree(right_frame)

master.mainloop()

运行 dircmp.py 后的错误

Traceback (most recent call last):
  File "E:\Python_code_to_compare_two_folders\dircmp_using class\dircmp_utility.py", line 3, in <module>
    import Tree_browser
  File "E:\Python_code_to_compare_two_folders\dircmp_using class\Tree_browser.py", line 7, in <module>
    class Tree():
  File "E:\Python_code_to_compare_two_folders\dircmp_using class\Tree_browser.py", line 82, in Tree
    populate_roots(tree)
TypeError: populate_roots() missing 1 required positional argument: 'tree'
>>> Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python38\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "E:\Python_code_to_compare_two_folders\dircmp_using class\Tree_browser.py", line 73, in <lambda>
    xscrollcommand=lambda f, l:autoscroll(hsb, f, l))
NameError: name 'autoscroll' is not defined
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python38\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "E:\Python_code_to_compare_two_folders\dircmp_using class\Tree_browser.py", line 72, in <lambda>
    displaycolumns="size", yscrollcommand=lambda f, l: autoscroll(vsb, f, l),
NameError: name 'autoscroll' is not defined
 File "E:\Python_code_to_compare_two_folders\dircmp_using class\Tree_browser.py", line 71, in Tree
    tree = ttk.Treeview(self.frame, columns=("fullpath", "type", "size"),
NameError: name 'self' is not defined

更新:发布工作脚本

保留我之前发布的错误代码,以便其他人可以从我的错误中吸取教训。感谢所有确实帮助我编写正确脚本的评论。

树浏览器.py

import os
import glob
import tkinter as tk
from tkinter import ttk

class Tree:
    def __init__(self,root):
        self.pane = tk.Frame(root, bg="red")
        self.frame = tk.Frame(self.pane, bg="blue")

        self.vsb = ttk.Scrollbar(self.frame, orient="vertical")
        self.hsb = ttk.Scrollbar(self.pane, orient="horizontal")

        self.tree = ttk.Treeview(self.frame, columns=("fullpath", "type", "size"),
                            displaycolumns="size", yscrollcommand=lambda f, l: self.yautoscroll(self.vsb, f, l),
                            xscrollcommand=lambda f, l: self.xautoscroll(self.hsb, f, l))
        self.vsb['command'] = self.tree.yview
        self.hsb['command'] = self.tree.xview

        self.tree.heading("#0", text="Directory Structure", anchor='w')
        self.tree.heading("size", text="File Size", anchor='w')
        self.tree.column("size", stretch=0, width=100)

        self.populate_roots(self.tree)
        self.tree.bind('<<TreeviewOpen>>', self.update_tree)
        self.tree.bind('<Double-Button-1>', self.change_dir)

        # Arrange the tree and its scrollbars in the toplevel
        self.pane.pack(side=tk.BOTTOM, expand=1, fill=tk.BOTH)
        self.frame.pack(side=tk.TOP, expand=1, fill=tk.BOTH)
        self.tree.pack(side=tk.LEFT, expand=1, fill=tk.BOTH)
        self.vsb.pack(expand=1, fill=tk.Y)
        self.hsb.pack(expand=1, fill=tk.X)

    def populate_tree(self, tree, node):
        if self.tree.set(self.node, "type") != 'directory':
            return
        self.path = self.tree.set(self.node, "fullpath")
        self.tree.delete(*self.tree.get_children(self.node))

        self.parent = self.tree.parent(self.node)
        self.special_dirs = [] if self.parent else glob.glob('.') + glob.glob('..')

        for p in self.special_dirs + os.listdir(self.path):
            ptype = None
            p = os.path.join(self.path, p).replace('\\', '/')
            if os.path.isdir(p): ptype = "directory"
            elif os.path.isfile(p): ptype = "file"

            self.fname = os.path.split(p)[1]
            id = tree.insert(self.node, "end", text=self.fname, values=[p, ptype])

            if ptype == 'directory':
                if self.fname not in ('.', '..'):
                    self.tree.insert(id, 0, text="dummy", open=True)
                    self.tree.item(id, text=self.fname)
            elif ptype == 'file':
                size = os.stat(p).st_size
                self.tree.set(id, "size", "%d bytes" % size)

    def populate_roots(self, tree):
        self.dir = os.path.abspath('.').replace('\\', '/')
        self.node = tree.insert('', 'end', text=self.dir, values=[self.dir, "directory"], open=True)
        self.populate_tree(self.tree, self.node)

    def update_tree(self, event):
        self.tree = event.widget
        self.node = self.tree.focus()
        self.populate_tree(self.tree, self.node)

    def change_dir(self,event):
        self.tree = event.widget
        self.node = self.tree.focus()
        if self.tree.parent(self.node):
            self.path = os.path.abspath(self.tree.set(self.node, "fullpath"))
            if os.path.isdir(self.path):
                os.chdir(self.path)
                self.tree.delete(self.tree.get_children(''))
                self.populate_roots(self.tree)

    def yautoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        self.first, self.last = float(first), float(last)
        if self.first <= 0 and self.last >= 1:
            sbar.pack_forget()
        else:
            sbar.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
        sbar.set(self.first, self.last)

    def xautoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        self.first, self.last = float(first), float(last)
        if self.first <= 0 and self.last >= 1:
            sbar.pack_forget()
        else:
            sbar.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        sbar.set(self.first, self.last)

if __name__ == '__main__':
    pass

dircmp_utility.py

import tkinter as tk

master = tk.Tk()

left_frame = tk.Frame(master)
right_frame = tk.Frame(master)

left_frame.pack(side=tk.LEFT, expand=1, fill=tk.BOTH)
right_frame.pack(side=tk.LEFT, expand=1, fill=tk.BOTH)

left_tree = Tree(left_frame)
right_tree = Tree(right_frame)

master.mainloop()

标签: pythonpython-3.xtkinterdjango-filebrowser

解决方案


让我们提取您的类配置文件、一个函数配置文件和错误语句:

class Tree():
    ...
    ...

    def autoscroll(self, sbar, first, last):
        ...
        ...

    tree = ttk.Treeview(self.frame, columns=("fullpath", "type", "size"),
        displaycolumns="size", yscrollcommand=lambda f, l: autoscroll(vsb, f, l),
        xscrollcommand=lambda f, l:autoscroll(hsb, f, l))

您似乎对类的结构有些困惑。上面的 3 行语句在你的类的正文中。不在实例方法中——因此没有共生体实例,也没有self定义变量。

稍后在同一语句中,您尝试调用“打开”函数,autoscroll. 没有这样的功能。您将此与同名的实例方法混淆了。查找如何调用实例方法——这将是一些持续的事情

seedling = Tree()
...
seedling.autoscroll(vsb, f, l)

后退几步。当您还没有学会如何调用类方法时,不要编写所有这些代码,而是从类和方法头重新开始。为标头制作存根——正文将是 only pass,或者返回一个常量供调用者保存。使用这些来使您的层次结构清晰。 然后恢复真实的方法体。没有必要贴上 100 多行的代码来说明一个 10 行的问题;我也学会了按照这个原则进行编码。


推荐阅读