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: .. code-block:: python # 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:** .. code-block:: python # 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: .. code-block:: python # 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('', self.on_file_double_click) self.widgets['file_list'].bind('', 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python 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('', update_layout) return content_frame return build_responsive_content Error Handling and Validation ----------------------------- Robust Error Handling ~~~~~~~~~~~~~~~~~~~~ Implement comprehensive error handling: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python 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: .. code-block:: python # 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.