Menu Aplikasi

Masalahnya

Aplikasi desktop profesional memerlukan menu bar—File, Edit, View, Help. Namun menu bekerja berbeda di setiap platform:

  • macOS: Menu bar global di bagian atas layar
  • Windows: Menu bar di title bar window
  • Linux: Bervariasi menurut desktop environment

Membangun menu yang sesuai platform secara manual membosankan dan rentan error.

Solusi Wails

Wails menyediakan API terpadu yang membuat menu native platform secara otomatis. Tulis sekali, dapatkan perilaku native di semua platform.

Memulai Cepat

package main

import (
    "runtime"
    "github.com/wailsapp/wails/v3/pkg/application"
)

func main() {
    app := application.New(application.Options{
        Name: "My App",
    })

    // Create menu
    menu := app.NewMenu()

    // Add standard menus (platform-appropriate)
    if runtime.GOOS == "darwin" {
        menu.AddRole(application.AppMenu)  // macOS only
    }
    menu.AddRole(application.FileMenu)
    menu.AddRole(application.EditMenu)
    menu.AddRole(application.WindowMenu)
    menu.AddRole(application.HelpMenu)

    // Set the application menu
    app.Menu.Set(menu)

    // Create window with UseApplicationMenu to inherit the menu on Windows/Linux
    app.Window.NewWithOptions(application.WebviewWindowOptions{
        UseApplicationMenu: true,
    })

    app.Run()
}

Itu saja! Anda sekarang memiliki menu native platform dengan item standar. Opsi UseApplicationMenu memastikan window Windows dan Linux menampilkan menu tanpa kode tambahan.

Membuat Menu

Pembuatan Menu Dasar

// Create a new menu
menu := app.NewMenu()

// Add a top-level menu
fileMenu := menu.AddSubmenu("File")

// Add menu items
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
    // Handle New
})

fileMenu.Add("Open").OnClick(func(ctx *application.Context) {
    // Handle Open
})

fileMenu.AddSeparator()

fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
    app.Quit()
})

Mengatur Menu

Pendekatan yang direkomendasikan — Gunakan UseApplicationMenu untuk konsistensi lintas platform:

// Set the application menu once
app.Menu.Set(menu)

// Create windows that inherit the menu on Windows/Linux
app.Window.NewWithOptions(application.WebviewWindowOptions{
    UseApplicationMenu: true,  // Window uses the app menu
})

Pendekatan ini:

  • Di macOS: Menu muncul di bagian atas layar (perilaku standar)
  • Di Windows/Linux: Setiap window dengan UseApplicationMenu: true menampilkan menu aplikasi

Detail spesifik platform:

Menu bar global (satu per aplikasi):

app.Menu.Set(menu)

Menu muncul di bagian atas layar dan tetap ada bahkan saat semua window ditutup. Opsi UseApplicationMenu tidak berpengaruh di macOS karena semua aplikasi menggunakan menu global.

Menu kustom per window:

Jika window memerlukan menu berbeda dari menu aplikasi, atur langsung:

window.SetMenu(customMenu)  // Overrides UseApplicationMenu

Role Menu

Wails menyediakan role menu yang telah ditentukan yang membuat struktur menu sesuai platform secara otomatis.

Role yang Tersedia

Role Deskripsi Catatan Platform
AppMenu Menu aplikasi dengan About, Preferences, Quit macOS saja
FileMenu Operasi file (New, Open, Save, dll.) Semua platform
EditMenu Pengeditan teks (Undo, Redo, Cut, Copy, Paste) Semua platform
WindowMenu Manajemen window (Minimise, Zoom, dll.) Semua platform
HelpMenu Bantuan dan informasi Semua platform

Menggunakan Role

menu := app.NewMenu()

// macOS: Add application menu
if runtime.GOOS == "darwin" {
    menu.AddRole(application.AppMenu)
}

// All platforms: Add standard menus
menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)

Yang Anda dapatkan:

AppMenu (dengan nama app):

  • About [App Name]
  • Preferences… (⌘,)

  • Services

  • Hide [App Name] (⌘H)
  • Hide Others (⌥⌘H)
  • Show All

  • Quit [App Name] (⌘Q)

FileMenu:

  • New (⌘N)
  • Open… (⌘O)

  • Close Window (⌘W)

EditMenu:

  • Undo (⌘Z)
  • Redo (⇧⌘Z)

  • Cut (⌘X)
  • Copy (⌘C)
  • Paste (⌘V)
  • Select All (⌘A)

WindowMenu:

  • Minimise (⌘M)
  • Zoom

  • Bring All to Front

HelpMenu:

  • [App Name] Help

Menyesuaikan Menu Role

Menu.AddRole(role) mengembalikan menu receiver (menu tingkat atas), bukan submenu role. Untuk menambahkan item ke submenu role, cari item role yang dimasukkan dengan FindByRole dan panggil GetSubmenu():

menu.AddRole(application.FileMenu)

fileMenu := menu.FindByRole(application.FileMenu).GetSubmenu()
fileMenu.Add("Import...").OnClick(handleImport)
fileMenu.Add("Export...").OnClick(handleExport)

Buat menu sendiri untuk fitur spesifik aplikasi:

// Add a custom top-level menu
toolsMenu := menu.AddSubmenu("Tools")

// Add items
toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) {
    showSettingsWindow()
})

toolsMenu.AddSeparator()

// Add checkbox
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
    isDark := ctx.ClickedMenuItem().Checked()
    setTheme(isDark)
})

// Add radio group
toolsMenu.AddRadio("Small", true).OnClick(handleFontSize)
toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize)
toolsMenu.AddRadio("Large", false).OnClick(handleFontSize)

// Add submenu
advancedMenu := toolsMenu.AddSubmenu("Advanced")
advancedMenu.Add("Configure...").OnClick(showAdvancedSettings)

Untuk tipe item menu lainnya, lihat Referensi Menu.

Perbarui menu berdasarkan status aplikasi:

Aktifkan/Nonaktifkan Item

var saveMenuItem *application.MenuItem

func createMenu() {
    menu := app.NewMenu()
    fileMenu := menu.AddSubmenu("File")
    
    saveMenuItem = fileMenu.Add("Save")
    saveMenuItem.SetEnabled(false)  // Initially disabled
    saveMenuItem.OnClick(handleSave)
    
    app.Menu.Set(menu)
}

func onDocumentChanged() {
    saveMenuItem.SetEnabled(hasUnsavedChanges())
    menu.Update()  // Important!
}

Ubah Label

updateMenuItem := menu.Add("Check for Updates")

updateMenuItem.OnClick(func(ctx *application.Context) {
    updateMenuItem.SetLabel("Checking...")
    menu.Update()
    
    checkForUpdates()
    
    updateMenuItem.SetLabel("Check for Updates")
    menu.Update()
})

Rebuild Menu

Untuk perubahan besar, rebuild seluruh menu:

func rebuildFileMenu() {
    menu := app.NewMenu()
    fileMenu := menu.AddSubmenu("File")
    
    fileMenu.Add("New").OnClick(handleNew)
    fileMenu.Add("Open").OnClick(handleOpen)
    
    // Add recent files dynamically
    if hasRecentFiles() {
        recentMenu := fileMenu.AddSubmenu("Open Recent")
        for _, file := range getRecentFiles() {
            filePath := file  // Capture for closure
            recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) {
                openFile(filePath)
            })
        }
        recentMenu.AddSeparator()
        recentMenu.Add("Clear Recent").OnClick(clearRecentFiles)
    }
    
    fileMenu.AddSeparator()
    fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
        app.Quit()
    })
    
    app.Menu.Set(menu)
}

Kontrol Window dari Menu

Item menu dapat mengendalikan window:

viewMenu := menu.AddSubmenu("View")

// Toggle fullscreen
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
    if window, ok := app.Window.GetByName("main"); ok {
        window.ToggleFullscreen()
    }
})

// Zoom controls
viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) {
    // Increase zoom
})

viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) {
    // Decrease zoom
})

viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) {
    // Reset zoom
})

Dapatkan window aktif:

menuItem.OnClick(func(ctx *application.Context) {
    window := application.Get().Window.Current() // the window the menu was invoked from
    // Use window
})

Pertimbangan Spesifik Platform

macOS

Perilaku menu bar:

  • Muncul di bagian atas layar (global)
  • Tetap ada saat semua window ditutup
  • Menu pertama selalu menu aplikasi
  • Gunakan menu.AddRole(application.AppMenu) untuk item standar

Lokasi standar:

  • About: Menu aplikasi
  • Preferences: Menu aplikasi (⌘,)
  • Quit: Menu aplikasi (⌘Q)
  • Help: Menu Help

Contoh:

if runtime.GOOS == "darwin" {
    menu.AddRole(application.AppMenu)  // Adds About, Preferences, Quit
    
    // Don't add Quit to File menu on macOS
    // Don't add About to Help menu on macOS
}

Windows

Perilaku menu bar:

  • Muncul di title bar window
  • Setiap window memiliki menu sendiri
  • Tidak ada menu aplikasi

Lokasi standar:

  • Exit: Menu File (Alt+F4)
  • Settings: Menu Tools atau Edit
  • About: Menu Help

Contoh:

if runtime.GOOS == "windows" {
    menu.AddRole(application.FileMenu) // Exit is added automatically
    menu.AddRole(application.HelpMenu) // About is added automatically
}

Linux

Perilaku menu bar:

  • Biasanya per-window (seperti Windows)
  • Beberapa DE mendukung menu global (Unity, GNOME dengan extension)
  • Tampilan bervariasi menurut desktop environment

Praktik terbaik: Ikuti konvensi Windows, uji di DE target.

Contoh Lengkap

Berikut struktur menu siap produksi:

package main

import (
    "runtime"
    "github.com/wailsapp/wails/v3/pkg/application"
)

func main() {
    app := application.New(application.Options{
        Name: "My Application",
    })

    // Create and set menu
    createMenu(app)

    // Create main window with UseApplicationMenu for cross-platform menu support
    app.Window.NewWithOptions(application.WebviewWindowOptions{
        UseApplicationMenu: true,
    })

    app.Run()
}

func createMenu(app *application.App) {
    menu := app.NewMenu()

    // Platform-specific application menu (macOS only)
    if runtime.GOOS == "darwin" {
        menu.AddRole(application.AppMenu)
    }

    // File menu — AddRole returns the receiver menu, not the role submenu.
    // To add items into the File submenu, look it up via FindByRole + GetSubmenu.
    menu.AddRole(application.FileMenu)
    fileMenu := menu.FindByRole(application.FileMenu).GetSubmenu()
    fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport)
    fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport)

    // Edit menu
    menu.AddRole(application.EditMenu)

    // View menu
    viewMenu := menu.AddSubmenu("View")
    viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen)
    viewMenu.AddSeparator()
    viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar)
    viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar)

    // Tools menu
    toolsMenu := menu.AddSubmenu("Tools")
    
    // Settings location varies by platform
    if runtime.GOOS == "darwin" {
        // On macOS, Preferences is in Application menu (added by AppMenu role)
    } else {
        toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings)
    }
    
    toolsMenu.AddSeparator()
    toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode)

    // Window menu
    menu.AddRole(application.WindowMenu)

    // Help menu
    helpMenu := menu.AddRole(application.HelpMenu)
    helpMenu.Add("Documentation").OnClick(openDocumentation)
    
    // About location varies by platform
    if runtime.GOOS == "darwin" {
        // On macOS, About is in Application menu (added by AppMenu role)
    } else {
        helpMenu.AddSeparator()
        helpMenu.Add("About").OnClick(showAbout)
    }

    // Set the application menu
    app.Menu.Set(menu)
}

func handleImport(ctx *application.Context) {
    // Implementation
}

func handleExport(ctx *application.Context) {
    // Implementation
}

func toggleFullscreen(ctx *application.Context) {
    window := application.Get().Window.Current()
    window.ToggleFullscreen()
}

func toggleSidebar(ctx *application.Context) {
    // Implementation
}

func toggleToolbar(ctx *application.Context) {
    // Implementation
}

func showSettings(ctx *application.Context) {
    // Implementation
}

func toggleDarkMode(ctx *application.Context) {
    isDark := ctx.ClickedMenuItem().Checked()
    // Apply theme
}

func openDocumentation(ctx *application.Context) {
    // Open browser
}

func showAbout(ctx *application.Context) {
    // Show about dialog
}

Praktik Terbaik

✅ Lakukan

  • Gunakan role menu untuk menu standar (File, Edit, dll.)
  • Ikuti konvensi platform untuk struktur menu
  • Tambahkan keyboard shortcut ke aksi umum
  • Panggil menu.Update() setelah mengubah status menu
  • Uji di semua platform - perilaku bervariasi
  • Jaga menu dangkal - maksimum 2-3 level
  • Gunakan label jelas - “Save Project” bukan “Save”

❌ Jangan

  • Jangan hardcode shortcut platform - Gunakan CmdOrCtrl
  • Jangan taruh Quit di menu File di macOS - Ada di menu Application
  • Jangan taruh About di menu Help di macOS - Ada di menu Application
  • Jangan lupa menu.Update() - Menu tidak akan berfungsi dengan benar
  • Jangan nest terlalu dalam - Pengguna akan tersesat
  • Jangan gunakan jargon - Jaga label ramah pengguna

Langkah Selanjutnya

Referensi Menu

Referensi lengkap untuk tipe item menu dan properti.

Pelajari Selengkapnya →

Context Menu

Buat context menu klik kanan.

Pelajari Selengkapnya →

Menu System Tray

Tambahkan integrasi system tray/menu bar.

Pelajari Selengkapnya →

Pola Menu

Pola menu umum dan praktik terbaik.

Pelajari Selengkapnya →


Pertanyaan? Tanyakan di Discord atau lihat contoh menu.

Edit page

Last updated: