gui/docs/ref/wails-v3/features/windows/events.mdx
Snider 4bdbb68f46
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m21s
refactor: update import path from go-config to core/config
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 10:26:36 +00:00

693 lines
14 KiB
Text

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