Mehrere Fenster
Muster und Best Practices für Anwendungen mit mehreren Fenstern
Anwendungen mit mehreren Fenstern
Wails v3 bietet native Unterstützung für mehrere Fenster, um Einstellungs-, Dokument-, Werkzeugpaletten- und Inspektorfenster zu erstellen. Verfolgen Sie Fenster, ermöglichen Sie die Kommunikation zwischen ihnen und verwalten Sie ihren Lebenszyklus mit einfachen, einheitlichen APIs.
Haupt- und Einstellungsfenster
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 window
func (a *App) ShowSettings() {
if a.settingsWindow != nil {
a.settingsWindow.Show()
a.settingsWindow.Focus()
}
}Wichtige Punkte:
- Hauptfenster ist immer sichtbar
- Einstellungsfenster wird erstellt, bleibt aber ausgeblendet
- Einstellungen bei Bedarf anzeigen
- Dasselbe Fenster wiederverwenden (nicht mehrere erstellen)
Fensterverwaltung
Alle Fenster abrufen
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())
}Bestimmtes Fenster suchen
// By name
if settings, ok := app.Window.GetByName("settings"); ok {
settings.Show()
}
// By ID
if window, ok := app.Window.GetByID(123); ok {
window.Focus()
}
// Current (focused) window
current := app.Window.Current()Fensterregistrierungsmuster
Verwalten Sie die Fenster Ihrer Anwendung:
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)
}Fensterkommunikation
Ereignisse verwenden
Fenster kommunizieren über das Ereignissystem:
// In main window - emit event
app.Event.Emit("settings-changed", map[string]interface{}{
"theme": "dark",
"fontSize": 14,
})
// In settings window - listen for event
app.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)
})Muster für gemeinsamen Zustand
Verwenden Sie eine gemeinsame Zustandsverwaltung:
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
}Nachrichten zwischen Fenstern
Senden Sie Nachrichten zwischen bestimmten Fenstern:
// Get target window
if targetWindow, ok := app.Window.GetByName("preview"); ok {
// Emit event to specific window
targetWindow.EmitEvent("update-preview", previewData)
}Gängige Muster
Muster 1: Singleton-Fenster
Stellen Sie sicher, dass nur eine Instanz eines Fensters vorhanden ist:
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()
}Muster 2: Dokumentfenster
Mehrere Instanzen desselben Fenstertyps:
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)
}Muster 3: Werkzeugpaletten
Schwebende Fenster, die im Vordergrund bleiben:
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
}Muster 4: Modale Dialoge (nur macOS)
Untergeordnete Fenster, die das übergeordnete Fenster blockieren:
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)
}Muster 5: Inspektor-/Vorschaufenster
Verknüpfte Fenster, die gemeinsam aktualisiert werden:
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()
}
}Über- und untergeordnete Fenster
Untergeordnete Fenster erstellen
childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Child Window",
})
parentWindow.AttachModal(childWindow)Verhalten:
- Das untergeordnete Fenster bleibt über dem übergeordneten Fenster
- Das untergeordnete Fenster bewegt sich mit dem übergeordneten Fenster
- Das untergeordnete Fenster blockiert die Interaktion mit dem übergeordneten Fenster
Plattformunterstützung:
| macOS | Windows | Linux |
|---|---|---|
| ✅ | ❌ | ❌ |
Modales Verhalten
Erstellen Sie ein modalähnliches Verhalten:
func ShowModaldialog(parent *application.WebviewWindow, title string) {
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: title,
Width: 400,
Height: 200,
})
parent.AttachModal(dialog)
}Fensterlebenszyklus verwalten
Callbacks bei der Erstellung
Lassen Sie sich benachrichtigen, wenn Fenster erstellt werden:
app.Window.OnCreate(func(window application.Window) {
fmt.Printf("Window created: %s\n", window.Name())
// Configure all new windows
window.SetMinSize(400, 300)
})Callbacks bei der Zerstörung
Bereinigen Sie Ressourcen, wenn Fenster zerstört werden:
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())
})Verhalten beim Beenden der Anwendung
Steuern Sie, wann die Anwendung beendet wird:
app := application.New(application.Options{
Mac: application.MacOptions{
// Don't quit when last window closes
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})Anwendungsfälle:
- Infobereichsanwendungen
- Hintergrunddienste
- Menüleistenanwendungen (macOS)
Speicherverwaltung
Speicherlecks verhindern
Bereinigen Sie stets alle Fensterreferenzen:
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)
})
}Fenster schließen
// Close — dispatches WindowClosing; a RegisterHook can call e.Cancel().
window.Close()In v3 gibt es kein window.Destroy(). Um das Schließen zu verhindern, verwenden Sie RegisterHook(events.Common.WindowClosing, ...) und rufen event.Cancel() auf.
Ressourcenbereinigung
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()
}Fortgeschrittene Muster
Fensterpool
Verwenden Sie Fenster wieder, anstatt neue zu erstellen:
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)
}Fenstergruppen
Verwalten Sie zusammengehörige Fenster gemeinsam:
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()
}
}Arbeitsbereichsverwaltung
Fensteranordnungen speichern und wiederherstellen:
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)
}
}
}Vollständiges Beispiel
Hier ist eine produktionsreife Anwendung mit mehreren Fenstern:
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"`
}Bewährte Vorgehensweisen
✅ Empfohlen
- Fenster nachverfolgen – Referenzen für einfachen Zugriff aufbewahren
- Beim Zerstören bereinigen – Speicherlecks verhindern
- Ereignisse zur Kommunikation verwenden – Entkoppelte Architektur
- Fenster wiederverwenden – Keine Duplikate erstellen
- Fensteranordnungen speichern und wiederherstellen – Bessere Benutzerfreundlichkeit
- Das Schließen von Fenstern behandeln – Bei nicht gespeicherten Daten vor dem Schließen eine Bestätigung anfordern
❌ Nicht empfohlen
- Nicht unbegrenzt viele Fenster erstellen – Speicher- und Leistungsprobleme
- Bereinigung nicht vergessen – Speicherlecks
- Globale Variablen nicht unbedacht verwenden – Probleme mit der Threadsicherheit
- Fenstererstellung nicht blockieren – Bei Bedarf asynchron erstellen
- Plattformunterschiede nicht ignorieren – Auf allen Plattformen testen
Nächste Schritte
- Fenstergrundlagen – Grundlagen der Fensterverwaltung erlernen
- Fensterereignisse – Ereignisse im Fensterlebenszyklus behandeln
- Ereignissystem – Das Ereignissystem eingehend kennenlernen
- Rahmenlose Fenster – Benutzerdefinierte Fensterdekoration erstellen
Fragen? Fragen Sie in Discord nach oder sehen Sie sich das Beispiel für mehrere Fenster an.