Sometimes you just need a simple, fast screenshot tool without installing large software.
In this tutorial, weβll build a desktop screen capture application using Python.
Features of our tool:
β’ πΈ Full screen capture
β’ πΌ Region capture (drag to select area)
β’ β± Delayed screenshots
β’ π Custom output folder
β’ π Progress indicator
β’ π§Ύ Processing log
The final tool looks and behaves like a mini professional desktop application.
You can also find the full project here:
GitHub repository
https://github.com/rogers-cyber/python-tiny-tools/tree/main/76-Screenshot%20tool
Step 1 β Install Required Libraries
Before we start, install the required Python packages.
pip install pillow ttkbootstrap
Libraries used in this project:
Library Purpose
tkinter GUI framework
ttkbootstrap modern UI theme
pillow screen capture
threading background tasks
Step 2 β Import Required Modules
Now letβs import the modules weβll use.
import os
import sys
import threading
import time
import traceback
from queue import Queue, Empty
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
from ttkbootstrap.constants import *
from PIL import ImageGrab
What these modules do
β’ threading β prevents the UI from freezing
β’ queue β safely communicate between threads
β’ ImageGrab β capture screenshots
β’ ttkbootstrap β modern dark UI theme
Step 3 β Application Configuration
Letβs define the application name and version.
APP_NAME = "Screenshot PRO"
APP_VERSION = "1.0.0"
These values will be used in the window title and About dialog.
Step 4 β Create the Main Window
Next we initialize the Tkinter application window.
app = tk.Tk()
app.title(f"{APP_NAME} {APP_VERSION}")
app.geometry("1050x620")
tb.Style("darkly")
What happens here
β’ Creates the main GUI window
β’ Sets window size
β’ Applies the Darkly theme from ttkbootstrap
This makes the interface look much more professional than default Tkinter.
Step 5 β Utility Functions
Letβs add some helper functions.
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
This function helps when packaging the app with PyInstaller.
Error Logger
def log_error():
with open("error.log", "a", encoding="utf-8") as f:
f.write(traceback.format_exc() + "\n")
If something crashes, it writes the error to error.log.
About Dialog
def show_about():
messagebox.showinfo(
f"About {APP_NAME}",
f"{APP_NAME} v{APP_VERSION}\n\n"
"Professional Screen Capture Tool\n\n"
"Features:\n"
"β’ Full screen capture\n"
"β’ Region capture\n"
"β’ Delayed screenshots\n"
"β’ Custom output folder\n"
"β’ Progress tracking\n"
"β’ Processing log\n"
)
This displays a simple About window.
Step 6 β Create the Application Menu
Next we add a top menu.
menubar = tb.Menu(app)
help_menu = tb.Menu(menubar, tearoff=0)
help_menu.add_command(label="About", command=show_about)
menubar.add_cascade(label="Help", menu=help_menu)
app.config(menu=menubar)
Now the app has a Help β About menu.
Step 7 β Application State Variables
We define variables used across the application.
stop_flag = False
pause_flag = False
ui_queue = Queue()
output_path = tb.StringVar(value=os.getcwd())
delay_seconds = tb.IntVar(value=0)
Why use StringVar and IntVar?
They automatically sync values with the GUI inputs.
Step 8 β Title Header
Now letβs create the title section.
tb.Label(
app,
text=APP_NAME,
font=("Segoe UI", 24, "bold")
).pack(pady=(10, 2))
tb.Label(
app,
text="Professional Screen Capture Tool",
font=("Segoe UI", 10, "italic"),
foreground="#9ca3af"
).pack(pady=(0, 10))
This creates the main application header.
Step 9 β Capture Controls Panel
We create a labeled frame for screenshot settings.
frame1 = tb.Labelframe(app, text="Capture Controls", padding=10)
frame1.pack(fill="x", padx=10, pady=6)
Delay Setting
tb.Label(frame1, text="Delay (seconds):").pack(side="left")
delay_entry = tb.Entry(frame1, textvariable=delay_seconds, width=6)
delay_entry.pack(side="left", padx=5)
This allows users to delay screenshots.
Example:
Delay = 5 seconds
Useful for capturing menus or popups.
Output Folder Selection
tb.Label(frame1, text="Output Folder:", width=13).pack(side="left", padx=(20, 0))
tb.Entry(frame1, textvariable=output_path, width=40).pack(side="left", padx=6)
Browse Folder Button
def browse_output():
folder = filedialog.askdirectory()
if folder:
output_path.set(folder)
tb.Button(frame1, text="Browse", command=browse_output).pack(side="left", padx=4)
Users can select where screenshots are saved.
Step 10 β Full Screen Capture
Now we create the function that captures the entire screen.
def capture_fullscreen():
try:
if delay_seconds.get() > 0:
ui_queue.put(("log", f"Waiting {delay_seconds.get()} seconds..."))
time.sleep(delay_seconds.get())
img = ImageGrab.grab()
file_name = f"screenshot_{int(time.time())}.png"
path = os.path.join(output_path.get(), file_name)
img.save(path)
ui_queue.put(("log", f"β Saved: {file_name}"))
except Exception:
log_error()
ui_queue.put(("log", "β Screenshot failed"))
ui_queue.put(("progress", 100))
What this function does
1οΈβ£ Waits if delay is set
2οΈβ£ Captures screen
3οΈβ£ Saves image
4οΈβ£ Updates UI log
Step 11 β Region Capture Tool
Now we create the drag-to-select screenshot feature.
def capture_region():
selector = tk.Toplevel()
selector.attributes("-fullscreen", True)
selector.attributes("-alpha", 0.3)
selector.configure(bg="black")
canvas = tk.Canvas(selector, cursor="cross", bg="black")
canvas.pack(fill="both", expand=True)
This creates a transparent overlay window.
Mouse Events
start_x = start_y = 0
rect = None
These store the coordinates of the selection.
Mouse Press
def on_press(event):
nonlocal start_x, start_y, rect
start_x = event.x
start_y = event.y
rect = canvas.create_rectangle(start_x, start_y, start_x, start_y, outline="red", width=2)
Start drawing the selection box.
Mouse Drag
def on_drag(event):
canvas.coords(rect, start_x, start_y, event.x, event.y)
Updates the rectangle size.
Mouse Release
def on_release(event):
x1 = min(start_x, event.x)
y1 = min(start_y, event.y)
x2 = max(start_x, event.x)
y2 = max(start_y, event.y)
selector.destroy()
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
This captures the selected area.
Step 12 β Start Screenshot Threads
To prevent the UI from freezing we use threads.
def start_fullscreen():
progress_var.set(0)
threading.Thread(target=capture_fullscreen, daemon=True).start()
Step 13 β Screenshot Buttons
tb.Button(
frame1,
text="πΈ Full Screen",
bootstyle="success",
command=start_fullscreen
).pack(side="left", padx=6)
tb.Button(
frame1,
text="πΌ Capture Region",
bootstyle="info",
command=start_region
).pack(side="left", padx=4)
Now users can choose:
β’ Full screen capture
β’ Region capture
Step 14 β Progress Bar
frame2 = tb.Labelframe(app, text="Progress", padding=8)
frame2.pack(fill="x", padx=10)
progress_var = tb.IntVar()
tb.Progressbar(
frame2,
variable=progress_var,
maximum=100,
length=400
).pack(side="left", padx=10)
This shows screenshot progress.
Step 15 β Processing Log
frame3 = tb.Labelframe(app, text="Processing Log", padding=8)
frame3.pack(fill="both", expand=True, padx=10, pady=6)
log_text = tk.Text(frame3, height=10)
log_text.pack(side="left", fill="both", expand=True)
The log shows:
β Screenshot saved
β Screenshot failed
Waiting 5 seconds...
Step 16 β UI Queue Processor
Background threads communicate with the UI using a queue.
def process_ui_queue():
try:
while True:
cmd, data = ui_queue.get_nowait()
if cmd == "progress":
progress_var.set(data)
elif cmd == "log":
log_text.config(state="normal")
log_text.insert("end", data + "\n")
log_text.see("end")
log_text.config(state="disabled")
except Empty:
pass
app.after(100, process_ui_queue)
This keeps the UI responsive and thread-safe.
Step 17 β Start the Application
Finally, we start the GUI.
app.after(100, process_ui_queue)
app.mainloop()
Final Result
You now have a fully functional desktop screenshot tool built with Python.
Capabilities:
β Full screen capture
β Region capture
β Delayed screenshots
β Modern UI
β Progress tracking
β Log output
Source Code
Full project:
https://github.com/rogers-cyber/python-tiny-tools/tree/main/76-Screenshot%20tool

Top comments (0)