---
The Universal Loading Dialog is a professional, customizable loading screen component designed for Tkinter applications. It provides a modern, animated interface for displaying loading progress and status messages to users during application startup or long-running operations.
---
---
The Universal Loading Dialog is part of the Bravura Framework. It's located at:
Bravura/src/bravura/components/universal_loading_dialog.py
# Required (standard library)
import tkinter as tk
from tkinter import ttk
import threading
import time
import logging
# Optional (for music support)
from pygame import mixer # Only if ENABLE_MUSIC = True
from bravura.components.universal_loading_dialog import (
UniversalLoadingDialog,
simulate_loading_with_steps
)
---
from bravura.components.universal_loading_dialog import UniversalLoadingDialog
import time
# Create and show the loading dialog
loading = UniversalLoadingDialog()
loading.show()
# Update progress in your code
loading.update_progress(50, "Loading modules...")
time.sleep(1)
loading.update_progress(100, "Complete!")
time.sleep(0.5)
# Close the dialog
loading.close()
from bravura.components.universal_loading_dialog import (
UniversalLoadingDialog,
simulate_loading_with_steps
)
# Create loading dialog
loading = UniversalLoadingDialog()
loading.show()
# Define loading steps
steps = [
(20, "Initializing...", 0.5),
(50, "Loading data...", 0.8),
(80, "Finalizing...", 0.5),
(100, "Complete!", 0.3)
]
# Run simulation (auto-closes when done)
simulate_loading_with_steps(loading, steps, auto_close=True)
# Run the dialog (blocks until closed)
loading.run()
---
# Default theme (Professional Dark)
loading = UniversalLoadingDialog()
# With custom theme
custom_theme = {
'bg_color': '#1a1a1a',
'fg_color': '#ffffff',
'progress_color': '#4a9eff',
'font_family': 'Segoe UI',
'font_size': 11
}
loading = UniversalLoadingDialog(theme=custom_theme)
# With custom logo
loading = UniversalLoadingDialog(logo_path="path/to/logo.png")
# Show the dialog (non-blocking, creates window)
loading.show()
# Run the dialog's event loop (blocking until closed)
loading.run()
# Update progress and status message
loading.update_progress(progress=45, status="Loading configuration...")
# Progress: 0-100 (integer or float)
# Status: string message to display
# Closes window and cleans up resources
loading.close()
# Check if dialog is still running
if loading.is_running():
print("Dialog is active")
---
When using the Universal Loading Dialog before launching a main Tkinter application, you must use the Bridge Tk Instance Pattern to properly clear TTK's internal state. Failing to do this will result in theme corruption and styling issues.
TTK maintains global state that can become corrupted when one Tk() instance is destroyed and another is created immediately after. The Bridge Tk Instance Pattern solves this by inserting a temporary Tk() instance between the loading dialog and your main application.
Tk() instance doesn't immediately clear TTK stateTk() instance too quickly causes style corruption
import tkinter as tk
import time
from bravura.components.universal_loading_dialog import (
UniversalLoadingDialog,
simulate_loading_with_steps
)
class ApplicationLauncher:
"""Properly launches application with loading dialog."""
def __init__(self):
self.loading_dialog = None
def start(self):
"""Start the loading sequence."""
# Create loading dialog
self.loading_dialog = UniversalLoadingDialog()
self.loading_dialog.show()
# Define loading steps
steps = [
(10, "Initializing...", 0.3),
(30, "Loading modules...", 0.5),
(60, "Setting up interface...", 0.7),
(90, "Finalizing...", 0.4),
(100, "Complete!", 0.3)
]
# Start loading simulation
simulate_loading_with_steps(self.loading_dialog, steps, auto_close=True)
# Run loading dialog (blocks here)
self.loading_dialog.run()
# CRITICAL: Use bridge Tk instance pattern
time.sleep(0.3) # Brief pause after dialog closes
# Create temporary bridge Tk instance
temp_root = tk.Tk()
temp_root.withdraw() # Hide it
# Schedule main app launch after TTK cleanup delay
temp_root.after(500, lambda: self._delayed_main_launch(temp_root))
# Run bridge instance mainloop
temp_root.mainloop()
def _delayed_main_launch(self, temp_root):
"""Launch main application after bridge cleanup."""
try:
# Destroy the bridge Tk instance
temp_root.destroy()
# Now launch main application with clean TTK state
self.launch_main_application()
except Exception as e:
print(f"Error in delayed launch: {e}")
def launch_main_application(self):
"""Launch your main Tkinter application."""
# Create your main application
root = tk.Tk()
root.title("My Application")
root.geometry("800x600")
# Your application code here...
root.mainloop()
# Usage
if __name__ == "__main__":
launcher = ApplicationLauncher()
launcher.start()
# After loading dialog closes
loading.run() # Blocks until dialog closes
# STEP 1: Brief pause (0.2-0.5 seconds)
time.sleep(0.3)
# STEP 2: Create bridge Tk instance
temp_root = tk.Tk()
temp_root.withdraw()
# STEP 3: Schedule main launch (400-600ms delay)
temp_root.after(500, lambda: launch_callback(temp_root))
# STEP 4: Run bridge mainloop
temp_root.mainloop()
# In launch_callback:
# - Destroy temp_root
# - Launch main application
โ WRONG: Direct Launch (causes theme corruption)
# DON'T DO THIS!
loading.run()
launch_main_application() # Themes will be corrupted
โ WRONG: Delayed Launch Without Bridge
# DON'T DO THIS!
loading.run()
time.sleep(1)
launch_main_application() # Still causes issues
โ CORRECT: Bridge Tk Instance Pattern
# DO THIS!
loading.run()
time.sleep(0.3)
temp_root = tk.Tk()
temp_root.withdraw()
temp_root.after(500, lambda: delayed_launch(temp_root))
temp_root.mainloop()
---
theme = {
# Background colors
'bg_color': '#2b2b2b', # Main window background
'secondary_bg': '#1e1e1e', # Secondary elements
# Foreground colors
'fg_color': '#e0e0e0', # Text color
'accent_color': '#4a9eff', # Accent elements
# Progress bar
'progress_color': '#4a9eff', # Progress bar fill
'progress_bg': '#3a3a3a', # Progress bar background
# Typography
'font_family': 'Segoe UI', # Font name
'font_size': 11, # Base font size (int)
'font_weight': 'normal', # Font weight
# Dimensions
'window_width': 500, # Window width (pixels)
'window_height': 400, # Window height (pixels)
'logo_size': 150, # Logo size (pixels)
'border_radius': 10 # Corner radius (if supported)
}
PROFESSIONAL_DARK_THEME = {
'bg_color': '#2b2b2b',
'secondary_bg': '#1e1e1e',
'fg_color': '#e0e0e0',
'accent_color': '#4a9eff',
'progress_color': '#4a9eff',
'progress_bg': '#3a3a3a',
'font_family': 'Segoe UI',
'font_size': 11,
'window_width': 500,
'window_height': 400
}
loading = UniversalLoadingDialog() # Uses this by default
DEMO_PINK_THEME = {
'bg_color': '#ffe6f0',
'secondary_bg': '#ffd6e8',
'fg_color': '#800040',
'accent_color': '#ff1493',
'progress_color': '#ff69b4',
'progress_bg': '#ffb6d9',
'font_family': 'Arial',
'font_size': 10,
'window_width': 450,
'window_height': 380
}
from bravura.components.universal_loading_dialog import DEMO_PINK_THEME
loading = UniversalLoadingDialog(theme=DEMO_PINK_THEME)
# Corporate theme example
CORPORATE_THEME = {
'bg_color': '#f5f5f5',
'secondary_bg': '#e8e8e8',
'fg_color': '#333333',
'accent_color': '#0066cc',
'progress_color': '#0066cc',
'progress_bg': '#d0d0d0',
'font_family': 'Arial',
'font_size': 10,
'window_width': 600,
'window_height': 350
}
# Gaming theme example
GAMING_THEME = {
'bg_color': '#0a0e27',
'secondary_bg': '#1a1f3a',
'fg_color': '#00ff88',
'accent_color': '#ff00ff',
'progress_color': '#00ff88',
'progress_bg': '#1a1f3a',
'font_family': 'Consolas',
'font_size': 12,
'window_width': 550,
'window_height': 450
}
---
UniversalLoadingDialog(
logo_path: str = None,
theme: dict = None,
enable_music: bool = ENABLE_MUSIC,
music_path: str = None
)
Parameters:
logo_path (str, optional): Path to logo image file (PNG recommended)theme (dict, optional): Theme dictionary (uses PROFESSIONAL_DARK_THEME if None)enable_music (bool, optional): Enable background musicmusic_path (str, optional): Path to music file (MP3, WAV, OGG)Returns: UniversalLoadingDialog instance
def show() -> None
Creates and displays the loading dialog window. Non-blocking.
Usage:
loading.show()
---
def run() -> None
Starts the Tkinter main event loop. Blocks until dialog is closed.
Usage:
loading.show()
# ... setup code ...
loading.run() # Blocks here until close() is called
---
def update_progress(progress: int | float, status: str = "") -> None
Updates the progress bar and status message. Thread-safe.
Parameters:
progress (int | float): Progress value from 0 to 100status (str, optional): Status message to displayUsage:
loading.update_progress(45, "Loading configuration...")
loading.update_progress(100) # Just update progress
---
def close() -> None
Closes the loading dialog and cleans up resources. Stops animations and music.
Usage:
loading.close()
---
def is_running() -> bool
Checks if the dialog is currently active.
Returns: True if dialog is running, False otherwise
Usage:
if loading.is_running():
loading.update_progress(50, "Still loading...")
---
def simulate_loading_with_steps(
loading_dialog: UniversalLoadingDialog,
steps: list[tuple[int, str, float]],
auto_close: bool = True
) -> threading.Thread
Simulates loading progress with predefined steps. Runs in a background thread.
Parameters:
loading_dialog: UniversalLoadingDialog instancesteps: List of tuples (progress, status_message, delay_seconds)auto_close: Automatically close dialog at 100% (default: True)Returns: Thread object running the simulation
Usage:
steps = [
(10, "Starting...", 0.5),
(50, "Loading...", 1.0),
(100, "Complete!", 0.3)
]
simulate_loading_with_steps(loading, steps, auto_close=True)
loading.run()
---
When transitioning from a loading dialog to a main application:
# โ
ALWAYS use this pattern
loading.run()
time.sleep(0.3)
temp_root = tk.Tk()
temp_root.withdraw()
temp_root.after(500, lambda: launch_callback(temp_root))
temp_root.mainloop()
# โ
Good
loading.update_progress(30, "Loading user preferences...")
# โ Avoid
loading.update_progress(30, "Loading...")
# โ
Break down long operations
loading.update_progress(10, "Connecting to database...")
time.sleep(0.5)
loading.update_progress(30, "Loading user data...")
time.sleep(0.8)
loading.update_progress(60, "Initializing interface...")
try:
loading.show()
# ... loading operations ...
loading.close()
except Exception as e:
if loading.is_running():
loading.close()
raise
# โ
Auto-close prevents hanging dialogs
simulate_loading_with_steps(loading, steps, auto_close=True)
# โ
Always close the dialog
try:
loading.run()
finally:
if loading.is_running():
loading.close()
# โ
Test custom themes thoroughly
custom_theme = {...}
loading = UniversalLoadingDialog(theme=custom_theme)
# Verify all colors are readable and visually appealing
# โ
Don't update too frequently
time.sleep(0.1) # Minimum delay between updates
# โ Avoid rapid updates (causes flickering)
for i in range(100):
loading.update_progress(i, f"Step {i}") # Too fast!
---
Problem: Main application has wrong colors or missing themes after loading dialog.
Solution: Use the Bridge Tk Instance Pattern (see Advanced Integration)
# After loading dialog
time.sleep(0.3)
temp_root = tk.Tk()
temp_root.withdraw()
temp_root.after(500, lambda: delayed_launch(temp_root))
temp_root.mainloop()
---
Problem: Dialog window stays open indefinitely.
Solution: Ensure auto_close=True in simulations and explicitly call close():
simulate_loading_with_steps(loading, steps, auto_close=True)
loading.run()
---
Problem: Errors about destroyed Tk instance after closing dialog.
Solution:
close()
loading.close()
# DON'T do this after close():
# loading.update_progress(...) # โ Error!
---
Problem: Background music doesn't play.
Solution:
pip install pygameENABLE_MUSIC = True in the module
loading = UniversalLoadingDialog(
enable_music=True,
music_path="path/to/music.mp3"
)
---
Problem: Custom logo doesn't appear.
Solution:
import os
logo_path = "assets/logo.png"
if os.path.exists(logo_path):
loading = UniversalLoadingDialog(logo_path=logo_path)
---
Problem: Dialog appears in wrong position or off-screen.
Solution: The dialog auto-centers. If issues persist:
# Manually adjust in theme
theme = {
'window_width': 500,
'window_height': 400,
# ... other settings
}
---
Problem: Progress bar appears frozen.
Solution:
update_progress() correctlyis_running()
if loading.is_running():
loading.update_progress(50, "Loading...")
---
from bravura.components.universal_loading_dialog import (
UniversalLoadingDialog,
simulate_loading_with_steps
)
import tkinter as tk
import time
def main():
# Create loading dialog
loading = UniversalLoadingDialog()
loading.show()
# Simulate loading
steps = [
(25, "Initializing application...", 0.5),
(75, "Loading resources...", 0.8),
(100, "Ready!", 0.3)
]
simulate_loading_with_steps(loading, steps, auto_close=True)
loading.run()
# Bridge pattern
time.sleep(0.3)
temp_root = tk.Tk()
temp_root.withdraw()
temp_root.after(500, lambda: launch_app(temp_root))
temp_root.mainloop()
def launch_app(temp_root):
temp_root.destroy()
# Main application
root = tk.Tk()
root.title("My App")
root.geometry("600x400")
tk.Label(root, text="Application Loaded!", font=("Arial", 20)).pack(pady=100)
root.mainloop()
if __name__ == "__main__":
main()
---
from bravura.components.universal_loading_dialog import UniversalLoadingDialog
# Custom theme
MY_THEME = {
'bg_color': '#1a1a2e',
'secondary_bg': '#16213e',
'fg_color': '#eef2f3',
'accent_color': '#0f3460',
'progress_color': '#e94560',
'progress_bg': '#533483',
'font_family': 'Helvetica',
'font_size': 12,
'window_width': 550,
'window_height': 450
}
# Create with custom theme and logo
loading = UniversalLoadingDialog(
logo_path="assets/my_logo.png",
theme=MY_THEME
)
loading.show()
# Manual progress updates
for i in range(0, 101, 10):
loading.update_progress(i, f"Loading step {i}%...")
time.sleep(0.3)
loading.close()
---
import tkinter as tk
import time
from bravura.components.universal_loading_dialog import UniversalLoadingDialog
class DataLoaderApp:
def __init__(self):
self.loading = None
self.data = None
def start(self):
# Show loading dialog
self.loading = UniversalLoadingDialog()
self.loading.show()
# Start loading in background thread
import threading
thread = threading.Thread(target=self.load_data, daemon=True)
thread.start()
# Run dialog
self.loading.run()
# Bridge pattern
time.sleep(0.3)
temp_root = tk.Tk()
temp_root.withdraw()
temp_root.after(500, lambda: self.launch_ui(temp_root))
temp_root.mainloop()
def load_data(self):
"""Simulate real data loading operations."""
try:
# Step 1: Connect to database
self.loading.update_progress(10, "Connecting to database...")
time.sleep(0.5)
# db_connect() # Your actual code here
# Step 2: Fetch user data
self.loading.update_progress(30, "Loading user data...")
time.sleep(0.8)
# self.data = fetch_user_data() # Your actual code here
# Step 3: Load configuration
self.loading.update_progress(60, "Loading configuration...")
time.sleep(0.6)
# load_config() # Your actual code here
# Step 4: Initialize modules
self.loading.update_progress(85, "Initializing modules...")
time.sleep(0.4)
# init_modules() # Your actual code here
# Step 5: Complete
self.loading.update_progress(100, "Ready!")
time.sleep(0.5)
# Close dialog
self.loading.close()
except Exception as e:
self.loading.update_progress(0, f"Error: {e}")
time.sleep(2)
self.loading.close()
def launch_ui(self, temp_root):
"""Launch main UI after loading."""
temp_root.destroy()
# Create main window
root = tk.Tk()
root.title("Data Viewer")
root.geometry("800x600")
# Show loaded data
label = tk.Label(root, text="Data Loaded Successfully!",
font=("Arial", 16))
label.pack(pady=100)
root.mainloop()
if __name__ == "__main__":
app = DataLoaderApp()
app.start()
---
from bravura.components.universal_loading_dialog import UniversalLoadingDialog
# Enable music in the module first
# Set ENABLE_MUSIC = True at the top of universal_loading_dialog.py
loading = UniversalLoadingDialog(
enable_music=True,
music_path="assets/loading_music.mp3"
)
loading.show()
# Your loading operations...
for i in range(0, 101, 20):
loading.update_progress(i, f"Loading {i}%...")
time.sleep(0.5)
loading.close() # Automatically stops music
---
from bravura.components.universal_loading_dialog import UniversalLoadingDialog
import time
def perform_complex_startup():
loading = UniversalLoadingDialog()
loading.show()
# Phase 1: System checks
loading.update_progress(5, "Running system checks...")
time.sleep(0.3)
# run_system_checks()
# Phase 2: Resource loading
resources = ["Database", "Cache", "Templates", "Assets", "Plugins"]
progress_step = 80 / len(resources)
for idx, resource in enumerate(resources):
current_progress = 10 + int(progress_step * (idx + 1))
loading.update_progress(current_progress, f"Loading {resource}...")
time.sleep(0.4)
# load_resource(resource)
# Phase 3: Finalization
loading.update_progress(95, "Finalizing startup...")
time.sleep(0.3)
# finalize_startup()
loading.update_progress(100, "Complete!")
time.sleep(0.5)
loading.close()
perform_complex_startup()
---
At the top of universal_loading_dialog.py, you can modify these global settings:
# Enable background music support (requires pygame)
ENABLE_MUSIC = False
# Default music file path (if music is enabled)
DEFAULT_MUSIC_PATH = "assets/loading_music.mp3"
# Animation speed (milliseconds per frame)
ANIMATION_DELAY = 50
# Logo rotation speed (degrees per frame)
ROTATION_SPEED = 5
---
---
auto_close parameter to simulations---
Bravura/src/bravura/components/universal_loading_dialog.pydemos/professional/demos/professional_advanced_features_demo.pydemos/enterprise/demos/enterprise_advanced_features_demo.py---
This component is part of the Bravura Framework and is subject to the same licensing terms.
---
When contributing improvements to the Universal Loading Dialog:
---
Last Updated: October 19, 2025
Documentation Version: 1.2
Component Version: 1.2