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()
Let's extract your class profile, one function profile, and the erroneous statement:
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
selfvariable 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 continaingBack 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.