Skip to content

Basic File Explorer

Abstract

A file explorer is one of the most universally useful desktop GUIs you can build. In this project you will build a Python file viewer with Tkinter that lets the user pick any file via the OS file dialog, then displays its contents in a scrollable text area. Then we grow it: editable buffer, save-back, a directory tree, recent-files list, encoding detection, and a syntax-highlighted code view.

You will learn:

  • How filedialogfiledialog integrates with the native OS picker.
  • How to read text safely with the correct encoding.
  • How to wire scrollbars to a TextText widget.
  • The gridgrid vs packpack decision for layout.
  • How to add real features (editing, saving, a directory tree) one at a time.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Tkinter (ships with most Python installs).
  • Comfort with classes (optional but recommended).

On Linux you may need to install Tkinter separately:

install
sudo apt-get install python3-tk        # Debian/Ubuntu
sudo dnf install python3-tkinter       # Fedora
install
sudo apt-get install python3-tk        # Debian/Ubuntu
sudo dnf install python3-tkinter       # Fedora

Getting Started

Create the project

  1. Create folder file-explorerfile-explorer.
  2. Inside, create fileexplorer.pyfileexplorer.py.

Write the code

File ExplorerSource
File Explorer
# Basic File Explorer in Python
 
import tkinter as tk # pip install tk
from tkinter import *
from tkinter import filedialog, Text
 
root = tk.Tk()
apps = []
 
def browseFiles():
    output.delete('1.0', END)
    filename = filedialog.askopenfilename(initialdir = "/", title = "Select a File", filetypes = (("Text files","*.txt*"), ("all files", "*.*")))
    pathh.config(text = "Path: " + filename)
    tf = open(filename, encoding="utf8")
    data = tf.read()
    output.insert(END, data)
    tf.close()   
    
        
root.title("File Explorer")
root.geometry("700x700")
root.config(background="white")
 
label_file_explorer = Label(root, text = "File Explorer using Tkinter", width = 100, height = 3, fg = "gray", bg = "whitesmoke")
 
button_explore = Button(root, text = "Browse Files", command = browseFiles)
exit_button = Button(root, text = "Exit", command = root.destroy)
output = Text(root)
pathh = Label(root, text = "Path: ", width = 100, height = 3, fg = "gray", bg = "whitesmoke")
 
label_file_explorer.grid(column = 1, row = 1)
button_explore.grid(column = 1, row = 2)
exit_button.grid(column = 1, row = 3)
output.grid(column = 1, row = 4)
pathh.grid(column = 1, row = 5)
 
root.mainloop()
File Explorer
# Basic File Explorer in Python
 
import tkinter as tk # pip install tk
from tkinter import *
from tkinter import filedialog, Text
 
root = tk.Tk()
apps = []
 
def browseFiles():
    output.delete('1.0', END)
    filename = filedialog.askopenfilename(initialdir = "/", title = "Select a File", filetypes = (("Text files","*.txt*"), ("all files", "*.*")))
    pathh.config(text = "Path: " + filename)
    tf = open(filename, encoding="utf8")
    data = tf.read()
    output.insert(END, data)
    tf.close()   
    
        
root.title("File Explorer")
root.geometry("700x700")
root.config(background="white")
 
label_file_explorer = Label(root, text = "File Explorer using Tkinter", width = 100, height = 3, fg = "gray", bg = "whitesmoke")
 
button_explore = Button(root, text = "Browse Files", command = browseFiles)
exit_button = Button(root, text = "Exit", command = root.destroy)
output = Text(root)
pathh = Label(root, text = "Path: ", width = 100, height = 3, fg = "gray", bg = "whitesmoke")
 
label_file_explorer.grid(column = 1, row = 1)
button_explore.grid(column = 1, row = 2)
exit_button.grid(column = 1, row = 3)
output.grid(column = 1, row = 4)
pathh.grid(column = 1, row = 5)
 
root.mainloop()

Run it

command
C:\Users\Your Name\file-explorer> python fileexplorer.py
# Window opens with:
#   - Title bar "File Explorer using Tkinter"
#   - "Browse Files" button
#   - "Exit" button
#   - Large text area
#   - "Path: ..." label at the bottom
command
C:\Users\Your Name\file-explorer> python fileexplorer.py
# Window opens with:
#   - Title bar "File Explorer using Tkinter"
#   - "Browse Files" button
#   - "Exit" button
#   - Large text area
#   - "Path: ..." label at the bottom

Click Browse Files, pick any text file, and its content appears in the panel.

Step-by-Step Explanation

1. Imports and root window

fileexplorer.py
import tkinter as tk
from tkinter import filedialog, Text, Label, Button, END
 
root = tk.Tk()
root.title("File Explorer")
root.geometry("700x700")
root.config(background="white")
fileexplorer.py
import tkinter as tk
from tkinter import filedialog, Text, Label, Button, END
 
root = tk.Tk()
root.title("File Explorer")
root.geometry("700x700")
root.config(background="white")
  • Tk()Tk() creates the main window.
  • geometry("WxH")geometry("WxH") sets size in pixels.
  • Importing names like TextText, LabelLabel, ButtonButton directly is convenient — but only inside small scripts. For larger projects use tk.Texttk.Text, tk.Labeltk.Label, etc.

2. The browse callback

fileexplorer.py
def browseFiles():
    output.delete("1.0", END)
    filename = filedialog.askopenfilename(
        initialdir="/",
        title="Select a File",
        filetypes=(("Text files", "*.txt"), ("All files", "*.*")),
    )
    if not filename:
        return                                # user cancelled
    path_label.config(text=f"Path: {filename}")
    try:
        with open(filename, encoding="utf-8") as f:
            data = f.read()
    except UnicodeDecodeError:
        output.insert(END, "[binary or non-UTF-8 file]")
        return
    output.insert(END, data)
fileexplorer.py
def browseFiles():
    output.delete("1.0", END)
    filename = filedialog.askopenfilename(
        initialdir="/",
        title="Select a File",
        filetypes=(("Text files", "*.txt"), ("All files", "*.*")),
    )
    if not filename:
        return                                # user cancelled
    path_label.config(text=f"Path: {filename}")
    try:
        with open(filename, encoding="utf-8") as f:
            data = f.read()
    except UnicodeDecodeError:
        output.insert(END, "[binary or non-UTF-8 file]")
        return
    output.insert(END, data)

Three improvements over the naive version:

  • Cancel-safe. askopenfilenameaskopenfilename returns """" when the user closes the dialog; we bail early.
  • with open(...)with open(...) closes the file even on exception.
  • UnicodeDecodeErrorUnicodeDecodeError handler. Lets the program survive binary or weird-encoding files.

3. The widgets

fileexplorer.py
title_label = Label(root, text="File Explorer using Tkinter",
                    width=100, height=3, fg="gray", bg="whitesmoke")
browse_button = Button(root, text="Browse Files", command=browseFiles)
exit_button   = Button(root, text="Exit", command=root.destroy)
output        = Text(root, wrap="none")
path_label    = Label(root, text="Path: ", width=100, height=3, fg="gray", bg="whitesmoke")
fileexplorer.py
title_label = Label(root, text="File Explorer using Tkinter",
                    width=100, height=3, fg="gray", bg="whitesmoke")
browse_button = Button(root, text="Browse Files", command=browseFiles)
exit_button   = Button(root, text="Exit", command=root.destroy)
output        = Text(root, wrap="none")
path_label    = Label(root, text="Path: ", width=100, height=3, fg="gray", bg="whitesmoke")
  • Text(root)Text(root) is a multi-line text widget; the line/character coordinates work like "line.char""line.char" ("1.0""1.0" means line 1, char 0).
  • wrap="none"wrap="none" shows long lines with horizontal scrolling rather than wrapping.

4. Layout with grid

fileexplorer.py
title_label.grid(column=1, row=1)
browse_button.grid(column=1, row=2)
exit_button.grid(column=1, row=3)
output.grid(column=1, row=4)
path_label.grid(column=1, row=5)
 
root.mainloop()
fileexplorer.py
title_label.grid(column=1, row=1)
browse_button.grid(column=1, row=2)
exit_button.grid(column=1, row=3)
output.grid(column=1, row=4)
path_label.grid(column=1, row=5)
 
root.mainloop()

gridgrid arranges widgets in cells. Even with one column this gives a tidy vertical stack.

Add Scrollbars

A TextText widget without scrollbars is unusable for big files. Wire one up:

scroll.py
vsb = tk.Scrollbar(root, orient="vertical", command=output.yview)
hsb = tk.Scrollbar(root, orient="horizontal", command=output.xview)
output.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
vsb.grid(column=2, row=4, sticky="ns")
hsb.grid(column=1, row=6, sticky="ew")
scroll.py
vsb = tk.Scrollbar(root, orient="vertical", command=output.yview)
hsb = tk.Scrollbar(root, orient="horizontal", command=output.xview)
output.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
vsb.grid(column=2, row=4, sticky="ns")
hsb.grid(column=1, row=6, sticky="ew")

The bidirectional binding — command=output.yviewcommand=output.yview plus yscrollcommand=vsb.setyscrollcommand=vsb.set — connects scrollbar and text so they stay in sync.

Make It Editable + Save

Tkinter’s TextText widget is already editable by default. To save changes back:

save.py
def save_file():
    if not current_path:
        save_file_as()
        return
    try:
        with open(current_path, "w", encoding="utf-8") as f:
            f.write(output.get("1.0", END))
    except OSError as e:
        path_label.config(text=f"Save failed: {e}")
 
def save_file_as():
    path = filedialog.asksaveasfilename(defaultextension=".txt")
    if path:
        global current_path
        current_path = path
        save_file()
save.py
def save_file():
    if not current_path:
        save_file_as()
        return
    try:
        with open(current_path, "w", encoding="utf-8") as f:
            f.write(output.get("1.0", END))
    except OSError as e:
        path_label.config(text=f"Save failed: {e}")
 
def save_file_as():
    path = filedialog.asksaveasfilename(defaultextension=".txt")
    if path:
        global current_path
        current_path = path
        save_file()
  • output.get("1.0", END)output.get("1.0", END) returns the entire text content.
  • defaultextension=".txt"defaultextension=".txt" ensures users get a sensible extension if they forget.

Encoding Detection

Real files come in many encodings. chardetchardet can guess:

install
pip install chardet
install
pip install chardet
detect.py
import chardet
with open(filename, "rb") as f:
    raw = f.read()
enc = chardet.detect(raw)["encoding"] or "utf-8"
data = raw.decode(enc, errors="replace")
detect.py
import chardet
with open(filename, "rb") as f:
    raw = f.read()
enc = chardet.detect(raw)["encoding"] or "utf-8"
data = raw.decode(enc, errors="replace")

errors="replace"errors="replace" substitutes a replacement character for un-decodable bytes instead of crashing.

Add a Directory Tree

For a “real” explorer, show a tree on the left:

tree.py
from tkinter import ttk
import os
 
tree = ttk.Treeview(root)
tree.grid(column=0, row=4, sticky="ns")
 
def populate(parent, path):
    for entry in sorted(os.listdir(path)):
        full = os.path.join(path, entry)
        node = tree.insert(parent, "end", text=entry, values=[full])
        if os.path.isdir(full):
            tree.insert(node, "end")                 # placeholder for expansion
 
def on_open(event):
    node = tree.focus()
    tree.delete(*tree.get_children(node))
    populate(node, tree.item(node, "values")[0])
 
tree.bind("<<TreeviewOpen>>", on_open)
populate("", "/")
tree.py
from tkinter import ttk
import os
 
tree = ttk.Treeview(root)
tree.grid(column=0, row=4, sticky="ns")
 
def populate(parent, path):
    for entry in sorted(os.listdir(path)):
        full = os.path.join(path, entry)
        node = tree.insert(parent, "end", text=entry, values=[full])
        if os.path.isdir(full):
            tree.insert(node, "end")                 # placeholder for expansion
 
def on_open(event):
    node = tree.focus()
    tree.delete(*tree.get_children(node))
    populate(node, tree.item(node, "values")[0])
 
tree.bind("<<TreeviewOpen>>", on_open)
populate("", "/")

Lazy expansion (insert one placeholder child, populate only when opened) keeps the UI snappy for huge directories.

Common Mistakes

ProblemCauseFix
App crashes on cancelfilenamefilename is """"if not filename: returnif not filename: return
UnicodeDecodeErrorUnicodeDecodeError on binary fileDefault encodingUse try/except UnicodeDecodeErrortry/except UnicodeDecodeError
Old content still shownForgot to clear the widgetoutput.delete("1.0", END)output.delete("1.0", END) first
Scrollbar does not moveDid not wire yscrollcommandyscrollcommandBidirectional binding both ways
Large file freezes UIReading 1 GB into a TextText widgetStream / paginate, or refuse over a threshold

Variations to Try

1. Recent files

Store the last 10 opened paths in a JSON file; show as a dropdown.

2. Syntax highlighting

Use Pygments to color Python/JS/HTML code:

pygments.py
from pygments import lex
from pygments.lexers import get_lexer_for_filename
for token, value in lex(data, get_lexer_for_filename(filename)):
    output.insert(END, value, str(token))
pygments.py
from pygments import lex
from pygments.lexers import get_lexer_for_filename
for token, value in lex(data, get_lexer_for_filename(filename)):
    output.insert(END, value, str(token))

Define tag colors with output.tag_configure("Token.Keyword", foreground="blue")output.tag_configure("Token.Keyword", foreground="blue").

3. Find / Replace

A small dialog with two EntryEntry widgets and Tkinter’s built-in TextText search:

find.py
idx = output.search(term, "1.0", stopindex=END)
if idx:
    output.tag_add("sel", idx, f"{idx}+{len(term)}c")
    output.see(idx)
find.py
idx = output.search(term, "1.0", stopindex=END)
if idx:
    output.tag_add("sel", idx, f"{idx}+{len(term)}c")
    output.see(idx)

4. Multiple tabs

Use ttk.Notebookttk.Notebook so each opened file lives in its own tab.

5. Image preview

For .png/.jpg.png/.jpg files, swap the TextText widget for a LabelLabel showing a PIL.ImageTk.PhotoImagePIL.ImageTk.PhotoImage.

6. CSV viewer

When opening a .csv.csv, parse and display in a ttk.Treeviewttk.Treeview table instead of plain text.

7. Markdown preview

On open, render Markdown to HTML with pip install markdownpip install markdown and show in a side panel (use tkhtmlviewtkhtmlview for an HTML widget).

8. Drag & drop

With tkinterdnd2tkinterdnd2, dropping a file onto the window opens it.

9. Recent edits tracker

Compare on-open content with on-save content and show diff stats (lines added/removed).

10. File operations

Right-click context menu: Copy, Move, Rename, Delete — wire to shutil.moveshutil.move, os.removeos.remove, etc. Confirm before destructive operations.

Architecture: Move to a Class

When the script grows past 100 lines, refactor:

oop.py
class FileExplorer:
    def __init__(self, root):
        self.root = root
        self.current_path = None
        self._build_ui()
 
    def _build_ui(self): ...
    def browse(self): ...
    def save(self): ...
oop.py
class FileExplorer:
    def __init__(self, root):
        self.root = root
        self.current_path = None
        self._build_ui()
 
    def _build_ui(self): ...
    def browse(self): ...
    def save(self): ...

Everything else stays the same — but state lives on selfself, not module globals, and you can test the class without launching a window.

Real-World Applications

  • In-product file viewers — log viewers, debug consoles.
  • Internal tools — quick text viewers for ops teams.
  • Educational software — viewing student submissions.
  • Configuration editors — load YAML/INI, validate on save.
  • Prototype IDEs — every editor started as a file viewer.

Educational Value

  • Tkinter widgets — buttons, labels, text, frames, scrollbars.
  • OS file dialogs — leveraging the native UI.
  • Encoding handling — what is at stake when the bytes are not ASCII.
  • Event-driven design — callbacks rather than top-down scripts.
  • Layout reasoninggridgrid vs packpack, stickysticky, weights.

Next Steps

  • Add scrollbars (most important quick win).
  • Make the buffer editable + saveable.
  • Add encoding detection with chardetchardet.
  • Add a directory tree with ttk.Treeviewttk.Treeview.
  • Layer syntax highlighting with Pygments.
  • Refactor into a class.
  • See Basic Text Editor for the next evolution — a full editor with undo/redo, find/replace, and a menu bar.

Conclusion

You built a working file viewer, fixed three real-world bugs (cancel, encoding, missing scrollbars), and have a road map for turning it into a real editor. Tkinter is unfashionable but its model — widgets, callbacks, the event loop — matches every other GUI framework you will meet. Full source on GitHub. Explore more GUI projects on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did