Best Practices
This guide provides best practices for building robust, maintainable, and user-friendly applications with ThreePaneWindows.
Architecture and Design
Application Structure
Organize your application with clear separation of concerns:
# Recommended project structure
my_app/
├── main.py # Application entry point
├── config/
│ ├── __init__.py
│ ├── settings.py # Application settings
│ └── themes.py # Custom themes
├── ui/
│ ├── __init__.py
│ ├── main_window.py # Main window setup
│ ├── panels/ # Panel builders
│ │ ├── __init__.py
│ │ ├── file_panel.py
│ │ ├── editor_panel.py
│ │ └── properties_panel.py
│ └── dialogs/ # Dialog windows
├── core/
│ ├── __init__.py
│ ├── models.py # Data models
│ ├── services.py # Business logic
│ └── events.py # Event handling
├── resources/
│ ├── icons/ # Application icons
│ ├── themes/ # Theme definitions
│ └── config/ # Configuration files
└── tests/ # Unit tests
Main Application Structure:
# main.py
import tkinter as tk
from ui.main_window import MainWindow
from config.settings import AppSettings
class Application:
def __init__(self):
self.root = tk.Tk()
self.settings = AppSettings()
self.main_window = None
def initialize(self):
"""Initialize the application."""
self.setup_root_window()
self.create_main_window()
self.load_user_preferences()
def setup_root_window(self):
"""Configure the root window."""
self.root.title(self.settings.APP_NAME)
self.root.geometry(self.settings.DEFAULT_GEOMETRY)
self.root.minsize(800, 600)
def create_main_window(self):
"""Create the main application window."""
self.main_window = MainWindow(self.root, self.settings)
def load_user_preferences(self):
"""Load and apply user preferences."""
prefs = self.settings.load_preferences()
if prefs:
self.main_window.apply_preferences(prefs)
def run(self):
"""Start the application."""
self.initialize()
self.root.mainloop()
def shutdown(self):
"""Clean shutdown of the application."""
if self.main_window:
self.main_window.save_preferences()
self.root.quit()
if __name__ == "__main__":
app = Application()
try:
app.run()
except KeyboardInterrupt:
app.shutdown()
Modular Panel Design
Create reusable, self-contained panel modules:
# ui/panels/base_panel.py
import tkinter as tk
from abc import ABC, abstractmethod
class BasePanel(ABC):
"""Base class for all panels."""
def __init__(self, parent, config=None):
self.parent = parent
self.config = config or {}
self.widgets = {}
self.is_initialized = False
@abstractmethod
def build_ui(self):
"""Build the panel UI. Must be implemented by subclasses."""
pass
def initialize(self):
"""Initialize the panel."""
if not self.is_initialized:
self.build_ui()
self.setup_bindings()
self.load_data()
self.is_initialized = True
def setup_bindings(self):
"""Setup event bindings. Override in subclasses."""
pass
def load_data(self):
"""Load initial data. Override in subclasses."""
pass
def cleanup(self):
"""Cleanup resources. Override in subclasses."""
pass
# ui/panels/file_panel.py
from .base_panel import BasePanel
class FilePanel(BasePanel):
"""File explorer panel."""
def build_ui(self):
"""Build the file panel UI."""
# Header
header_frame = tk.Frame(self.parent)
header_frame.pack(fill=tk.X, padx=5, pady=5)
tk.Label(header_frame, text="📁 Files",
font=("Arial", 12, "bold")).pack(side=tk.LEFT)
refresh_btn = tk.Button(header_frame, text="🔄",
command=self.refresh_files)
refresh_btn.pack(side=tk.RIGHT)
# File list
list_frame = tk.Frame(self.parent)
list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.widgets['file_list'] = tk.Listbox(list_frame)
self.widgets['file_list'].pack(fill=tk.BOTH, expand=True)
# Scrollbar
scrollbar = tk.Scrollbar(list_frame,
command=self.widgets['file_list'].yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.widgets['file_list'].config(yscrollcommand=scrollbar.set)
def setup_bindings(self):
"""Setup file panel bindings."""
self.widgets['file_list'].bind('<Double-Button-1>', self.on_file_double_click)
self.widgets['file_list'].bind('<Button-3>', self.show_context_menu)
def load_data(self):
"""Load file list."""
self.refresh_files()
def refresh_files(self):
"""Refresh the file list."""
# Implementation here
pass
def on_file_double_click(self, event):
"""Handle file double-click."""
# Implementation here
pass
def show_context_menu(self, event):
"""Show context menu for files."""
# Implementation here
pass
Configuration Management
Implement robust configuration management:
# config/settings.py
import json
import os
from pathlib import Path
class AppSettings:
"""Application settings manager."""
# Default settings
APP_NAME = "My ThreePaneWindows App"
DEFAULT_GEOMETRY = "1200x800"
DEFAULT_THEME = "light"
def __init__(self):
self.config_dir = Path.home() / ".my_app"
self.config_file = self.config_dir / "config.json"
self.ensure_config_dir()
def ensure_config_dir(self):
"""Ensure configuration directory exists."""
self.config_dir.mkdir(exist_ok=True)
def load_preferences(self):
"""Load user preferences."""
try:
if self.config_file.exists():
with open(self.config_file, 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading preferences: {e}")
return self.get_default_preferences()
def save_preferences(self, preferences):
"""Save user preferences."""
try:
with open(self.config_file, 'w') as f:
json.dump(preferences, f, indent=2)
except Exception as e:
print(f"Error saving preferences: {e}")
def get_default_preferences(self):
"""Get default preferences."""
return {
"window": {
"geometry": self.DEFAULT_GEOMETRY,
"theme": self.DEFAULT_THEME
},
"panes": {
"left_width": 250,
"right_width": 300,
"left_detached": False,
"right_detached": False
},
"recent_files": [],
"ui": {
"show_status_bar": True,
"show_toolbar": True
}
}
User Interface Design
Consistent Visual Design
Maintain visual consistency throughout your application:
# ui/styles.py
class UIStyles:
"""Centralized UI styling constants."""
# Fonts
HEADER_FONT = ("Arial", 12, "bold")
CONTENT_FONT = ("Arial", 10)
CODE_FONT = ("Consolas", 10)
# Colors (will be overridden by themes)
PRIMARY_COLOR = "#007bff"
SECONDARY_COLOR = "#6c757d"
SUCCESS_COLOR = "#28a745"
WARNING_COLOR = "#ffc107"
ERROR_COLOR = "#dc3545"
# Spacing
PADDING_SMALL = 5
PADDING_MEDIUM = 10
PADDING_LARGE = 20
# Widget sizes
BUTTON_WIDTH = 100
ENTRY_WIDTH = 200
LISTBOX_HEIGHT = 10
@classmethod
def apply_button_style(cls, button, style="primary"):
"""Apply consistent button styling."""
styles = {
"primary": {"bg": cls.PRIMARY_COLOR, "fg": "white"},
"secondary": {"bg": cls.SECONDARY_COLOR, "fg": "white"},
"success": {"bg": cls.SUCCESS_COLOR, "fg": "white"},
"warning": {"bg": cls.WARNING_COLOR, "fg": "black"},
"danger": {"bg": cls.ERROR_COLOR, "fg": "white"}
}
if style in styles:
button.configure(**styles[style])
button.configure(relief=tk.FLAT, padx=cls.PADDING_MEDIUM)
Responsive Layout Design
Design layouts that work well at different sizes:
def create_responsive_panel(parent):
"""Create panel that adapts to different sizes."""
def build_responsive_content(frame):
"""Build content that adapts to frame size."""
# Use frames that expand/contract appropriately
header_frame = tk.Frame(frame, height=40)
header_frame.pack(fill=tk.X)
header_frame.pack_propagate(False)
content_frame = tk.Frame(frame)
content_frame.pack(fill=tk.BOTH, expand=True)
footer_frame = tk.Frame(frame, height=30)
footer_frame.pack(fill=tk.X)
footer_frame.pack_propagate(False)
# Responsive content in main area
def update_layout(event=None):
"""Update layout based on available space."""
width = content_frame.winfo_width()
height = content_frame.winfo_height()
# Adjust layout based on size
if width < 300:
# Narrow layout - stack vertically
configure_narrow_layout(content_frame)
else:
# Wide layout - use columns
configure_wide_layout(content_frame)
# Bind to size changes
content_frame.bind('<Configure>', update_layout)
return content_frame
return build_responsive_content
Error Handling and Validation
Robust Error Handling
Implement comprehensive error handling:
# core/error_handling.py
import logging
import traceback
from functools import wraps
class ErrorHandler:
"""Centralized error handling."""
def __init__(self):
self.setup_logging()
def setup_logging(self):
"""Setup application logging."""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
def handle_exception(self, exc_type, exc_value, exc_traceback):
"""Handle uncaught exceptions."""
if issubclass(exc_type, KeyboardInterrupt):
return # Allow Ctrl+C to work
error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
self.logger.error(f"Uncaught exception: {error_msg}")
# Show user-friendly error dialog
self.show_error_dialog("An unexpected error occurred", str(exc_value))
def show_error_dialog(self, title, message):
"""Show error dialog to user."""
import tkinter.messagebox as messagebox
messagebox.showerror(title, message)
def safe_execute(error_handler=None):
"""Decorator for safe function execution."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if error_handler:
error_handler.logger.error(f"Error in {func.__name__}: {e}")
error_handler.show_error_dialog("Operation Failed", str(e))
else:
print(f"Error in {func.__name__}: {e}")
return None
return wrapper
return decorator
Input Validation
Validate user input consistently:
# core/validation.py
import re
from typing import Any, Tuple, Optional
class Validator:
"""Input validation utilities."""
@staticmethod
def validate_file_path(path: str) -> Tuple[bool, str]:
"""Validate file path."""
if not path:
return False, "Path cannot be empty"
if not os.path.exists(path):
return False, f"Path does not exist: {path}"
if not os.path.isfile(path):
return False, f"Path is not a file: {path}"
return True, "Valid file path"
@staticmethod
def validate_icon_path(path: str) -> Tuple[bool, str]:
"""Validate icon file path."""
if not path:
return True, "No icon specified"
is_valid, message = Validator.validate_file_path(path)
if not is_valid:
return False, message
# Check file extension
valid_extensions = ['.ico', '.png', '.gif', '.bmp', '.xbm']
ext = os.path.splitext(path)[1].lower()
if ext not in valid_extensions:
return False, f"Unsupported icon format: {ext}"
return True, "Valid icon file"
@staticmethod
def validate_number_range(value: Any, min_val: float, max_val: float) -> Tuple[bool, str]:
"""Validate number is within range."""
try:
num_value = float(value)
if min_val <= num_value <= max_val:
return True, f"Valid number: {num_value}"
else:
return False, f"Number must be between {min_val} and {max_val}"
except (ValueError, TypeError):
return False, f"Invalid number: {value}"
Performance Optimization
Efficient Content Loading
Implement lazy loading and caching:
# core/content_manager.py
import threading
import time
from typing import Dict, Any, Callable
class ContentManager:
"""Manage content loading and caching."""
def __init__(self):
self.cache: Dict[str, Any] = {}
self.loading_tasks: Dict[str, threading.Thread] = {}
self.max_cache_size = 100
def load_content_async(self, key: str, loader_func: Callable,
callback: Callable = None):
"""Load content asynchronously."""
# Check cache first
if key in self.cache:
if callback:
callback(self.cache[key])
return self.cache[key]
# Check if already loading
if key in self.loading_tasks and self.loading_tasks[key].is_alive():
return None
# Start loading task
def load_task():
try:
content = loader_func()
self.cache[key] = content
# Manage cache size
if len(self.cache) > self.max_cache_size:
# Remove oldest entry (simple FIFO)
oldest_key = next(iter(self.cache))
del self.cache[oldest_key]
if callback:
# Schedule callback on main thread
root.after(0, lambda: callback(content))
except Exception as e:
print(f"Error loading content for {key}: {e}")
if callback:
root.after(0, lambda: callback(None))
finally:
# Clean up task reference
if key in self.loading_tasks:
del self.loading_tasks[key]
task = threading.Thread(target=load_task, daemon=True)
self.loading_tasks[key] = task
task.start()
return None
def clear_cache(self):
"""Clear the content cache."""
self.cache.clear()
def preload_content(self, keys_and_loaders: Dict[str, Callable]):
"""Preload multiple content items."""
for key, loader in keys_and_loaders.items():
self.load_content_async(key, loader)
Memory Management
Implement proper memory management:
# core/memory_manager.py
import gc
import psutil
import os
class MemoryManager:
"""Monitor and manage memory usage."""
def __init__(self, warning_threshold=80, critical_threshold=90):
self.warning_threshold = warning_threshold
self.critical_threshold = critical_threshold
self.process = psutil.Process(os.getpid())
def get_memory_usage(self):
"""Get current memory usage percentage."""
memory_info = self.process.memory_info()
system_memory = psutil.virtual_memory()
usage_percent = (memory_info.rss / system_memory.total) * 100
return {
'usage_percent': usage_percent,
'rss': memory_info.rss,
'vms': memory_info.vms,
'system_total': system_memory.total,
'system_available': system_memory.available
}
def check_memory_status(self):
"""Check memory status and take action if needed."""
usage = self.get_memory_usage()
usage_percent = usage['usage_percent']
if usage_percent > self.critical_threshold:
self.handle_critical_memory()
return "critical"
elif usage_percent > self.warning_threshold:
self.handle_warning_memory()
return "warning"
return "normal"
def handle_warning_memory(self):
"""Handle warning memory level."""
print(f"Memory usage warning: {self.get_memory_usage()['usage_percent']:.1f}%")
# Trigger garbage collection
gc.collect()
def handle_critical_memory(self):
"""Handle critical memory level."""
print(f"Critical memory usage: {self.get_memory_usage()['usage_percent']:.1f}%")
# Force garbage collection
gc.collect()
# Clear caches
# Notify user
def start_monitoring(self, interval=30000): # 30 seconds
"""Start periodic memory monitoring."""
def monitor():
self.check_memory_status()
root.after(interval, monitor)
monitor()
Cross-Platform Compatibility
Platform-Specific Handling
Handle platform differences gracefully:
# core/platform_utils.py
import platform
import os
class PlatformUtils:
"""Platform-specific utilities."""
@staticmethod
def get_platform():
"""Get current platform."""
return platform.system()
@staticmethod
def get_config_dir():
"""Get platform-appropriate config directory."""
system = platform.system()
if system == "Windows":
return os.path.join(os.environ['APPDATA'], 'MyApp')
elif system == "Darwin": # macOS
return os.path.expanduser('~/Library/Application Support/MyApp')
else: # Linux and others
return os.path.expanduser('~/.config/myapp')
@staticmethod
def get_default_font():
"""Get platform-appropriate default font."""
system = platform.system()
if system == "Windows":
return ("Segoe UI", 10)
elif system == "Darwin": # macOS
return ("SF Pro Text", 10)
else: # Linux
return ("Ubuntu", 10)
@staticmethod
def setup_platform_specific_ui(root):
"""Setup platform-specific UI elements."""
system = platform.system()
if system == "Darwin": # macOS
# macOS-specific menu setup
root.createcommand('tk::mac::ShowPreferences', lambda: show_preferences())
root.createcommand('tk::mac::Quit', lambda: root.quit())
elif system == "Windows":
# Windows-specific setup
try:
# Set Windows-specific icon
root.iconbitmap('resources/icons/app.ico')
except:
pass
Icon Management Best Practices
Implement robust icon handling:
# ui/icon_manager.py
from threepanewindows import get_recommended_icon_formats, validate_icon_path
class IconManager:
"""Manage application icons across platforms."""
def __init__(self, icon_dir="resources/icons"):
self.icon_dir = icon_dir
self.icon_cache = {}
self.recommended_formats = get_recommended_icon_formats()
def get_icon_path(self, icon_name):
"""Get best icon path for current platform."""
# Try recommended formats first
for ext in self.recommended_formats:
icon_path = os.path.join(self.icon_dir, f"{icon_name}{ext}")
if os.path.exists(icon_path):
is_valid, _ = validate_icon_path(icon_path)
if is_valid:
return icon_path
# Try all supported formats
all_formats = ['.ico', '.png', '.gif', '.bmp', '.xbm']
for ext in all_formats:
if ext not in self.recommended_formats:
icon_path = os.path.join(self.icon_dir, f"{icon_name}{ext}")
if os.path.exists(icon_path):
is_valid, _ = validate_icon_path(icon_path)
if is_valid:
return icon_path
return "" # No suitable icon found
def create_pane_config_with_icon(self, title, icon_name, **kwargs):
"""Create PaneConfig with appropriate icon."""
from threepanewindows import PaneConfig
icon_path = self.get_icon_path(icon_name)
return PaneConfig(
title=title,
window_icon=icon_path,
**kwargs
)
Testing and Quality Assurance
Unit Testing
Implement comprehensive unit tests:
# tests/test_panels.py
import unittest
import tkinter as tk
from ui.panels.file_panel import FilePanel
class TestFilePanel(unittest.TestCase):
"""Test cases for FilePanel."""
def setUp(self):
"""Set up test fixtures."""
self.root = tk.Tk()
self.root.withdraw() # Hide test window
self.test_frame = tk.Frame(self.root)
self.panel = FilePanel(self.test_frame)
def tearDown(self):
"""Clean up after tests."""
self.root.destroy()
def test_panel_initialization(self):
"""Test panel initializes correctly."""
self.assertFalse(self.panel.is_initialized)
self.panel.initialize()
self.assertTrue(self.panel.is_initialized)
def test_ui_creation(self):
"""Test UI elements are created."""
self.panel.initialize()
self.assertIn('file_list', self.panel.widgets)
self.assertIsInstance(self.panel.widgets['file_list'], tk.Listbox)
def test_error_handling(self):
"""Test error handling in panel operations."""
# Test with invalid configuration
with self.assertRaises(ValueError):
FilePanel(None) # Invalid parent
Integration Testing
Test component integration:
# tests/test_integration.py
import unittest
import tkinter as tk
from threepanewindows import EnhancedDockableThreePaneWindow, PaneConfig
class TestIntegration(unittest.TestCase):
"""Integration tests for ThreePaneWindows."""
def setUp(self):
"""Set up integration test environment."""
self.root = tk.Tk()
self.root.withdraw()
def tearDown(self):
"""Clean up integration tests."""
self.root.destroy()
def test_complete_window_creation(self):
"""Test complete window creation and configuration."""
def build_test_panel(frame):
tk.Label(frame, text="Test Panel").pack()
# Create window with all features
window = EnhancedDockableThreePaneWindow(
self.root,
left_config=PaneConfig(title="Left", detachable=True),
center_config=PaneConfig(title="Center", detachable=False),
right_config=PaneConfig(title="Right", detachable=True),
left_builder=build_test_panel,
center_builder=build_test_panel,
right_builder=build_test_panel,
theme_name="light"
)
window.pack(fill=tk.BOTH, expand=True)
# Test window is created and functional
self.assertIsNotNone(window)
self.assertEqual(window.get_current_theme(), "light")
Documentation and Maintenance
Code Documentation
Document your code thoroughly:
def create_professional_application():
"""
Create a professional three-pane application.
This function demonstrates best practices for creating a robust,
maintainable three-pane application with ThreePaneWindows.
Returns:
EnhancedDockableThreePaneWindow: Configured main window
Example:
>>> app = create_professional_application()
>>> app.pack(fill=tk.BOTH, expand=True)
Note:
This function requires proper icon files in the resources/icons
directory for optimal cross-platform compatibility.
"""
# Implementation with detailed comments
pass
Version Management
Implement proper version management:
# version.py
__version__ = "1.0.0"
__version_info__ = (1, 0, 0)
def check_compatibility():
"""Check ThreePaneWindows version compatibility."""
import threepanewindows
required_version = (1, 0, 4)
current_version = threepanewindows.__version_info__
if current_version < required_version:
raise RuntimeError(
f"ThreePaneWindows {'.'.join(map(str, required_version))} "
f"or higher required, found {'.'.join(map(str, current_version))}"
)
Summary of Best Practices
Architecture: 1. Use modular design with clear separation of concerns 2. Implement proper configuration management 3. Use event-driven architecture for loose coupling 4. Design for testability from the start
User Interface: 1. Maintain visual consistency throughout the application 2. Implement responsive design principles 3. Provide comprehensive keyboard navigation 4. Follow platform-specific UI conventions
Error Handling: 1. Implement comprehensive error handling and logging 2. Validate all user input 3. Provide meaningful error messages 4. Handle edge cases gracefully
Performance: 1. Use lazy loading for expensive operations 2. Implement efficient caching strategies 3. Monitor memory usage and clean up resources 4. Optimize for the common use case
Cross-Platform: 1. Test on all target platforms regularly 2. Handle platform differences gracefully 3. Use appropriate file paths and conventions 4. Provide platform-specific optimizations
Quality Assurance: 1. Write comprehensive unit and integration tests 2. Use static analysis tools 3. Implement continuous integration 4. Document code thoroughly
Maintenance: 1. Keep dependencies up to date 2. Monitor performance metrics 3. Collect and analyze user feedback 4. Plan for future extensibility
Following these best practices will help you create robust, maintainable, and user-friendly applications that provide an excellent user experience across all platforms.