How to use Python oops concepts to develop dual pane file browser?

97 Views Asked by At

I am trying to create a dual-pane file browser using python and Tkinter GUI to which I will be adding functions like 'compare directories', 'compare files' on a button click.

(The final GUI looks somewhat like this tool called Meld https://en.wikipedia.org/wiki/Meld_(software))

As a first step, I am trying to create a Tree() class (whose script is written in Tree_browser.py). Then I am importing this using import Tree_browser into dircmp_utility.py. (I will also be writing one more script for directory/file comparison called dircmp.py)

Now my problem is I am new to class & oops concepts and still trying to learn & understand. So after spending some time over the internet(YouTube, StackOverflow, geeks for geeks, etc..), I tried the below code for which I am getting below shown errors.

Can someone please tell me why I am getting this error and what is the mistake in my code

Even though self, autoscroll(), populate_roots() are defined it is throwing an error saying they are not defined. Why is this??

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

Errors after running 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

UPDATE: Posting WORKING SCRIPT

Keeping the incorrect code which I posted earlier as it is so that others can learn from my mistakes. Thanks for all the comments which actually did help me to write the correct script.

Tree_browser.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()
1

There are 1 best solutions below

2
Prune On

Let's extract your class profile, one function profile, and the erroneous statement:

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

You seem to be a bit confused about the structure of a class. The 3-line statement above is in the body of your class. It is not within an instance method -- so there is no symbiont instance, and no self variable is defined.

Later in the same statement, you try to call an "open" function, autoscroll. There is no such function. You are confusing this with the instance method of the same name. Look up how to call an instance method -- it would be something continaing

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

Back up a few steps. Instead of writing all this code when you haven't yet learned how to call a class method, start again with just the class and method headers. Make stubs for the headers -- the body will be only pass, or return a constant for the caller to save. Use these to get your hierarchy straight. Then restore the real method bodies. There is no need to post over 100 lines of code to illustrate a 10-line problem; I've learned to do my coding with that principle, as well.