Beberapa Window
Aplikasi Multi-Window
Section titled “Aplikasi Multi-Window”Wails v3 menyediakan dukungan multi-window native untuk membuat window pengaturan, window dokumen, tool palette, dan window inspector. Lacak window, aktifkan komunikasi antar window, dan kelola siklus hidupnya dengan API sederhana dan konsisten.
Window Utama + Pengaturan
Section titled “Window Utama + Pengaturan”package main
import "github.com/wailsapp/wails/v3/pkg/application"
type App struct { app *application.App mainWindow *application.WebviewWindow settingsWindow *application.WebviewWindow}
func main() { app := &App{}
app.app = application.New(application.Options{ Name: "Multi-Window App", })
// Create main window app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Main Application", Width: 1200, Height: 800, })
// Create settings window (hidden initially) app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, Height: 400, Hidden: true, })
app.app.Run()}
// Show settings from main windowfunc (a *App) ShowSettings() { if a.settingsWindow != nil { a.settingsWindow.Show() a.settingsWindow.Focus() }}Key points:
- Main window always visible
- Settings window created but hidden
- Show settings on demand
- Reuse same window (don’t create multiple)
Pelacakan Window
Section titled “Pelacakan Window”Dapatkan Semua Window
Section titled “Dapatkan Semua Window”windows := app.Window.GetAll()fmt.Printf("Total windows: %d\n", len(windows))
for _, window := range windows { fmt.Printf("- %s (ID: %d)\n", window.Name(), window.ID())}Temukan Window Tertentu
Section titled “Temukan Window Tertentu”// By nameif settings, ok := app.Window.GetByName("settings"); ok { settings.Show()}
// By IDif window, ok := app.Window.GetByID(123); ok { window.Focus()}
// Current (focused) windowcurrent := app.Window.Current()Pola Registry Window
Section titled “Pola Registry Window”Track windows in your application:
type WindowManager struct { windows map[string]*application.WebviewWindow mu sync.RWMutex}
func (wm *WindowManager) Register(name string, window *application.WebviewWindow) { wm.mu.Lock() defer wm.mu.Unlock() wm.windows[name] = window}
func (wm *WindowManager) Get(name string) *application.WebviewWindow { wm.mu.RLock() defer wm.mu.RUnlock() return wm.windows[name]}
func (wm *WindowManager) Remove(name string) { wm.mu.Lock() defer wm.mu.Unlock() delete(wm.windows, name)}Komunikasi Window
Section titled “Komunikasi Window”Menggunakan Event
Section titled “Menggunakan Event”Windows communicate via the event system:
// In main window - emit eventapp.Event.Emit("settings-changed", map[string]interface{}{ "theme": "dark", "fontSize": 14,})
// In settings window - listen for eventapp.Event.On("settings-changed", func(event *application.CustomEvent) { data := event.Data.(map[string]interface{}) theme := data["theme"].(string) fontSize := data["fontSize"].(int)
// Update UI updateSettings(theme, fontSize)})Pola State Bersama
Section titled “Pola State Bersama”Use a shared state manager:
type AppState struct { theme string fontSize int mu sync.RWMutex}
var state = &AppState{ theme: "light", fontSize: 12,}
func (s *AppState) SetTheme(theme string) { s.mu.Lock() s.theme = theme s.mu.Unlock()
// Notify all windows app.Event.Emit("theme-changed", theme)}
func (s *AppState) GetTheme() string { s.mu.RLock() defer s.mu.RUnlock() return s.theme}Pesan Window-ke-Window
Section titled “Pesan Window-ke-Window”Send messages between specific windows:
// Get target windowif targetWindow, ok := app.Window.GetByName("preview"); ok { // Emit event to specific window targetWindow.EmitEvent("update-preview", previewData)}Pola Umum
Section titled “Pola Umum”Pola 1: Window Singleton
Section titled “Pola 1: Window Singleton”Ensure only one instance of a window exists:
var settingsWindow *application.WebviewWindow
func ShowSettings(app *application.App) { // Create if doesn't exist if settingsWindow == nil { settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, Height: 400, })
// Cleanup on close settingsWindow.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { settingsWindow = nil }) }
// Show and focus settingsWindow.Show() settingsWindow.Focus()}Pola 2: Window Dokumen
Section titled “Pola 2: Window Dokumen”Multiple instances of the same window type:
type DocumentWindow struct { window *application.WebviewWindow filePath string modified bool}
var documents = make(map[string]*DocumentWindow)
func OpenDocument(app *application.App, filePath string) { // Check if already open if doc, exists := documents[filePath]; exists { doc.window.Show() doc.window.Focus() return }
// Create new document window window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: filepath.Base(filePath), Width: 800, Height: 600, })
doc := &DocumentWindow{ window: window, filePath: filePath, modified: false, }
documents[filePath] = doc
// Cleanup on close window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { delete(documents, filePath) })
// Load document loadDocument(window, filePath)}Pola 3: Tool Palette
Section titled “Pola 3: Tool Palette”Floating windows that stay on top:
func CreateToolPalette(app *application.App) *application.WebviewWindow { palette := app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "tools", Title: "Tools", Width: 200, Height: 400, AlwaysOnTop: true, DisableResize: true, })
return palette}Pattern 4: Dialog Modals (macOS only)
Section titled “Pattern 4: Dialog Modals (macOS only)”Child windows that block parent:
func ShowModaldialog(parent *application.WebviewWindow, title string) { dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 200, AlwaysOnTop: true, DisableResize: true, })
parent.AttachModal(dialog)}Pola 5: Window Inspector/Preview
Section titled “Pola 5: Window Inspector/Preview”Linked windows that update together:
type EditorApp struct { editor *application.WebviewWindow preview *application.WebviewWindow}
func (e *EditorApp) UpdatePreview(content string) { if e.preview != nil && e.preview.IsVisible() { e.preview.EmitEvent("content-changed", content) }}
func (e *EditorApp) TogglePreview() { if e.preview == nil { e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "preview", Title: "Preview", Width: 600, Height: 800, })
e.preview.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { e.preview = nil }) }
if e.preview.IsVisible() { e.preview.Hide() } else { e.preview.Show() }}Relasi Parent-Child
Section titled “Relasi Parent-Child”Membuat Window Child
Section titled “Membuat Window Child”childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Child Window",})
parentWindow.AttachModal(childWindow)Behaviour:
- Child stays above parent
- Child moves with parent
- Child blocks interaction to parent
Platform support:
| macOS | Windows | Linux |
|---|---|---|
| ✅ | ❌ | ❌ |
Perilaku Modal
Section titled “Perilaku Modal”Create modal-like behaviour:
func ShowModaldialog(parent *application.WebviewWindow, title string) { dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: title, Width: 400, Height: 200, })
parent.AttachModal(dialog)}Manajemen Siklus Hidup Window
Section titled “Manajemen Siklus Hidup Window”Callback Pembuatan
Section titled “Callback Pembuatan”Be notified when windows are created:
app.Window.OnCreate(func(window application.Window) { fmt.Printf("Window created: %s\n", window.Name())
// Configure all new windows window.SetMinSize(400, 300)})Callback Penghancuran
Section titled “Callback Penghancuran”Cleanup when windows are destroyed:
window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { fmt.Printf("Window %s is closing\n", window.Name())
// Cleanup resources cleanup(window.ID())
// Remove from tracking removeFromRegistry(window.Name())})Perilaku Quit Aplikasi
Section titled “Perilaku Quit Aplikasi”Control when application quits:
app := application.New(application.Options{ Mac: application.MacOptions{ // Don't quit when last window closes ApplicationShouldTerminateAfterLastWindowClosed: false, },})Kasus penggunaans:
- System tray applications
- Background services
- Menu bar applications (macOS)
Manajemen Memori
Section titled “Manajemen Memori”Mencegah Kebocoran
Section titled “Mencegah Kebocoran”Always clean up window references:
var windows = make(map[string]*application.WebviewWindow)
func CreateWindow(name string) { window := app.Window.New() windows[name] = window
// IMPORTANT: Clean up on close window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { delete(windows, name) })}Menutup Window
Section titled “Menutup Window”// Close — dispatches WindowClosing; a RegisterHook can call e.Cancel().window.Close()There is no window.Destroy() in v3. To prevent closing, use RegisterHook(events.Common.WindowClosing, ...) and call event.Cancel().
Pembersihan Resource
Section titled “Pembersihan Resource”type ManagedWindow struct { window *application.WebviewWindow resources []io.Closer}
func (mw *ManagedWindow) Destroy() { // Close all resources for _, resource := range mw.resources { resource.Close() }
// Close the window — WindowClosing listeners run before the window goes away. mw.window.Close()}Pola Lanjutan
Section titled “Pola Lanjutan”Pool Window
Section titled “Pool Window”Reuse windows instead of creating new ones:
type WindowPool struct { available []*application.WebviewWindow inUse map[uint]*application.WebviewWindow mu sync.Mutex}
func (wp *WindowPool) Acquire() *application.WebviewWindow { wp.mu.Lock() defer wp.mu.Unlock()
// Reuse available window if len(wp.available) > 0 { window := wp.available[0] wp.available = wp.available[1:] wp.inUse[window.ID()] = window return window }
// Create new window window := app.Window.New() wp.inUse[window.ID()] = window return window}
func (wp *WindowPool) Release(window *application.WebviewWindow) { wp.mu.Lock() defer wp.mu.Unlock()
delete(wp.inUse, window.ID()) window.Hide() wp.available = append(wp.available, window)}Grup Window
Section titled “Grup Window”Manage related windows together:
type WindowGroup struct { name string windows []*application.WebviewWindow}
func (wg *WindowGroup) Add(window *application.WebviewWindow) { wg.windows = append(wg.windows, window)}
func (wg *WindowGroup) ShowAll() { for _, window := range wg.windows { window.Show() }}
func (wg *WindowGroup) HideAll() { for _, window := range wg.windows { window.Hide() }}
func (wg *WindowGroup) CloseAll() { for _, window := range wg.windows { window.Close() }}Manajemen Workspace
Section titled “Manajemen Workspace”Save and restore window layouts:
type WindowLayout struct { Windows []WindowState `json:"windows"`}
type WindowState struct { Name string `json:"name"` X int `json:"x"` Y int `json:"y"` Width int `json:"width"` Height int `json:"height"`}
func SaveLayout() *WindowLayout { layout := &WindowLayout{}
for _, window := range app.Window.GetAll() { x, y := window.Position() width, height := window.Size()
layout.Windows = append(layout.Windows, WindowState{ Name: window.Name(), X: x, Y: y, Width: width, Height: height, }) }
return layout}
func RestoreLayout(layout *WindowLayout) { for _, state := range layout.Windows { if window, ok := app.Window.GetByName(state.Name); ok { window.SetPosition(state.X, state.Y) window.SetSize(state.Width, state.Height) } }}Contoh Lengkap
Section titled “Contoh Lengkap”Here’s a production-ready multi-window application:
package main
import ( "encoding/json" "os" "sync"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events")
type MultiWindowApp struct { app *application.App windows map[string]*application.WebviewWindow mu sync.RWMutex}
func main() { mwa := &MultiWindowApp{ windows: make(map[string]*application.WebviewWindow), }
mwa.app = application.New(application.Options{ Name: "Multi-Window Application", Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: false, }, })
// Create main window mwa.CreateMainWindow()
// Load saved layout mwa.LoadLayout()
mwa.app.Run()}
func (mwa *MultiWindowApp) CreateMainWindow() { window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "main", Title: "Main Application", Width: 1200, Height: 800, })
mwa.RegisterWindow("main", window)}
func (mwa *MultiWindowApp) ShowSettings() { if window := mwa.GetWindow("settings"); window != nil { window.Show() window.Focus() return }
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: "settings", Title: "Settings", Width: 600, Height: 400, })
mwa.RegisterWindow("settings", window)}
func (mwa *MultiWindowApp) OpenDocument(path string) { name := "doc-" + path
if window := mwa.GetWindow(name); window != nil { window.Show() window.Focus() return }
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ Name: name, Title: path, Width: 800, Height: 600, })
mwa.RegisterWindow(name, window)}
func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) { mwa.mu.Lock() mwa.windows[name] = window mwa.mu.Unlock()
window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { mwa.UnregisterWindow(name) })}
func (mwa *MultiWindowApp) UnregisterWindow(name string) { mwa.mu.Lock() delete(mwa.windows, name) mwa.mu.Unlock()}
func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow { mwa.mu.RLock() defer mwa.mu.RUnlock() return mwa.windows[name]}
func (mwa *MultiWindowApp) SaveLayout() { layout := make(map[string]WindowState)
mwa.mu.RLock() for name, window := range mwa.windows { x, y := window.Position() width, height := window.Size()
layout[name] = WindowState{ X: x, Y: y, Width: width, Height: height, } } mwa.mu.RUnlock()
data, _ := json.Marshal(layout) os.WriteFile("layout.json", data, 0644)}
func (mwa *MultiWindowApp) LoadLayout() { data, err := os.ReadFile("layout.json") if err != nil { return }
var layout map[string]WindowState if err := json.Unmarshal(data, &layout); err != nil { return }
for name, state := range layout { if window := mwa.GetWindow(name); window != nil { window.SetPosition(state.X, state.Y) window.SetSize(state.Width, state.Height) } }}
type WindowState struct { X int `json:"x"` Y int `json:"y"` Width int `json:"width"` Height int `json:"height"`}Praktik Terbaik
Section titled “Praktik Terbaik”✅ Lakukan
Section titled “✅ Lakukan”- Track windows - Keep references for easy access
- Clean up on destroy - Prevent memory leaks
- Use events for communication - Decoupled architecture
- Reuse windows - Don’t create duplicates
- Save/restore layouts - Better UX
- Handle window close - Confirm before closing with unsaved data
❌ Jangan
Section titled “❌ Jangan”- Don’t create unlimited windows - Memory and performance issues
- Don’t forget to clean up - Memory leaks
- Don’t use global variables carelessly - Thread-safety issues
- Don’t block window creation - Create asynchronously if needed
- Don’t ignore platform differences - Test on all platforms
Langkah Selanjutnya
Section titled “Langkah Selanjutnya”- Window Basics - Learn the fundamentals of window management
- Window Events - Handle window lifecycle events
- Events System - Deep dive into the event system
- Frameless Windows - Create custom window chrome
Pertanyaan? Tanyakan di Discord or lihat multi-window example.