In this tutorial, we'll build a professional desktop screenshot tool using Python.
By the end, you'll have an app that can:
📸 Capture the full screen
🖥 Capture the active window
🎯 Select a custom screen region
🖼 Show a live preview
📋 Copy screenshots to clipboard
🗂 Automatically name and save files
📊 Show a processing log
Source code:
https://github.com/rogers-cyber/python-tiny-tools/tree/main/76-Screenshot%20tool
Final Result
Our application will look like a professional desktop tool with:
Screenshot controls
Preview panel
Processing log
Progress bar
Step 1 — Install Required Libraries
Before starting, install the dependencies:
pip install ttkbootstrap pillow pygetwindow pywin32
These libraries provide:
Library Purpose
tkinter GUI framework
ttkbootstrap Modern UI styling
Pillow Image processing
pygetwindow Access active window
pywin32 Clipboard support
Step 2 — Import Required Modules
Now let's import the modules we need.
import os
import sys
import threading
import time
import traceback
from datetime import datetime
from queue import Queue, Empty
These modules help with:
file management
threading
logging errors
queue communication
Next we import the GUI libraries.
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
from ttkbootstrap.constants import *
And finally the screenshot tools.
from PIL import ImageGrab, Image, ImageTk
import pygetwindow as gw
Step 3 — Application Configuration
We define the application name and version.
APP_NAME = "Screenshot PRO"
APP_VERSION = "2.0.0"
Step 4 — Create the Main Application Window
Now we initialize the Tkinter application.
app = tk.Tk()
app.title(f"{APP_NAME} {APP_VERSION}")
app.geometry("1200x700")
We also apply a modern ttkbootstrap theme.
tb.Style("darkly")
This gives the application a dark professional UI.
Step 5 — Utility Functions
Resource Path
Helps load files when the app is packaged into an executable.
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)
Error Logger
Any unexpected errors are written to error.log.
def log_error():
with open("error.log", "a", encoding="utf-8") as f:
f.write(traceback.format_exc() + "\n")
About Window
Displays application information.
def show_about():
messagebox.showinfo(
f"About {APP_NAME}",
f"{APP_NAME} v{APP_VERSION}\n\n"
"Advanced Screen Capture Tool\n\n"
"Features:\n"
"• Full screen capture\n"
"• Window capture\n"
"• Smart region selector\n"
"• Screenshot preview panel\n"
"• Clipboard copy\n"
"• Auto file naming\n"
"• Processing log\n\n"
"Built with Python + Tkinter + ttkbootstrap\n"
)
Step 6 — Create the Menu Bar
We add a simple Help → About 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)
Step 7 — Application State Variables
We create shared variables used throughout the application.
ui_queue = Queue()
output_path = tb.StringVar(value=os.getcwd())
file_prefix = tb.StringVar(value="screenshot")
last_image = None
Explanation:
Variable Purpose
ui_queue communication between threads
output_path folder where screenshots are saved
file_prefix screenshot file naming
last_image latest captured screenshot
Step 8 — Application Title
Let's add a clean application header.
tb.Label(
app,
text=APP_NAME,
font=("Segoe UI", 24, "bold")
).pack(pady=(10, 2))
Subtitle:
tb.Label(
app,
text="Professional Screen Capture Tool",
font=("Segoe UI", 10, "italic"),
foreground="#9ca3af"
).pack(pady=(0, 10))
Step 9 — Capture Settings Panel
Now we build the settings panel.
frame1 = tb.Labelframe(app, text="Capture Settings", padding=10)
frame1.pack(fill="x", padx=10, pady=6)
File Prefix Input
tb.Label(frame1, text="File Prefix:").pack(side="left")
tb.Entry(frame1, textvariable=file_prefix, width=20).pack(side="left", padx=6)
Output Folder
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 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)
Step 10 — Automatic File Naming
Every screenshot will automatically receive a timestamped name.
def generate_filename():
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
return f"{file_prefix.get()}_{ts}.png"
Example output:
screenshot_20260311_194550.png
Step 11 — Saving the Screenshot
Now we create the function that saves the image.
def save_image(img):
global last_image
try:
filename = generate_filename()
path = os.path.join(output_path.get(), filename)
img.save(path)
last_image = img
ui_queue.put(("preview", img))
ui_queue.put(("log", f"✔ Saved: {filename}"))
except:
log_error()
ui_queue.put(("log", "❌ Save failed"))
This function:
Generates a filename
Saves the screenshot
Updates the preview panel
Writes to the log
Step 12 — Full Screen Capture
The simplest capture mode.
def capture_fullscreen():
try:
img = ImageGrab.grab()
save_image(img)
except:
log_error()
ui_queue.put(("progress", 100))
ImageGrab.grab() captures the entire screen.
Step 13 — Active Window Capture
This captures only the currently focused window.
def capture_window():
try:
win = gw.getActiveWindow()
if not win:
ui_queue.put(("log","❌ No active window"))
return
bbox = (win.left, win.top, win.right, win.bottom)
img = ImageGrab.grab(bbox)
save_image(img)
except:
log_error()
ui_queue.put(("progress",100))
We use pygetwindow to get the window position.
Step 14 — Region Selection Tool
This feature allows users to draw a rectangle to capture a custom area.
def capture_region():
selector = tk.Toplevel()
selector.attributes("-fullscreen", True)
selector.attributes("-alpha", 0.25)
selector.configure(bg="black")
canvas = tk.Canvas(selector, cursor="cross")
canvas.pack(fill="both", expand=True)
The window becomes a transparent overlay.
Users drag to draw a capture box.
start_x = start_y = 0
rect = None
Mouse press:
def press(e):
nonlocal start_x, start_y, rect
start_x = e.x
start_y = e.y
rect = canvas.create_rectangle(start_x,start_y,start_x,start_y,outline="red",width=2)
Mouse drag:
def drag(e):
canvas.coords(rect,start_x,start_y,e.x,e.y)
Mouse release:
def release(e):
x1 = min(start_x,e.x)
y1 = min(start_y,e.y)
x2 = max(start_x,e.x)
y2 = max(start_y,e.y)
selector.destroy()
try:
img = ImageGrab.grab(bbox=(x1,y1,x2,y2))
save_image(img)
except:
log_error()
ui_queue.put(("progress",100))
Bind mouse events:
canvas.bind("<ButtonPress-1>",press)
canvas.bind("<B1-Motion>",drag)
canvas.bind("<ButtonRelease-1>",release)
Step 15 — Copy Screenshot to Clipboard
This feature copies the latest screenshot directly to the Windows clipboard.
def copy_clipboard():
global last_image
if not last_image:
messagebox.showerror("Error","No screenshot yet")
return
Clipboard conversion:
try:
import win32clipboard
from io import BytesIO
output = BytesIO()
last_image.convert("RGB").save(output,"BMP")
data = output.getvalue()[14:]
output.close()
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB,data)
win32clipboard.CloseClipboard()
ui_queue.put(("log","📋 Copied to clipboard"))
except:
ui_queue.put(("log","❌ Clipboard copy failed"))
Step 16 — Capture Buttons
We now add the control buttons.
frame_buttons = tb.Frame(app)
frame_buttons.pack(fill="x", padx=10, pady=6)
Full screen button:
tb.Button(
frame_buttons,
text="📸 Full Screen",
bootstyle="success",
command=lambda: threading.Thread(
target=capture_fullscreen,
daemon=True
).start()
).pack(side="left",padx=4)
Window capture:
tb.Button(
frame_buttons,
text="🖥 Window Capture",
bootstyle="info",
command=lambda: threading.Thread(
target=capture_window,
daemon=True
).start()
).pack(side="left",padx=4)
Region capture:
tb.Button(
frame_buttons,
text="🎯 Region Capture",
bootstyle="warning",
command=capture_region
).pack(side="left",padx=4)
Clipboard copy:
tb.Button(
frame_buttons,
text="📋 Copy Clipboard",
bootstyle="secondary",
command=copy_clipboard
).pack(side="left",padx=4)
Step 17 — Preview Panel
We create a panel to display the latest screenshot.
preview_frame = tb.Labelframe(main_frame,text="Preview",padding=8)
preview_frame.pack(side="left",fill="both",expand=True,padx=6)
preview_label = tk.Label(preview_frame)
preview_label.pack(expand=True)
Step 18 — Processing Log
A log panel shows application activity.
log_frame = tb.Labelframe(main_frame,text="Processing Log",padding=8)
log_frame.pack(side="right",fill="both",expand=True,padx=6)
Text log:
log_text = tk.Text(log_frame,height=10)
log_text.pack(side="left",fill="both",expand=True)
Scrollbar:
scroll = tk.Scrollbar(log_frame,command=log_text.yview)
scroll.pack(side="right",fill="y")
log_text.config(yscrollcommand=scroll.set,state="disabled")
Step 19 — UI Queue Processing
Threads send UI updates through a queue.
def process_ui_queue():
try:
while True:
cmd,data = ui_queue.get_nowait()
Handle progress updates:
if cmd=="progress":
progress_var.set(data)
Handle log messages:
elif cmd=="log":
log_text.config(state="normal")
log_text.insert("end",data+"\n")
log_text.see("end")
log_text.config(state="disabled")
Handle preview images:
elif cmd=="preview":
img = data.copy()
img.thumbnail((550,400))
tk_img = ImageTk.PhotoImage(img)
preview_label.config(image=tk_img)
preview_label.image = tk_img
Repeat every 100ms:
except Empty:
pass
app.after(100,process_ui_queue)
Step 20 — Start the Application
Finally we start the UI loop.
app.after(100,process_ui_queue)
app.mainloop()
Conclusion
You just built a professional screenshot application in Python.
Key features include:
📸 Full screen capture
🖥 Active window capture
🎯 Region selector
🖼 Live preview panel
📋 Clipboard support
📊 Processing log
Complete source code:
https://github.com/rogers-cyber/python-tiny-tools/tree/main/76-Screenshot%20tool

Top comments (0)