Issue with File Explorer Implementation in Python

537
1
05-18-2024 03:19 PM
happydzoranian
New Contributor

I'm encountering an issue with my Python File Explorer application and seeking guidance from fellow developers in the Python community. I've developed a simple file explorer using Tkinter, but I'm facing a problem specifically with the "This PC" section.

When I click on a folder or a drive in the left sidebar, it does not open as expected. The other sections like Desktop, Documents, etc., work perfectly fine. However, the issue arises when I attempt to navigate through the drives or folders under "This PC".

I've included the full script below for your reference. Could you please review it and provide insights into what might be causing this issue? Additionally, any suggestions or corrections to improve the functionality of the file explorer would be highly appreciated.

Thank you for your time and assistance!

import os
import tkinter as tk
from tkinter import ttk, messagebox
import time
import shutil

class FileExplorer(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("File Explorer")
        self.geometry("800x600")

        self.sidebar = Sidebar(self)
        self.toolbar = Toolbar(self)
        self.breadcrumb = Breadcrumb(self)
        self.file_list = FileList(self)

        self.sidebar.pack(side='left', fill='y')
        self.toolbar.pack(side='top', fill='x')
        self.breadcrumb.pack(fill='x')
        self.file_list.pack(expand=True, fill='both')

        self.current_path = os.path.expanduser("~")
        self.update_file_list()

    def update_file_list(self):
        if self.current_path == "This PC":
            # Handle special case for This PC
            drives = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
                      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
            self.file_list.delete(*self.file_list.get_children())
            for drive in drives:
                try:
                    if os.path.exists(drive + ":\\"):
                        total_size = self.file_list.get_drive_size(drive)
                        self.file_list.insert('', 'end', text=drive + ":", values=('', '', total_size, ''))
                except Exception as e:
                    print(f"Error getting drive info for {drive}: {e}")
        else:
            self.file_list.update_file_list(self.current_path)
            self.breadcrumb.update_breadcrumb(self.current_path)

    def change_directory(self, directory):
        special_dirs = {
            "Quick Access": os.path.expanduser("~"),
            "Desktop": os.path.join(os.path.expanduser("~"), "Desktop"),
            "Downloads": os.path.join(os.path.expanduser("~"), "Downloads"),
            "Documents": os.path.join(os.path.expanduser("~"), "Documents"),
            "Pictures": os.path.join(os.path.expanduser("~"), "Pictures"),
            "Music": os.path.join(os.path.expanduser("~"), "Music"),
            "Videos": os.path.join(os.path.expanduser("~"), "Videos"),
            "This PC": "This PC",
            "Network": None
        }

        if directory in special_dirs:
            if directory == "This PC":
                self.current_path = "This PC"
            else:
                self.current_path = special_dirs[directory]
        else:
            self.current_path = directory

        self.update_file_list()

class Sidebar(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.options = ["Quick Access", "Desktop", "Downloads", "Documents", "Pictures", "Music", "Videos", "This PC", "Network"]
        self.create_sidebar()

    def create_sidebar(self):
        for option in self.options:
            btn = tk.Button(self, text=option, bg='lightgrey', relief='flat', command=lambda o=option: self.master.change_directory(o))
            btn.pack(fill='x')

class Toolbar(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, bg='lightgrey', *args, **kwargs)
        self.buttons = ["File", "Home", "Share", "View"]
        self.create_toolbar()

    def create_toolbar(self):
        for btn_text in self.buttons:
            btn = tk.Button(self, text=btn_text, command=lambda b=btn_text: self.toolbar_action(b))
            btn.pack(side='left')

    def toolbar_action(self, action):
        if action == "File":
            self.master.file_list.open_file_popup()

class Breadcrumb(tk.Label):
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, text="", bg='lightgrey', anchor='w', relief='sunken', borderwidth=1, *args, **kwargs)

    def update_breadcrumb(self, current_path):
        self.config(text=current_path)

class FileList(ttk.Treeview):
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, columns=("Extension", "Date", "Size", "Comments"), *args, **kwargs)
        self.heading("#0", text="Name", anchor='w')
        self.heading("Extension", text="Extension", anchor='w')
        self.heading("Date", text="Date Modified", anchor='w')
        self.heading("Size", text="Size", anchor='w')
        self.heading("Comments", text="Comments", anchor='w')
        self.column("#0", width=200, stretch=False)
        self.column("Extension", width=100, stretch=False)
        self.column("Date", width=150, stretch=False)
        self.column("Size", width=100, stretch=False)
        self.column("Comments", width=200, stretch=False)
        self.bind("<Double-1>", self.on_file_open)
        self.bind("<Button-3>", self.show_column_menu)

        self.show_columns = {"Extension": False, "Date": False, "Size": False, "Comments": False}

    def show_column_menu(self, event):
        menu = tk.Menu(self, tearoff=0)
        for column in self.show_columns.keys():
            menu.add_checkbutton(label=column, command=lambda col=column: self.toggle_column(col), onvalue=True, offvalue=False, variable=tk.BooleanVar(value=self.show_columns[column]))
        menu.add_separator()
        menu.add_command(label="Others...", command=self.show_others_popup)
        menu.post(event.x_root, event.y_root)

    def show_others_popup(self):
        popup = tk.Toplevel(self.master)
        popup.title("Select Columns")
        popup.geometry("300x400")

        available_columns = ["Name", "Extension", "Date", "Size", "Comments"]
        listbox = tk.Listbox(popup, selectmode=tk.MULTIPLE)
        for col in available_columns:
            listbox.insert(tk.END, col)
        listbox.pack(expand=True, fill='both')

        def apply_selection():
            selected = listbox.curselection()
            for col in available_columns:
                self.show_columns[col] = False
            for i in selected:
                self.show_columns[available_columns[i]] = True
            self.update_column_visibility()
            popup.destroy()

        button = tk.Button(popup, text="Apply", command=apply_selection)
        button.pack()

    def toggle_column(self, column):
        self.show_columns[column] = not self.show_columns[column]
        self.update_column_visibility()

    def update_column_visibility(self):
        for column, visible in self.show_columns.items():
            if visible:
                self.heading(column, text=column, anchor='w')
                self.column(column, width=100, stretch=False)
            else:
                self.heading(column, text="")
                self.column(column, width=0, stretch=False)

    def get_drive_size(self, drive):
        try:
            total, used, free = shutil.disk_usage(drive + ":\\")
            total_size = self.format_file_size(total)
            return total_size
        except:
            return ""

    def get_folder_size(self, folder):
        total_size = 0
        try:
            for entry in os.scandir(folder):
                if entry.is_file():
                    total_size += entry.stat().st_size
                elif entry.is_dir():
                    total_size += self.get_folder_size(entry.path)
            return self.format_file_size(total_size)
        except:
            return ""

    def format_file_size(self, size):
        if size < 1024:
            return f"{size} B"
        elif size < 1024 ** 2:
            return f"{size / 1024:.1f} KB"
        elif size < 1024 ** 3:
            return f"{size / (1024 ** 2):.1f} MB"
        else:
            return f"{size / (1024 ** 3):.1f} GB"

    def update_file_list(self, directory):
        self.delete(*self.get_children())
        try:
            for entry in os.scandir(directory):
                name = entry.name
                if entry.is_file():
                    extension = os.path.splitext(name)[1]
                    date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(entry.stat().st_mtime))
                    size = self.format_file_size(entry.stat().st_size)
                    self.insert('', 'end', text=name, values=(extension, date, size, ''))
                elif entry.is_dir():
                    self.insert('', 'end', text=name, values=('', '', '', ''))
        except Exception as e:
            print(f"Error scanning directory {directory}: {e}")

    def on_file_open(self, event):
        item = self.selection()[0]
        path = self.item(item, "text")
        if self.master.current_path != "This PC":
            path = os.path.join(self.master.current_path, path)
        if os.path.isdir(path):
            self.master.current_path = path
            self.master.update_file_list()

if __name__ == "__main__":
    app = FileExplorer()
    app.mainloop()
Tags (3)
0 Kudos
1 Reply
Luke_Pinner
MVP Regular Contributor

This community is specific to the ArcGIS software suite, we're not a general python forum.  You're more likely to get some help if you post somewhere like Stack Overflow

But if you post on Stack Overflow,  I recommend you read their "How do I ask a good question?" help page.

You'll need to provide more information than just  "encountering an issue". Instead explain exactly what the issue is, i.e is there an error, and if so, include the full error text? Or perhaps the actual result is different to your expected, so specify that. 

Currently you are just presenting a wall of code and asking someone to debug it blind.