DEV Community

Mate Technologies
Mate Technologies

Posted on

FileMate Explorer Pro: A Modern Python File Explorer with Tkinter

FileMate Explorer Pro is a sleek, user-friendly file explorer built with Python and Tkinter. It supports dark mode, drag-and-drop, copy/paste, folder creation, deletion, search, sorting, and a tree-view sidebarโ€”everything you need to manage files efficiently.

If you've ever wanted a lightweight, fully-featured file explorer made entirely in Python, FileMate Explorer Pro is here. Using tkinter for the GUI and sv_ttk for modern theming, this app allows you to navigate, manage, and organize your files with ease.

Key features include:

Tree-view sidebar for quick navigation

File list with search and sort functionality

Drag-and-drop file management

Copy, paste, rename, and delete options

Dark mode toggle

Status bar updates for better feedback

Full Script: FileMate_Explorer_Pro.py

import os
import shutil
import threading
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import sv_ttk

# =========================
# App Setup
# =========================
root = tk.Tk()
root.title("FileMate Explorer Pro")
root.geometry("1200x750")
root.minsize(1200, 700)
sv_ttk.set_theme("light")

# =========================
# Globals
# =========================
dark_mode_var = tk.BooleanVar(value=False)
current_path_var = tk.StringVar(value=os.path.expanduser("~"))
status_var = tk.StringVar(value="Ready")
clipboard = []  # for copy/paste multiple items
search_var = tk.StringVar()
sort_var = tk.StringVar(value="Name")
drag_data = {"items": None}

# =========================
# Helpers
# =========================
def set_status(msg):
    status_var.set(msg)
    root.update_idletasks()

def toggle_theme():
    sv_ttk.set_theme("dark" if dark_mode_var.get() else "light")
    set_status(f"Theme switched to {'Dark' if dark_mode_var.get() else 'Light'} mode")

def list_files(path):
    entries = []
    try:
        for item in os.listdir(path):
            full_path = os.path.join(path, item)
            is_dir = os.path.isdir(full_path)
            try:
                mtime = os.path.getmtime(full_path)
            except Exception:
                mtime = 0
            ext = os.path.splitext(item)[1].lower() if not is_dir else ""
            entries.append({"name": item, "path": full_path, "is_dir": is_dir, "mtime": mtime, "ext": ext})
    except PermissionError:
        pass
    return entries

def sort_entries(entries):
    method = sort_var.get()
    if method == "Name":
        entries.sort(key=lambda x: x["name"].lower())
    elif method == "Type":
        entries.sort(key=lambda x: (x["ext"], x["name"].lower()))
    elif method == "Date":
        entries.sort(key=lambda x: x["mtime"], reverse=True)
    return entries

def refresh_file_list(path=None):
    path = path or current_path_var.get()
    if not os.path.exists(path):
        messagebox.showerror("Error", "Path does not exist!")
        return
    current_path_var.set(path)
    file_listbox.delete(0, tk.END)
    set_status(f"Listing files in {path}...")

    def worker():
        entries = sort_entries(list_files(path))
        search_text = search_var.get().lower()
        def update_ui():
            for e in entries:
                if search_text in e["name"].lower():
                    icon = "๐Ÿ“ " if e["is_dir"] else "๐Ÿ“„ "
                    file_listbox.insert(tk.END, icon + e["name"])
            set_status(f"Listing files in {path}")
        root.after(0, update_ui)

    threading.Thread(target=worker, daemon=True).start()

def get_selected_items():
    return [os.path.join(current_path_var.get(), file_listbox.get(idx)[2:]) for idx in file_listbox.curselection()]

def open_selected():
    items = get_selected_items()
    for path in items:
        if os.path.isdir(path):
            refresh_file_list(path)
            break
        else:
            try:
                os.startfile(path)
                set_status(f"Opened '{os.path.basename(path)}'")
            except Exception as e:
                messagebox.showerror("Error", f"Cannot open file: {e}")

def go_up():
    parent = os.path.dirname(current_path_var.get())
    refresh_file_list(parent)

def create_folder():
    folder_name = simpledialog.askstring("Create Folder", "Enter folder name:")
    if folder_name:
        path = os.path.join(current_path_var.get(), folder_name)
        try:
            os.makedirs(path)
            refresh_file_list()
            set_status(f"Folder '{folder_name}' created")
        except FileExistsError:
            messagebox.showerror("Error", "Folder already exists.")
        except Exception as e:
            messagebox.showerror("Error", str(e))

def delete_items():
    items = get_selected_items()
    if not items:
        return
    names = ', '.join([os.path.basename(i) for i in items])
    if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete:\n{names}?"):
        for path in items:
            try:
                if os.path.isdir(path):
                    shutil.rmtree(path)
                else:
                    os.remove(path)
            except Exception as e:
                messagebox.showerror("Error", str(e))
        refresh_file_list()
        set_status(f"Deleted selected items")

def rename_item():
    items = get_selected_items()
    if len(items) != 1:
        messagebox.showinfo("Rename", "Please select a single item to rename.")
        return
    old_path = items[0]
    old_name = os.path.basename(old_path)
    new_name = simpledialog.askstring("Rename", f"Enter new name for '{old_name}':")
    if new_name:
        new_path = os.path.join(current_path_var.get(), new_name)
        try:
            os.rename(old_path, new_path)
            refresh_file_list()
            set_status(f"Renamed '{old_name}' to '{new_name}'")
        except Exception as e:
            messagebox.showerror("Error", str(e))

def copy_items():
    global clipboard
    clipboard = get_selected_items()
    if clipboard:
        set_status(f"Copied {len(clipboard)} item(s)")

def paste_items():
    global clipboard
    if not clipboard:
        return
    for item in clipboard:
        dest = os.path.join(current_path_var.get(), os.path.basename(item))
        try:
            if os.path.isdir(item):
                shutil.copytree(item, dest)
            else:
                shutil.copy2(item, dest)
        except Exception as e:
            messagebox.showerror("Error", str(e))
    refresh_file_list()
    set_status(f"Pasted {len(clipboard)} item(s)")

# =========================
# Drag and Drop
# =========================
def on_start_drag(event):
    selection = file_listbox.curselection()
    if selection:
        drag_data["items"] = get_selected_items()

def on_drop(event):
    if not drag_data["items"]:
        return
    drop_index = file_listbox.nearest(event.y)
    dest_item_text = file_listbox.get(drop_index)
    dest_name = dest_item_text[2:]
    dest_path = os.path.join(current_path_var.get(), dest_name)
    if not os.path.isdir(dest_path):
        messagebox.showinfo("Drag & Drop", "You can only drop onto a folder.")
        return
    for item in drag_data["items"]:
        try:
            shutil.move(item, os.path.join(dest_path, os.path.basename(item)))
        except Exception as e:
            messagebox.showerror("Error", str(e))
    drag_data["items"] = None
    refresh_file_list()
    set_status("Moved items successfully")

# =========================
# Tree-view Sidebar
# =========================
tree_frame = ttk.Frame(root, width=300)
tree_frame.pack(side="left", fill="y", padx=(10,0), pady=10)
tree_scroll = ttk.Scrollbar(tree_frame)
tree_scroll.pack(side="right", fill="y")
tree = ttk.Treeview(tree_frame, yscrollcommand=tree_scroll.set)
tree.pack(expand=True, fill="y")
tree_scroll.config(command=tree.yview)

def populate_tree(parent, path):
    try:
        for item in os.listdir(path):
            abs_path = os.path.join(path, item)
            if os.path.isdir(abs_path):
                node = tree.insert(parent, "end", text=item, values=[abs_path], open=False)
                if any(os.path.exists(os.path.join(abs_path, f)) for f in os.listdir(abs_path)):
                    tree.insert(node, "end", text="dummy")
    except PermissionError:
        pass

def on_tree_expand(event):
    node = tree.focus()
    children = tree.get_children(node)
    for child in children:
        if tree.item(child, "text") == "dummy":
            tree.delete(child)
    values = tree.item(node, "values")
    if values:
        path = values[0]
        populate_tree(node, path)

def on_tree_select(event):
    node = tree.focus()
    values = tree.item(node, "values")
    if values:
        path = values[0]
        refresh_file_list(path)

home_path = os.path.expanduser("~")
root_node = tree.insert("", "end", text=os.path.basename(home_path), open=True, values=[home_path])
populate_tree(root_node, home_path)
tree.bind("<<TreeviewOpen>>", on_tree_expand)
tree.bind("<<TreeviewSelect>>", on_tree_select)

# =========================
# Context Menu
# =========================
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Open", command=open_selected)
context_menu.add_command(label="Delete", command=delete_items)
context_menu.add_command(label="Rename", command=rename_item)
context_menu.add_separator()
context_menu.add_command(label="Copy", command=copy_items)
context_menu.add_command(label="Paste", command=paste_items)

def show_context_menu(event):
    selection = file_listbox.nearest(event.y)
    file_listbox.selection_clear(0, tk.END)
    file_listbox.selection_set(selection)
    context_menu.post(event.x_root, event.y_root)
    context_menu.grab_release()

# =========================
# Main File Frame
# =========================
main_frame = ttk.Frame(root, padding=10)
main_frame.pack(side="left", expand=True, fill="both", pady=10, padx=10)

toolbar = ttk.Frame(main_frame)
toolbar.pack(fill="x", pady=(0,10))
ttk.Button(toolbar, text="Up", command=go_up).pack(side="left", padx=5)
ttk.Button(toolbar, text="New Folder", command=create_folder).pack(side="left", padx=5)
ttk.Button(toolbar, text="Delete", command=delete_items).pack(side="left", padx=5)
ttk.Button(toolbar, text="Copy", command=copy_items).pack(side="left", padx=5)
ttk.Button(toolbar, text="Paste", command=paste_items).pack(side="left", padx=5)
ttk.Checkbutton(toolbar, text="Dark Mode", variable=dark_mode_var, command=toggle_theme).pack(side="right", padx=5)

search_frame = ttk.Frame(main_frame)
search_frame.pack(fill="x", pady=(0,10))
ttk.Label(search_frame, text="Search:").pack(side="left")
ttk.Entry(search_frame, textvariable=search_var).pack(side="left", fill="x", expand=True, padx=(5,10))
search_var.trace("w", lambda *args: refresh_file_list())
ttk.Label(search_frame, text="Sort by:").pack(side="left")
ttk.Combobox(search_frame, textvariable=sort_var, values=["Name","Type","Date"], state="readonly", width=10).pack(side="left")
sort_var.trace("w", lambda *args: refresh_file_list())

path_frame = ttk.Frame(main_frame)
path_frame.pack(fill="x", pady=(0,10))
ttk.Label(path_frame, text="Current Path:").pack(side="left")
ttk.Entry(path_frame, textvariable=current_path_var, width=80).pack(side="left", padx=(5,0))
ttk.Button(path_frame, text="Go", command=lambda: refresh_file_list(current_path_var.get())).pack(side="left", padx=5)

file_frame = ttk.Frame(main_frame)
file_frame.pack(expand=True, fill="both")
file_listbox = tk.Listbox(file_frame, font=("Segoe UI", 11), selectmode=tk.EXTENDED)
file_listbox.pack(side="left", expand=True, fill="both")
file_listbox.bind("<Double-Button-1>", lambda e: open_selected())
file_listbox.bind("<Button-3>", show_context_menu)
file_listbox.bind("<ButtonPress-1>", on_start_drag)
file_listbox.bind("<ButtonRelease-1>", on_drop)
scrollbar = ttk.Scrollbar(file_frame, orient="vertical", command=file_listbox.yview)
scrollbar.pack(side="right", fill="y")
file_listbox.config(yscrollcommand=scrollbar.set)

# =========================
# Status Bar
# =========================
ttk.Label(root, textvariable=status_var, anchor="w", font=("Segoe UI", 10)).pack(side="bottom", fill="x")

# =========================
# Initialize
# =========================
refresh_file_list()
root.mainloop()
Enter fullscreen mode Exit fullscreen mode

How It Works

Tkinter GUI & sv_ttk
The app uses tkinter for the interface and sv_ttk for a modern, light/dark theme toggle.

File Navigation
The left sidebar tree-view allows users to navigate directories. The main panel lists files/folders with icons.

File Operations
You can create folders, rename files, delete items, copy/paste multiple files, and drag-and-drop to move files.

Search & Sort
Dynamically search file/folder names and sort by Name, Type, or Date.

Status Bar
Provides live feedback for all operations.

This script gives you a fully functional, Python-based file explorer ready to run. Perfect for learning GUI programming, file management logic, or even customizing for your own use.

Top comments (0)