--- title: Window Events description: Handle window lifecycle and state change events sidebar: order: 5 --- import { Tabs, TabItem } from "@astrojs/starlight/components"; ## Window Events Wails provides **comprehensive event hooks** for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs. ## Lifecycle Events ### OnCreate Called when a window is created: ```go app.OnWindowCreation(func(window *application.WebviewWindow) { fmt.Printf("Window created: %s (ID: %d)\n", window.Name(), window.ID()) // Configure all new windows window.SetMinSize(400, 300) // Register window-specific handlers window.OnClose(func() bool { return confirmClose() }) }) ``` **Use cases:** - Configure all windows consistently - Register event handlers - Track window creation - Initialise window-specific resources ### OnClose Called when user attempts to close window: ```go window.OnClose(func() bool { // Return false to cancel close // Return true to allow close if hasUnsavedChanges() { result := showConfirmdialog("Unsaved changes. Close anyway?") return result == "yes" } return true }) ``` **Important:** - Only triggered by user actions (clicking X button) - NOT triggered by `window.Destroy()` - Can cancel the close by returning `false` **Use cases:** - Confirm before closing - Save state - Prevent accidental closure - Cleanup before close **Example with dialog:** ```go window.OnClose(func() bool { if !hasUnsavedChanges() { return true } // Show confirmation dialog dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Confirm Close", Width: 400, Height: 150, Parent: window, AlwaysOnTop: true, }) // Wait for user response result := waitFordialogResult(dialog) return result == "yes" }) ``` ### OnDestroy Called when window is destroyed: ```go window.OnDestroy(func() { fmt.Printf("Window destroyed: %s\n", window.Name()) // Cleanup resources closeDatabase() // Remove from tracking removeWindowFromRegistry(window.ID()) // Update application state updateWindowCount() }) ``` **Important:** - Always called when window is destroyed - Cannot be cancelled - Last chance to cleanup **Use cases:** - Release resources - Close connections - Update application state - Remove from tracking **Example with resource cleanup:** ```go type ManagedWindow struct { window *application.WebviewWindow db *sql.DB listeners []func() } func (mw *ManagedWindow) Setup() { mw.window.OnDestroy(func() { // Close database if mw.db != nil { mw.db.Close() } // Remove event listeners for _, listener := range mw.listeners { listener() } // Clear references mw.db = nil mw.listeners = nil }) } ``` ## Focus Events ### OnFocus Called when window gains focus: ```go window.OnFocus(func() { fmt.Println("Window gained focus") // Update UI updateTitleBar(true) // Refresh data refreshContent() // Notify other windows app.Event.Emit("window-focused", window.ID()) }) ``` **Use cases:** - Update UI appearance - Refresh data - Resume operations - Coordinate with other windows ### OnBlur Called when window loses focus: ```go window.OnBlur(func() { fmt.Println("Window lost focus") // Update UI updateTitleBar(false) // Pause operations pauseAnimations() // Save state saveCurrentState() }) ``` **Use cases:** - Update UI appearance - Pause operations - Save state - Reduce resource usage **Example: Focus-aware UI:** ```go type FocusAwareWindow struct { window *application.WebviewWindow focused bool } func (fw *FocusAwareWindow) Setup() { fw.window.OnFocus(func() { fw.focused = true fw.updateAppearance() }) fw.window.OnBlur(func() { fw.focused = false fw.updateAppearance() }) } func (fw *FocusAwareWindow) updateAppearance() { if fw.focused { fw.window.EmitEvent("update-theme", "active") } else { fw.window.EmitEvent("update-theme", "inactive") } } ``` ## State Change Events ### OnMinimise / OnUnMinimise Called when window is minimised or restored: ```go window.OnMinimise(func() { fmt.Println("Window minimised") // Pause expensive operations pauseRendering() // Save state saveWindowState() }) window.OnUnMinimise(func() { fmt.Println("Window restored from minimised") // Resume operations resumeRendering() // Refresh data refreshContent() }) ``` **Use cases:** - Pause/resume operations - Save/restore state - Reduce resource usage - Update UI ### OnMaximise / OnUnMaximise Called when window is maximised or restored: ```go window.OnMaximise(func() { fmt.Println("Window maximised") // Adjust layout window.EmitEvent("layout-mode", "maximised") // Update button icon updateMaximiseButton("restore") }) window.OnUnMaximise(func() { fmt.Println("Window restored from maximised") // Adjust layout window.EmitEvent("layout-mode", "normal") // Update button icon updateMaximiseButton("maximise") }) ``` **Use cases:** - Adjust layout - Update UI - Save window state - Coordinate with other windows ### OnFullscreen / OnUnFullscreen Called when window enters or exits fullscreen: ```go window.OnFullscreen(func() { fmt.Println("Window entered fullscreen") // Hide UI chrome window.EmitEvent("chrome-visibility", false) // Adjust layout window.EmitEvent("layout-mode", "fullscreen") }) window.OnUnFullscreen(func() { fmt.Println("Window exited fullscreen") // Show UI chrome window.EmitEvent("chrome-visibility", true) // Restore layout window.EmitEvent("layout-mode", "normal") }) ``` **Use cases:** - Show/hide UI elements - Adjust layout - Update controls - Save preferences ## Position and Size Events ### OnMove Called when window is moved: ```go window.OnMove(func(x, y int) { fmt.Printf("Window moved to: %d, %d\n", x, y) // Save position saveWindowPosition(x, y) // Update related windows updateRelatedWindowPositions(x, y) }) ``` **Use cases:** - Save window position - Update related windows - Snap to edges - Multi-monitor handling ### OnResize Called when window is resized: ```go window.OnResize(func(width, height int) { fmt.Printf("Window resized to: %dx%d\n", width, height) // Save size saveWindowSize(width, height) // Adjust layout window.EmitEvent("window-size", map[string]int{ "width": width, "height": height, }) }) ``` **Use cases:** - Save window size - Adjust layout - Update UI - Responsive design **Example: Responsive layout:** ```go window.OnResize(func(width, height int) { var layout string if width < 600 { layout = "compact" } else if width < 1200 { layout = "normal" } else { layout = "wide" } window.EmitEvent("layout-changed", layout) }) ``` ## Complete Example Here's a production-ready window with full event handling: ```go package main import ( "encoding/json" "fmt" "os" "github.com/wailsapp/wails/v3/pkg/application" ) type WindowState struct { X int `json:"x"` Y int `json:"y"` Width int `json:"width"` Height int `json:"height"` Maximised bool `json:"maximised"` Fullscreen bool `json:"fullscreen"` } type ManagedWindow struct { app *application.Application window *application.WebviewWindow state WindowState dirty bool } func main() { app := application.New(application.Options{ Name: "Event Demo", }) mw := &ManagedWindow{app: app} mw.CreateWindow() mw.LoadState() mw.SetupEventHandlers() app.Run() } func (mw *ManagedWindow) CreateWindow() { mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Event Demo", Width: 800, Height: 600, }) } func (mw *ManagedWindow) SetupEventHandlers() { // Focus events mw.window.OnFocus(func() { fmt.Println("Window focused") mw.window.EmitEvent("focus-state", true) }) mw.window.OnBlur(func() { fmt.Println("Window blurred") mw.window.EmitEvent("focus-state", false) }) // State change events mw.window.OnMinimise(func() { fmt.Println("Window minimised") mw.SaveState() }) mw.window.OnUnMinimise(func() { fmt.Println("Window restored") }) mw.window.OnMaximise(func() { fmt.Println("Window maximised") mw.state.Maximised = true mw.dirty = true }) mw.window.OnUnMaximise(func() { fmt.Println("Window restored from maximised") mw.state.Maximised = false mw.dirty = true }) mw.window.OnFullscreen(func() { fmt.Println("Window fullscreen") mw.state.Fullscreen = true mw.dirty = true }) mw.window.OnUnFullscreen(func() { fmt.Println("Window exited fullscreen") mw.state.Fullscreen = false mw.dirty = true }) // Position and size events mw.window.OnMove(func(x, y int) { mw.state.X = x mw.state.Y = y mw.dirty = true }) mw.window.OnResize(func(width, height int) { mw.state.Width = width mw.state.Height = height mw.dirty = true }) // Lifecycle events mw.window.OnClose(func() bool { if mw.dirty { mw.SaveState() } return true }) mw.window.OnDestroy(func() { fmt.Println("Window destroyed") if mw.dirty { mw.SaveState() } }) } func (mw *ManagedWindow) LoadState() { data, err := os.ReadFile("window-state.json") if err != nil { return } if err := json.Unmarshal(data, &mw.state); err != nil { return } // Restore window state mw.window.SetPosition(mw.state.X, mw.state.Y) mw.window.SetSize(mw.state.Width, mw.state.Height) if mw.state.Maximised { mw.window.Maximise() } if mw.state.Fullscreen { mw.window.Fullscreen() } } func (mw *ManagedWindow) SaveState() { data, err := json.Marshal(mw.state) if err != nil { return } os.WriteFile("window-state.json", data, 0644) mw.dirty = false fmt.Println("Window state saved") } ``` ## Event Coordination ### Cross-Window Events Coordinate between multiple windows: ```go // In main window mainWindow.OnFocus(func() { // Notify all windows app.Event.Emit("main-window-focused", nil) }) // In other windows app.Event.On("main-window-focused", func(event *application.WailsEvent) { // Update UI updateRelativeToMain() }) ``` ### Event Chains Chain events together: ```go window.OnMaximise(func() { // Save state saveWindowState() // Update layout window.EmitEvent("layout-changed", "maximised") // Notify other windows app.Event.Emit("window-maximised", window.ID()) }) ``` ### Debounced Events Debounce frequent events: ```go var resizeTimer *time.Timer window.OnResize(func(width, height int) { if resizeTimer != nil { resizeTimer.Stop() } resizeTimer = time.AfterFunc(500*time.Millisecond, func() { // Save after resize stops saveWindowSize(width, height) }) }) ``` ## Best Practices ### ✅ Do - **Save state on close** - Restore window position/size - **Cleanup on destroy** - Release resources - **Debounce frequent events** - Resize, move - **Handle focus changes** - Update UI appropriately - **Coordinate windows** - Use events for communication - **Test all events** - Ensure handlers work correctly ### ❌ Don't - **Don't block event handlers** - Keep them fast - **Don't forget cleanup** - Memory leaks - **Don't ignore errors** - Log or handle them - **Don't save on every event** - Debounce first - **Don't create circular events** - Infinite loops - **Don't forget platform differences** - Test thoroughly ## Troubleshooting ### OnClose Not Firing **Cause:** Using `window.Destroy()` instead of `window.Close()` **Solution:** ```go // ✅ Triggers OnClose window.Close() // ❌ Doesn't trigger OnClose window.Destroy() ``` ### Events Not Firing **Cause:** Handler registered after event occurred **Solution:** ```go // Register handlers immediately after creation window := app.Window.New() window.OnClose(func() bool { return true }) ``` ### Memory Leaks **Cause:** Not cleaning up in OnDestroy **Solution:** ```go window.OnDestroy(func() { // Always cleanup closeResources() removeReferences() }) ``` ## Next Steps **Window Basics** - Learn the fundamentals of window management [Learn More →](/features/windows/basics) **Multiple Windows** - Patterns for multi-window applications [Learn More →](/features/windows/multiple) **Events System** - Deep dive into the event system [Learn More →](/features/events/system) **Application Lifecycle** - Understand the application lifecycle [Learn More →](/concepts/lifecycle) --- **Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).