693 lines
14 KiB
Text
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).
|