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
filedialogfiledialogintegrates with the native OS picker. - How to read text safely with the correct encoding.
- How to wire scrollbars to a
TextTextwidget. - The
gridgridvspackpackdecision 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:
sudo apt-get install python3-tk # Debian/Ubuntu
sudo dnf install python3-tkinter # Fedorasudo apt-get install python3-tk # Debian/Ubuntu
sudo dnf install python3-tkinter # FedoraGetting Started
Create the project
- Create folder
file-explorerfile-explorer. - Inside, create
fileexplorer.pyfileexplorer.py.
Write the code
File Explorer
Source# 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()# 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
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 bottomC:\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 bottomClick Browse Files, pick any text file, and its content appears in the panel.
Step-by-Step Explanation
1. Imports and root window
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")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,ButtonButtondirectly is convenient — but only inside small scripts. For larger projects usetk.Texttk.Text,tk.Labeltk.Label, etc.
2. The browse callback
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)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.
askopenfilenameaskopenfilenamereturns""""when the user closes the dialog; we bail early. with open(...)with open(...)closes the file even on exception.UnicodeDecodeErrorUnicodeDecodeErrorhandler. Lets the program survive binary or weird-encoding files.
3. The widgets
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")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
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()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:
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")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:
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()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:
pip install chardetpip install chardetimport chardet
with open(filename, "rb") as f:
raw = f.read()
enc = chardet.detect(raw)["encoding"] or "utf-8"
data = raw.decode(enc, errors="replace")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:
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("", "/")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
| Problem | Cause | Fix |
|---|---|---|
| App crashes on cancel | filenamefilename is """" | if not filename: returnif not filename: return |
UnicodeDecodeErrorUnicodeDecodeError on binary file | Default encoding | Use try/except UnicodeDecodeErrortry/except UnicodeDecodeError |
| Old content still shown | Forgot to clear the widget | output.delete("1.0", END)output.delete("1.0", END) first |
| Scrollbar does not move | Did not wire yscrollcommandyscrollcommand | Bidirectional binding both ways |
| Large file freezes UI | Reading 1 GB into a TextText widget | Stream / 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:
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))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:
idx = output.search(term, "1.0", stopindex=END)
if idx:
output.tag_add("sel", idx, f"{idx}+{len(term)}c")
output.see(idx)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:
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): ...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 reasoning —
gridgridvspackpack,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 coffeeWas this page helpful?
Let us know how we did
