Universal Loading Dialog Component - Comprehensive Guide

Back to Documentation Hub

Universal Loading Dialog Component - Comprehensive Guide

Table of Contents

  1. Overview
  2. Features
  3. Installation
  4. Quick Start
  5. Basic Usage
  6. Advanced Integration
  7. Bridge Tk Instance Pattern
  8. Theme Customization
  9. API Reference
  10. Best Practices
  11. Troubleshooting
  12. Examples

---

Overview

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.

Key Characteristics

---

Features

Visual Features

Technical Features

Supported Platforms

---

Installation

The Universal Loading Dialog is part of the Bravura Framework. It's located at:


Bravura/src/bravura/components/universal_loading_dialog.py

Dependencies


# 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

Importing


from bravura.components.universal_loading_dialog import (
    UniversalLoadingDialog,
    simulate_loading_with_steps
)

---

Quick Start

Minimal Example


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()

With Simulation Helper


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()

---

Basic Usage

Creating a Loading Dialog


# 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")

Displaying the Dialog


# Show the dialog (non-blocking, creates window)
loading.show()

# Run the dialog's event loop (blocking until closed)
loading.run()

Updating Progress


# Update progress and status message
loading.update_progress(progress=45, status="Loading configuration...")

# Progress: 0-100 (integer or float)
# Status: string message to display

Closing the Dialog


# Closes window and cleans up resources
loading.close()

Checking Status


# Check if dialog is still running
if loading.is_running():
    print("Dialog is active")

---

Advanced Integration

Integrating with Main Application

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.

โš ๏ธ CRITICAL: Bridge Tk Instance Pattern

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.

Why This Is Necessary

Implementation


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()

Integration Timing Guidelines


# 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

Common Integration Mistakes

โŒ 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 Customization

Theme Dictionary Structure


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)
}

Pre-built Themes

Professional Dark (Default)


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

Pink Theme (Demo)


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)

Creating Custom Themes


# 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
}

---

API Reference

UniversalLoadingDialog Class

Constructor


UniversalLoadingDialog(
    logo_path: str = None,
    theme: dict = None,
    enable_music: bool = ENABLE_MUSIC,
    music_path: str = None
)

Parameters:

Returns: UniversalLoadingDialog instance

Methods

`show()`

def show() -> None

Creates and displays the loading dialog window. Non-blocking.

Usage:


loading.show()

---

`run()`

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

---

`update_progress()`

def update_progress(progress: int | float, status: str = "") -> None

Updates the progress bar and status message. Thread-safe.

Parameters:

Usage:


loading.update_progress(45, "Loading configuration...")
loading.update_progress(100)  # Just update progress

---

`close()`

def close() -> None

Closes the loading dialog and cleans up resources. Stops animations and music.

Usage:


loading.close()

---

`is_running()`

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...")

---

Helper Functions

`simulate_loading_with_steps()`

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:

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()

---

Best Practices

1. Always Use the Bridge Pattern

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()

2. Use Meaningful Status Messages


# โœ… Good
loading.update_progress(30, "Loading user preferences...")

# โŒ Avoid
loading.update_progress(30, "Loading...")

3. Provide Visual Feedback for Long Operations


# โœ… 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...")

4. Handle Errors Gracefully


try:
    loading.show()
    # ... loading operations ...
    loading.close()
except Exception as e:
    if loading.is_running():
        loading.close()
    raise

5. Use Auto-Close for Simulations


# โœ… Auto-close prevents hanging dialogs
simulate_loading_with_steps(loading, steps, auto_close=True)

6. Clean Up Resources


# โœ… Always close the dialog
try:
    loading.run()
finally:
    if loading.is_running():
        loading.close()

7. Test Theme Compatibility


# โœ… Test custom themes thoroughly
custom_theme = {...}
loading = UniversalLoadingDialog(theme=custom_theme)
# Verify all colors are readable and visually appealing

8. Consider Performance


# โœ… 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!

---

Troubleshooting

Theme Corruption / Incorrect Styling

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()

---

Loading Dialog Won't Close

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()

---

"Application Has Been Destroyed" Errors

Problem: Errors about destroyed Tk instance after closing dialog.

Solution:

  1. Use proper timing delays
  2. Implement bridge pattern correctly
  3. Don't access dialog after calling close()

loading.close()
# DON'T do this after close():
# loading.update_progress(...)  # โŒ Error!

---

Music Not Playing

Problem: Background music doesn't play.

Solution:

  1. Ensure pygame is installed: pip install pygame
  2. Set ENABLE_MUSIC = True in the module
  3. Provide a valid music file path
  4. Check audio file format (MP3, WAV, OGG supported)

loading = UniversalLoadingDialog(
    enable_music=True,
    music_path="path/to/music.mp3"
)

---

Logo Not Displaying

Problem: Custom logo doesn't appear.

Solution:

  1. Check file path is correct
  2. Use supported formats (PNG recommended)
  3. Verify file exists and is readable
  4. Check image size (square images work best)

import os
logo_path = "assets/logo.png"
if os.path.exists(logo_path):
    loading = UniversalLoadingDialog(logo_path=logo_path)

---

Window Positioning Issues

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
}

---

Progress Bar Not Updating

Problem: Progress bar appears frozen.

Solution:

  1. Ensure you're calling update_progress() correctly
  2. Check if dialog is still running with is_running()
  3. Verify thread safety if updating from multiple threads

if loading.is_running():
    loading.update_progress(50, "Loading...")

---

Examples

Example 1: Simple Application Launch


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()

---

Example 2: Custom Theme with Logo


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()

---

Example 3: Real-World Data Loading


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()

---

Example 4: With Background Music


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

---

Example 5: Multiple Sequential Operations


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()

---

Configuration Constants

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

---

Platform-Specific Notes

Windows

macOS

Linux

---

Version History

Version 1.2 (Current)

Version 1.1

Version 1.0

---

Support and Resources

Related Documentation

Source Code

Demo Integrations

---

License

This component is part of the Bravura Framework and is subject to the same licensing terms.

---

Contributing

When contributing improvements to the Universal Loading Dialog:

  1. Maintain backward compatibility
  2. Follow the existing code style
  3. Update this documentation for any API changes
  4. Test on all supported platforms
  5. Ensure the Bridge Tk Instance Pattern remains functional
  6. Add examples for new features

---

Last Updated: October 19, 2025

Documentation Version: 1.2

Component Version: 1.2