Affichage des articles dont le libellé est tkinter. Afficher tous les articles
Affichage des articles dont le libellé est tkinter. Afficher tous les articles

Python Tkinter Collapsible Sidebar

How to Create a Collapsible Sidebar in Python Tkinter

How to Create a Collapsible Sidebar in Python Tkinter


In this Python Tutorial we will see How to Create a wooden chess board using Python's built-in Tkinter library.

What We Are Gonna Use In This Project:

- Python Programming Language.
- Tkinter (GUI).
- VS Editor.






Project Source Code:




import tkinter as tk
import time
import math


class ModernSidebar(tk.Frame):
"""
A modern, animated sidebar widget for tkinter applications.
"""
def __init__(self, parent):
# Initialize the parent Frame class
super().__init__(parent)
# ===== SIDEBAR DIMENSIONS =====
# These control how wide the sidebar is when expanded/collapsed
self.EXPANDED_WIDTH = 280 # Width when sidebar is open
self.COLLAPSED_WIDTH = 70 # Width when sidebar is closed
# ===== ANIMATION SETTINGS =====
# Controls how smooth the expand/collapse animation looks
self.ANIMATION_DURATION = 300 # Animation time in milliseconds
# ===== COLOR SCHEME =====
self.PRIMARY_COLOR = "#1f2937" # Main dark background
self.SECONDARY_COLOR = "#111827" # Slightly darker shade
self.ACCENT_COLOR = "#10b981" # Green accent color
self.HOVER_COLOR = "#374151" # Color when hovering over buttons
self.TEXT_COLOR = "#f3f4f6" # Light text color
self.CONTENT_BG = "#f9fafb" # Light background for main content
# ===== STATE VARIABLES =====
# These keep track of the sidebar's current state
self.is_collapsed = False # Is the sidebar currently collapsed?
self.current_width = self.EXPANDED_WIDTH # Current width of sidebar
self.animating = False # Is an animation currently running?
self.active_button = None # Which menu button is currently selected
# Set up the user interface
self.setup_ui()
def setup_ui(self):
"""
Creates all the visual elements of the sidebar.
This is like building the skeleton of our interface.
"""
# Configure the main frame (the container for everything)
self.configure(bg=self.PRIMARY_COLOR)
# ===== CREATE THE SIDEBAR CONTAINER =====
# This frame holds all the sidebar content (logo, menu, buttons)
self.sidebar = tk.Frame(
self,
bg=self.PRIMARY_COLOR,
width=self.EXPANDED_WIDTH,
highlightthickness=0 # Remove ugly border
)
self.sidebar.pack(side="left", fill="y")
self.sidebar.pack_propagate(False) # Don't let children resize this frame
# ===== CREATE THE LOGO SECTION =====
self.create_logo_section()
# ===== CREATE A SEPARATOR LINE =====
# This adds a subtle line between logo and menu
separator = tk.Frame(self.sidebar, height=1, bg="#374151")
separator.pack(fill="x", padx=15, pady=(0, 15))
# ===== CREATE THE MENU SECTION =====
self.create_menu_section()
# ===== CREATE THE COLLAPSE BUTTON =====
self.create_collapse_button()
# ===== CREATE THE MAIN CONTENT AREA =====
self.create_content_area()

def create_logo_section(self):
"""
Creates the logo area at the top of the sidebar.
Shows full text when expanded, initials when collapsed.
"""
# Container for logo elements
self.logo_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
self.logo_frame.pack(fill="x", padx=15, pady=(25, 20))
# Full logo text (shown when expanded)
self.logo_text = tk.Label(
self.logo_frame,
text="Modern Sidebar",
font=("Arial", 20, "bold"),
fg=self.ACCENT_COLOR,
bg=self.PRIMARY_COLOR
)
self.logo_text.pack(anchor="w") # Align to left (west)
# Short logo (shown when collapsed)
self.logo_icon = tk.Label(
self.logo_frame,
text="MS",
font=("Arial", 22, "bold"),
fg=self.ACCENT_COLOR,
bg=self.PRIMARY_COLOR
)
# Note: logo_icon is created but not packed yet - it will show when collapsed

def create_menu_section(self):
"""
Creates the menu buttons section of the sidebar.
Each button has an icon and text label.
"""
# Container for all menu buttons
self.menu_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
self.menu_frame.pack(fill="both", expand=True, padx=10, pady=5)
# ===== DEFINE MENU ITEMS =====
# Each item has: (display_text, icon_function, tooltip_text)
self.menu_items = [
("Dashboard", self.create_dashboard_icon, "View your dashboard"),
("Menu", self.create_menu_icon, "Manage menu items"),
("Orders", self.create_orders_icon, "Track orders"),
("Customers", self.create_customers_icon, "Customer management"),
("Analytics", self.create_analytics_icon, "View analytics"),
("Inventory", self.create_inventory_icon, "Manage inventory"),
("Marketing", self.create_marketing_icon, "Marketing campaigns"),
("Settings", self.create_settings_icon, "System settings")
]
# ===== CREATE MENU BUTTONS =====
# Store all buttons in a list so we can manage them later
self.menu_buttons = []
# Loop through each menu item and create a button for it
for i, (text, icon_func, tooltip) in enumerate(self.menu_items):
button = self.create_menu_button(text, icon_func, tooltip, i)
self.menu_buttons.append(button)
# Make the first button active by default
self.set_active_button(self.menu_buttons[0], 0)

def create_menu_button(self, text, icon_func, tooltip, index):
"""
Creates a single menu button with icon and text.
Args:
text: The text to display on the button
icon_func: Function that draws the icon
tooltip: Text to show when hovering (when collapsed)
index: Position of this button in the menu
"""
# ===== CREATE BUTTON CONTAINER =====
button_frame = tk.Frame(self.menu_frame, bg=self.PRIMARY_COLOR, height=52)
button_frame.pack(fill="x", pady=5)
button_frame.pack_propagate(False)
# ===== CREATE ACTIVE INDICATOR =====
# This colored bar shows which button is currently selected
indicator = tk.Frame(button_frame, width=4, bg=self.PRIMARY_COLOR)
indicator.pack(side="left", fill="y")
# ===== CREATE BUTTON CONTENT CONTAINER =====
content = tk.Frame(button_frame, bg=self.PRIMARY_COLOR)
content.pack(side="left", fill="both", expand=True, padx=8, pady=8)
# ===== CREATE ICON =====
# Canvas allows us to draw custom icons
icon_canvas = tk.Canvas(
content,
width=24,
height=28,
bg=self.PRIMARY_COLOR,
highlightthickness=0, # Remove border
bd=0 # Remove border
)
icon_canvas.pack(side="left", padx=(5, 10))
# Draw the icon using the provided function
icon_func(icon_canvas, self.TEXT_COLOR)
# ===== CREATE TEXT LABEL =====
text_label = tk.Label(
content,
text=text,
font=("Arial", 11),
fg=self.TEXT_COLOR,
bg=self.PRIMARY_COLOR,
anchor="w" # Align text to left
)
text_label.pack(side="left", fill="x", expand=True)
# ===== STORE REFERENCES =====
# Store important parts in the frame so we can access them later
button_frame.indicator = indicator
button_frame.icon_canvas = icon_canvas
button_frame.text_label = text_label
button_frame.content = content
button_frame.index = index
button_frame.icon_func = icon_func
# ===== BIND CLICK EVENTS =====
# Make all parts of the button clickable
for widget in [button_frame, content, icon_canvas, text_label]:
widget.bind("<Button-1>", lambda e, f=button_frame,
i=index: self.on_button_click(f, i))
widget.bind("<Enter>", lambda e, f=button_frame: self.on_button_hover(f))
widget.bind("<Leave>", lambda e, f=button_frame: self.on_button_leave(f))
pass
# Add tooltip for collapsed state
self.create_tooltip(button_frame, tooltip)
return button_frame
def create_tooltip(self, widget, text):
"""
Creates a tooltip that appears when hovering over buttons in collapsed mode.
"""
def show_tooltip(event):
# Only show tooltip when sidebar is collapsed
if self.is_collapsed:
# Calculate position for tooltip
x = widget.winfo_rootx() + 75
y = widget.winfo_rooty() + 20
# Create tooltip window
self.tooltip = tk.Toplevel(widget)
self.tooltip.wm_overrideredirect(True) # Remove window decorations
self.tooltip.wm_geometry(f"+{x}+{y}")
# Create tooltip content
tooltip_frame = tk.Frame(self.tooltip, bg=self.SECONDARY_COLOR)
tooltip_frame.pack()
label = tk.Label(
tooltip_frame,
text=text,
bg=self.SECONDARY_COLOR,
fg=self.TEXT_COLOR,
font=("Arial", 10)
)
label.pack(padx=10, pady=6)
def hide_tooltip(event):
# Remove tooltip if it exists
if hasattr(self, 'tooltip'):
self.tooltip.destroy()
# Bind hover events
widget.bind('<Enter>', show_tooltip)
widget.bind('<Leave>', hide_tooltip)




def create_collapse_button(self):
"""
Creates the button that collapses/expands the sidebar.
"""
# Container for the collapse button
bottom_frame = tk.Frame(self.sidebar, bg=self.PRIMARY_COLOR)
bottom_frame.pack(side="bottom", fill="x", pady=15)
# The actual collapse button
self.collapse_button = tk.Button(
bottom_frame,
text="◄", # Left arrow when expanded
font=("Arial", 12),
fg=self.TEXT_COLOR,
bg=self.SECONDARY_COLOR,
activebackground=self.ACCENT_COLOR, # Color when clicked
activeforeground="#ffffff",
bd=0, # No border
width=6,
height=2,
cursor="hand2", # Hand cursor on hover
relief="flat", # Flat appearance
command=self.toggle_sidebar # Function to call when clicked
)
self.collapse_button.pack(fill="x")
def toggle_sidebar(self):
"""
Expands or collapses the sidebar with smooth animation.
This is the main function that creates the sliding effect.
"""
# Don't start new animation if one is already running
if self.animating:
return
# Mark that animation is starting
self.animating = True
# Determine target width and calculate animation parameters
target_width = self.COLLAPSED_WIDTH \
if not self.is_collapsed \
else self.EXPANDED_WIDTH
start_width = self.current_width
width_difference = target_width - start_width
start_time = time.time()
# Switch logo display immediately
if not self.is_collapsed:
# Going to collapsed state - show icon only
self.logo_text.pack_forget()
self.logo_icon.pack(anchor="center")
else:
# Going to expanded state - show full text
self.logo_icon.pack_forget()
self.logo_text.pack(anchor="w")
def animate_step():
"""
This function runs repeatedly to create the animation effect.
It's called every 16 milliseconds.
"""
# Calculate how much time has passed
elapsed_time = time.time() - start_time
# Check if animation is complete
if elapsed_time >= self.ANIMATION_DURATION / 1000:
# Animation finished - set final state
self.update_sidebar_width(target_width)
self.is_collapsed = not self.is_collapsed
self.collapse_button.configure(text="►" if self.is_collapsed else "◄")
self.update_button_layout()
self.animating = False
return
# Calculate animation progress (0 to 1)
progress = elapsed_time / (self.ANIMATION_DURATION / 1000)
# Apply easing for smoother animation
eased_progress = self.ease_out_cubic(progress)
# Calculate new width and update sidebar
new_width = start_width + (width_difference * eased_progress)
self.update_sidebar_width(new_width)
# Schedule next animation step
self.after(16, animate_step)
# Start the animation
animate_step()

def ease_out_cubic(self, x):
"""
Easing function that makes animation start fast and slow down.
This creates a more natural, smooth animation effect.
"""
return 1 - math.pow(1 - x, 3)
def update_sidebar_width(self, width):
"""
Updates the actual width of the sidebar.
"""
self.current_width = width
self.sidebar.configure(width=int(width))
def update_button_layout(self):
"""
Updates the layout of menu buttons when sidebar is collapsed/expanded.
In collapsed mode, only icons are shown. In expanded mode, both icons and text.
"""
for button in self.menu_buttons:
if self.is_collapsed:
# Hide text and center the icon
button.text_label.pack_forget()
button.icon_canvas.pack_forget()
button.icon_canvas.pack(side="top", padx=0, pady=(10, 0),
anchor="center")
else:
# Show both icon and text
button.icon_canvas.pack_forget()
button.icon_canvas.pack(side="left", padx=(5, 10))
button.text_label.pack(side="left", fill="x", expand=True)

# Restore active button state
if self.active_button:
self.set_active_button(self.active_button, self.active_button.index)


def create_content_area(self):
"""
Creates the main content area next to the sidebar.
This is where the actual application content would go.
"""
# Main content container
self.content_frame = tk.Frame(self, bg=self.CONTENT_BG)
self.content_frame.pack(side="left", fill="both", expand=True)
# Sample header for the content area
header = tk.Frame(self.content_frame, bg=self.CONTENT_BG, height=70)
header.pack(fill="x", padx=20, pady=10)
header.pack_propagate(False)
# Welcome message
welcome_label = tk.Label(
header,
text="Welcome Back - Application Dashboard",
font=("Arial", 24, "bold"),
fg="#111827",
bg=self.CONTENT_BG
)
welcome_label.pack(anchor="w", pady=10)
# ===== ICON DRAWING FUNCTIONS =====
# These functions draw simple icons using canvas drawing methods
def create_dashboard_icon(self, canvas, color):
"""Draws a simple house/dashboard icon"""
# House base
canvas.create_rectangle(7, 14, 17, 24, outline=color, width=2)
# House roof
canvas.create_polygon(3, 14, 12, 5, 21, 14, fill="", outline=color, width=2)
def create_menu_icon(self, canvas, color):
"""Draws three horizontal lines (hamburger menu)"""
for y in [8, 14, 20]:
canvas.create_line(5, y, 19, y, fill=color, width=2)
def create_orders_icon(self, canvas, color):
"""Draws a clipboard with checkmarks"""
# Clipboard
canvas.create_rectangle(6, 2, 18, 22, outline=color, width=2)
# Clip at top
canvas.create_rectangle(10, 1, 14, 4, outline=color, width=2)
# Checkmarks
canvas.create_line(8, 8, 10, 10, fill=color, width=2)
canvas.create_line(10, 10, 14, 6, fill=color, width=2)
canvas.create_line(8, 14, 10, 16, fill=color, width=2)
canvas.create_line(10, 16, 14, 12, fill=color, width=2)
def create_customers_icon(self, canvas, color):
"""Draws a simple person icon"""
# Head (circle)
canvas.create_oval(8, 4, 16, 12, outline=color, width=2)
# Body (arc)
canvas.create_arc(6, 15, 18, 25, start=0, extent=180,
outline=color, width=2, style="arc")
# Connect head to body
canvas.create_line(12, 12, 12, 15, fill=color, width=2)
def create_analytics_icon(self, canvas, color):
"""Draws a simple chart/graph"""
# Axes
canvas.create_line(4, 20, 20, 20, fill=color, width=2) # X-axis
canvas.create_line(4, 4, 4, 20, fill=color, width=2) # Y-axis
# Chart line
points = [4, 16, 7, 12, 10, 16, 13, 8, 16, 11, 20, 4]
canvas.create_line(*points, fill=color, width=2, smooth=True)
def create_inventory_icon(self, canvas, color):
# Shelf frame
canvas.create_line(4, 4, 4, 20, fill=color, width=2) # Left support
canvas.create_line(20, 4, 20, 20, fill=color, width=2) # Right support
# Shelves
canvas.create_line(4, 8, 20, 8, fill=color, width=2)
canvas.create_line(4, 14, 20, 14, fill=color, width=2)
canvas.create_line(4, 20, 20, 20, fill=color, width=2)
# Items on shelves
canvas.create_rectangle(6, 5, 9, 8, fill=color, outline="")
canvas.create_rectangle(15, 5, 18, 8, fill=color, outline="")
canvas.create_rectangle(7, 11, 10, 14, fill=color, outline="")
canvas.create_rectangle(14, 11, 17, 14, fill=color, outline="")
def create_marketing_icon(self, canvas, color):
# Marketing icon with megaphone
# Megaphone body
canvas.create_polygon(4, 14, 4, 10, 12, 6, 12, 18, 4, 14,
fill="", outline=color, width=2)
# Handle
canvas.create_rectangle(2, 10, 4, 14, outline=color, width=2, fill="")
# Sound waves
canvas.create_arc(12, 8, 18, 16, start=270, extent=180, style="arc",
outline=color, width=1)
canvas.create_arc(14, 6, 20, 18, start=270, extent=180, style="arc",
outline=color, width=1)
canvas.create_arc(16, 4, 22, 20, start=270, extent=180, style="arc",
outline=color, width=1)
def create_settings_icon(self, canvas, color):
"""Draws a gear/settings icon"""
center_x, center_y = 12, 12
# Gear teeth
for angle in range(0, 360, 45):
rad = math.radians(angle)
inner_x = center_x + 6 * math.cos(rad)
inner_y = center_y + 6 * math.sin(rad)
outer_x = center_x + 9 * math.cos(rad)
outer_y = center_y + 9 * math.sin(rad)
canvas.create_line(inner_x, inner_y, outer_x, outer_y, fill=color, width=2)
# Center circles
canvas.create_oval(8, 8, 16, 16, outline=color, width=2)
canvas.create_oval(10, 10, 14, 14, fill=color)



def set_active_button(self, button_frame, index):
"""
Makes a button the active/selected button.
Updates colors and the indicator bar.
"""
# Reset previous active button
if self.active_button:
self.active_button.configure(bg=self.PRIMARY_COLOR)
self.active_button.content.configure(bg=self.PRIMARY_COLOR)
self.active_button.text_label.configure(bg=self.PRIMARY_COLOR,
fg=self.TEXT_COLOR)
self.active_button.indicator.configure(bg=self.PRIMARY_COLOR, width=0)
# Redraw previous active icon with normal color
self.active_button.icon_canvas.delete("all")
self.active_button.icon_func(self.active_button.icon_canvas,
self.TEXT_COLOR)
# Set new active button
button_frame.configure(bg=self.PRIMARY_COLOR)
button_frame.content.configure(bg=self.PRIMARY_COLOR)
button_frame.text_label.configure(bg=self.PRIMARY_COLOR, fg=self.ACCENT_COLOR)
button_frame.indicator.configure(bg=self.ACCENT_COLOR, width=4)
# Redraw icon with accent color
button_frame.icon_canvas.configure(bg=self.PRIMARY_COLOR)
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, self.ACCENT_COLOR)
# Update active button reference
self.active_button = button_frame


# ===== EVENT HANDLING FUNCTIONS =====
# These functions respond to user interactions
def on_button_click(self, button_frame, index):
"""
Called when a menu button is clicked.
Changes the active button and updates the display.
"""
self.set_active_button(button_frame, index)

def on_button_hover(self, button_frame):
"""
Called when mouse hovers over a button.
Changes the button's appearance to show it's interactive.
"""
# Don't change active button appearance
if button_frame == self.active_button:
return
# Change colors to show hover state
button_frame.configure(bg=self.HOVER_COLOR)
button_frame.content.configure(bg=self.HOVER_COLOR)
button_frame.icon_canvas.configure(bg=self.HOVER_COLOR)
button_frame.text_label.configure(bg=self.HOVER_COLOR, fg="#ffffff")
# Redraw icon with brighter color
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, "#ffffff")

def on_button_leave(self, button_frame):
"""
Called when mouse leaves a button.
Resets the button's appearance to normal.
"""
# Don't change active button appearance
if button_frame == self.active_button:
return
# Reset to normal colors
button_frame.configure(bg=self.PRIMARY_COLOR)
button_frame.content.configure(bg=self.PRIMARY_COLOR)
button_frame.icon_canvas.configure(bg=self.PRIMARY_COLOR)
button_frame.text_label.configure(bg=self.PRIMARY_COLOR, fg=self.TEXT_COLOR)
# Redraw icon with normal color
button_frame.icon_canvas.delete("all")
button_frame.icon_func(button_frame.icon_canvas, self.TEXT_COLOR)



class ModernApp(tk.Tk):
"""
Main application window that contains the sidebar.
This is the root window of our application.
"""
def __init__(self):
# Initialize the main window
super().__init__()
# ===== WINDOW CONFIGURATION =====
self.title("Modern Sidebar Demo")
self.geometry("1000x680")
self.configure(bg="#ffffff")
# ===== CREATE THE SIDEBAR =====
sidebar = ModernSidebar(self)
sidebar.pack(fill="both", expand=True)
# ===== CENTER THE WINDOW ON SCREEN =====
self.center_window()
def center_window(self):
"""
Centers the application window on the screen.
"""
# Update window to get actual size
self.update_idletasks()
# Get window dimensions
width = self.winfo_width()
height = self.winfo_height()
# Calculate center position
x = (self.winfo_screenwidth() // 2) - (width // 2)
y = (self.winfo_screenheight() // 2) - (height // 2)
# Set window position
self.geometry(f'+{x}+{y}')


# ===== MAIN PROGRAM =====
if __name__ == "__main__":
# Create and run the application
app = ModernApp()
app.mainloop() # Start the GUI event loop



The Final Result:

Python Tkinter Collapsible Sidebar 1

Python Tkinter Collapsible Sidebar 2

Python Tkinter Collapsible Sidebar 3









Python Tkinter Image Zoom

How To Create an Image Zooming and Pan App in Python Using Tkinter



How To Create an Image Zooming and Pan App in Python Using Tkinter




In this Python Tutorial we will see How to Create an image viewer with zoom, pan, and drag functionality Using Python and Tkinter library.

What We're Building?
Our image viewer will include:
- Image loading with file dialog.
- Zoom in/out functionality.
- Pan and drag.
- Fit-to-window.
- Keyboard shortcuts.
- Status bar with image information.

What We Are Gonna Use In This Project:

- Python Programming Language.
- Tkinter (GUI).
- VS Editor.






Project Source Code:




import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import os


class ImageZoomApp:
def __init__(self, root):
"""Initialize the image viewer application"""
# Set up the main window
self.root = root
self.root.title("Simple Image Zoom App")
self.root.geometry("1000x700") # Width x Height
self.root.configure(bg='#2c3e50') # Dark blue background
# Variables to store image data
self.original_image = None # The original image file
self.display_image = None # The image we show on screen
self.zoom_level = 1.0 # How zoomed in we are (1.0 = normal size)
# Variables for dragging the image around
self.drag_start_x = 0
self.drag_start_y = 0
self.image_x = 0 # Where the image is positioned
self.image_y = 0
# Create all the parts of our interface
self.create_buttons()
self.create_canvas()
self.create_status_bar()
self.setup_keyboard_shortcuts()



def create_buttons(self):
"""Create the button toolbar at the top"""
# Frame to hold all our buttons
button_frame = tk.Frame(self.root, bg='#34495e', height=60)
button_frame.pack(fill=tk.X, padx=10, pady=10)
button_frame.pack_propagate(False) # Keep the frame height fixed
# Open Image button
open_btn = tk.Button(
button_frame,
text="Open Image",
command=self.open_image,
bg='#3498db', # Blue background
fg='white', # White text
font=('Arial', 12, 'bold'),
padx=20,
pady=8,
cursor='hand2' # Hand cursor when hovering
)
open_btn.pack(side=tk.LEFT, padx=5, pady=10)

# Zoom In button
zoom_in_btn = tk.Button(
button_frame,
text="Zoom In (+)",
command=self.zoom_in,
bg='#27ae60', # Green background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
zoom_in_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Zoom Out button
zoom_out_btn = tk.Button(
button_frame,
text="Zoom Out (-)",
command=self.zoom_out,
bg='#e74c3c', # Red background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
zoom_out_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Reset Size button
reset_btn = tk.Button(
button_frame,
text="Reset Size",
command=self.reset_size,
bg='#9b59b6', # Purple background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
reset_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Fit to Window button
fit_btn = tk.Button(
button_frame,
text="Fit to Window",
command=self.fit_to_window,
bg='#f39c12', # Orange background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
fit_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Zoom level display
self.zoom_label = tk.Label(
button_frame,
text="100%",
bg='#34495e',
fg='#ecf0f1',
font=('Arial', 12, 'bold'),
padx=20
)
self.zoom_label.pack(side=tk.RIGHT, pady=10)
def create_canvas(self):
"""Create the canvas where we display the image"""
# Frame to hold the canvas and scrollbars
canvas_frame = tk.Frame(self.root, bg='#2c3e50')
canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# Create the canvas
self.canvas = tk.Canvas(
canvas_frame,
bg='#1a252f', # Dark background for the image area
highlightthickness=0, # No border
cursor='crosshair' # Crosshair cursor
)

# Create scrollbars for when image is larger than window
v_scrollbar = tk.Scrollbar(canvas_frame, orient=tk.VERTICAL,
command=self.canvas.yview)
h_scrollbar = tk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL,
command=self.canvas.xview)
# Connect scrollbars to canvas
self.canvas.configure(yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set)
# Position everything in the frame
v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Set up mouse interactions
self.setup_mouse_events()
def create_status_bar(self):
"""Create the status bar at the bottom"""
status_frame = tk.Frame(self.root, bg='#34495e', height=40)
status_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
status_frame.pack_propagate(False)
# Status text (left side)
self.status_text = tk.Label(
status_frame,
text="Ready - Click 'Open Image' to start",
bg='#34495e',
fg='#bdc3c7',
font=('Arial', 10),
anchor=tk.W # Align text to the left
)
self.status_text.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10, pady=10)
# Image info (right side)
self.image_info = tk.Label(
status_frame,
text="",
bg='#34495e',
fg='#3498db',
font=('Arial', 10, 'bold')
)
self.image_info.pack(side=tk.RIGHT, padx=10, pady=10)

def setup_mouse_events(self):
# Mouse dragging for moving the image around
self.canvas.bind("<Button-1>", self.start_drag) # Left click
self.canvas.bind("<B1-Motion>", self.drag_image) # Drag while holding left click
self.canvas.bind("<ButtonRelease-1>", self.end_drag) # Release left click
# Double-click to fit image to window
self.canvas.bind("<Double-Button-1>", lambda e: self.fit_to_window())


def setup_keyboard_shortcuts(self):
"""Set up keyboard shortcuts"""
# Ctrl+O to open image
self.root.bind("<Control-o>", lambda e: self.open_image())
# Plus key to zoom in
self.root.bind("<Control-plus>", lambda e: self.zoom_in())
# Minus key to zoom out
self.root.bind("<Control-minus>", lambda e: self.zoom_out())
# Ctrl+0 to reset size
self.root.bind("<Control-0>", lambda e: self.reset_size())





def open_image(self):
"""Open a file dialog to select an image"""
# Define what types of files we can open
file_types = [
("All Images", "*.png *.jpg *.jpeg *.gif *.bmp"),
("PNG files", "*.png"),
("JPEG files", "*.jpg *.jpeg"),
("GIF files", "*.gif"),
("BMP files", "*.bmp"),
("All files", "*.*")
]
# Show the file selection dialog
filename = filedialog.askopenfilename(
title="Choose an image file",
filetypes=file_types
)
# If user selected a file, try to load it
if filename:
try:
self.load_image(filename)
except Exception as e:
# Show error message if something goes wrong
messagebox.showerror("Error", f"Could not open image:\n{str(e)}")


def load_image(self, filename):
"""Load an image file and display it"""
try:
# Open the image file
self.original_image = Image.open(filename)

# Reset zoom to normal size
self.zoom_level = 1.0
# Center the image in the window
self.center_image()
# Update the display
self.update_display()

# Update status information
self.update_status(filename)

except Exception as e:
raise Exception(f"Failed to load image: {str(e)}")

def center_image(self):
"""Center the image in the canvas"""
# Wait a moment for the window to update its size
self.root.update_idletasks()
# Get the canvas size
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# Put the image in the center
if canvas_width > 1 and canvas_height > 1:
self.image_x = canvas_width // 2
self.image_y = canvas_height // 2

def update_display(self):
"""Update what we see on the screen"""
if not self.original_image:
return # Nothing to show if no image is loaded
try:
# Calculate the new size based on zoom level
new_width = int(self.original_image.width * self.zoom_level)
new_height = int(self.original_image.height * self.zoom_level)
# Make sure size is at least 1 pixel
new_width = max(1, new_width)
new_height = max(1, new_height)
# Resize the image for display
if self.zoom_level == 1.0:
# Use original image if no zoom
display_image = self.original_image
else:
# Resize the image
display_image = self.original_image.resize((new_width, new_height),
Image.LANCZOS)
# Convert to format Tkinter can display
self.display_image = ImageTk.PhotoImage(display_image)
# Clear the canvas and show the new image
self.canvas.delete("all")
self.canvas.create_image(
self.image_x, self.image_y,
anchor=tk.CENTER,
image=self.display_image
)
# Update the scroll area
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
# Update the zoom percentage display
zoom_percent = int(self.zoom_level * 100)
self.zoom_label.config(text=f"{zoom_percent}%")

except Exception as e:
self.status_text.config(text=f"Error displaying image: {str(e)}")

def update_status(self, filename):
"""Update the status bar with image information"""
if self.original_image:
# Get just the filename without the full path
basename = os.path.basename(filename)
# Get image dimensions
width = self.original_image.width
height = self.original_image.height
# Get image format
image_format = getattr(self.original_image, 'format', 'Unknown')
# Update status displays
self.status_text.config(text=f"Loaded: {basename}")
self.image_info.config(text=f"{width} × {height}{image_format}")


def zoom_in(self):
"""Make the image bigger"""
if self.original_image and self.zoom_level < 10.0: # Max zoom: 1000%
self.zoom_level *= 1.2 # Increase by 20%
self.update_display()
def zoom_out(self):
"""Make the image smaller"""
if self.original_image and self.zoom_level > 0.1: # Min zoom: 10%
self.zoom_level /= 1.2 # Decrease by 20%
self.update_display()
def reset_size(self):
"""Reset image to its original size (100%)"""
if self.original_image:
self.zoom_level = 1.0
self.center_image()
self.update_display()
def fit_to_window(self):
"""Resize image to fit in the window"""
if not self.original_image:
return
# Make sure window size is updated
self.root.update_idletasks()
# Get canvas size
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# Only proceed if canvas has valid size
if canvas_width > 50 and canvas_height > 50:
# Calculate how much we need to zoom to fit
zoom_x = (canvas_width - 20) / self.original_image.width #Leave 20px margin
zoom_y = (canvas_height - 20) / self.original_image.height
# Use the smaller zoom so image fits completely
self.zoom_level = min(zoom_x, zoom_y, 1.0) # Don't zoom larger than 100%
# Center the image
self.center_image()
self.update_display()

def start_drag(self, event):
"""Start dragging the image"""
self.drag_start_x = event.x
self.drag_start_y = event.y
self.canvas.configure(cursor='fleur') # Four-arrow cursor
def drag_image(self, event):
"""Move the image while dragging"""
if self.original_image:
# Calculate how far the mouse moved
dx = event.x - self.drag_start_x
dy = event.y - self.drag_start_y
# Move the image by that amount
self.image_x += dx
self.image_y += dy
# Update starting position for next movement
self.drag_start_x = event.x
self.drag_start_y = event.y
# Update the display
self.update_display()
def end_drag(self, event):
"""Stop dragging the image"""
self.canvas.configure(cursor='crosshair') # Back to normal cursor



def main():
"""Create and run the image viewer application"""
# Create the main window
root = tk.Tk()
# Create our image viewer
app = ImageZoomApp(root)
# Start the application
root.mainloop()

# Run the program
if __name__ == "__main__":
main()



The Final Result: