You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为同一应用的不同活动窗口设置不同的NSMenu?

为不同活动窗口设置不同NSMenu的实现方案

要在Mac应用中为不同活动窗口配置不同的全局菜单,核心思路是监听窗口的激活状态变化,动态替换NSApplication的mainMenu属性。下面分步骤详细说明实现方式:

一、准备不同窗口对应的菜单实例

首先,为每个需要自定义菜单的窗口创建专属的NSMenu实例。可以在AppDelegate中定义懒加载的菜单对象,或者封装到窗口子类中。

示例代码(Swift):

import Cocoa

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var window1: NSWindow!
    var window2: NSWindow!
    var defaultMainMenu: NSMenu?
    
    // 窗口1的自定义菜单
    lazy var window1Menu: NSMenu = {
        let menu = NSMenu(title: "Window 1 Menu")
        
        // 添加菜单项
        let fileMenu = NSMenu(title: "File")
        fileMenu.addItem(withTitle: "Window 1 Save", action: #selector(saveWindow1), keyEquivalent: "s")
        menu.addItem(withTitle: "File", submenu: fileMenu)
        
        let editMenu = NSMenu(title: "Edit")
        editMenu.addItem(withTitle: "Window 1 Special Edit", action: #selector(specialEditWindow1), keyEquivalent: "e")
        menu.addItem(withTitle: "Edit", submenu: editMenu)
        
        return menu
    }()
    
    // 窗口2的自定义菜单
    lazy var window2Menu: NSMenu = {
        let menu = NSMenu(title: "Window 2 Menu")
        
        let fileMenu = NSMenu(title: "File")
        fileMenu.addItem(withTitle: "Window 2 Export", action: #selector(exportWindow2), keyEquivalent: "e")
        menu.addItem(withTitle: "File", submenu: fileMenu)
        
        let viewMenu = NSMenu(title: "View")
        viewMenu.addItem(withTitle: "Window 2 Toggle Preview", action: #selector(togglePreviewWindow2), keyEquivalent: "p")
        menu.addItem(withTitle: "View", submenu: viewMenu)
        
        return menu
    }()
    
    // 菜单对应的动作方法
    @objc func saveWindow1() {
        print("Saving Window 1 content")
    }
    
    @objc func specialEditWindow1() {
        print("Special edit for Window 1")
    }
    
    @objc func exportWindow2() {
        print("Exporting Window 2 content")
    }
    
    @objc func togglePreviewWindow2() {
        print("Toggling preview for Window 2")
    }
}

二、监听窗口激活通知,动态切换菜单

在应用启动时,保存默认的全局菜单,并注册监听窗口成为主窗口的通知,当窗口激活时替换mainMenu。

在AppDelegate的applicationDidFinishLaunching方法中添加:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // 保存默认菜单,用于恢复
    defaultMainMenu = NSApp.mainMenu
    
    // 创建两个测试窗口
    window1 = NSWindow(
        contentRect: NSRect(x: 100, y: 100, width: 400, height: 300),
        styleMask: [.titled, .closable, .resizable, .miniaturizable],
        backing: .buffered,
        defer: false
    )
    window1.title = "Window 1"
    window1.makeKeyAndOrderFront(nil)
    
    window2 = NSWindow(
        contentRect: NSRect(x: 600, y: 100, width: 400, height: 300),
        styleMask: [.titled, .closable, .resizable, .miniaturizable],
        backing: .buffered,
        defer: false
    )
    window2.title = "Window 2"
    window2.makeKeyAndOrderFront(nil)
    
    // 注册窗口激活通知
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(windowDidBecomeMain(_:)),
        name: NSWindow.didBecomeMainNotification,
        object: nil
    )
    
    // 注册窗口关闭通知,处理菜单恢复逻辑
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(windowWillClose(_:)),
        name: NSWindow.willCloseNotification,
        object: nil
    )
}

然后实现通知处理方法:

@objc func windowDidBecomeMain(_ notification: Notification) {
    guard let activeWindow = notification.object as? NSWindow else { return }
    
    // 根据当前激活窗口切换菜单
    switch activeWindow {
    case window1:
        NSApp.mainMenu = window1Menu
    case window2:
        NSApp.mainMenu = window2Menu
    default:
        // 其他窗口恢复默认菜单
        NSApp.mainMenu = defaultMainMenu
    }
}

@objc func windowWillClose(_ notification: Notification) {
    guard let closedWindow = notification.object as? NSWindow else { return }
    
    // 如果关闭的是当前主窗口,切换到其他可见窗口的菜单或默认菜单
    if closedWindow.isMainWindow {
        if let nextMainWindow = NSApp.windows.first(where: { $0 != closedWindow && $0.isVisible }) {
            // 手动触发菜单切换
            windowDidBecomeMain(Notification(name: NSWindow.didBecomeMainNotification, object: nextMainWindow))
        } else {
            NSApp.mainMenu = defaultMainMenu
        }
    }
}

三、更封装的实现方式(自定义NSWindow子类)

如果你的应用有大量自定义窗口,推荐将菜单逻辑封装到NSWindow子类中,让窗口自身管理菜单切换:

class CustomMenuWindow: NSWindow {
    // 为窗口绑定专属菜单
    var customMenu: NSMenu?
    
    override func becomeMain() {
        super.becomeMain()
        // 当窗口成为主窗口时,替换应用菜单
        if let menu = customMenu {
            NSApp.mainMenu = menu
        }
    }
}

然后创建窗口时直接绑定菜单:

// 在applicationDidFinishLaunching中替换窗口创建代码
window1 = CustomMenuWindow(
    contentRect: NSRect(x: 100, y: 100, width: 400, height: 300),
    styleMask: [.titled, .closable, .resizable, .miniaturizable],
    backing: .buffered,
    defer: false
)
window1.title = "Window 1"
window1.customMenu = window1Menu
window1.makeKeyAndOrderFront(nil)

window2 = CustomMenuWindow(
    contentRect: NSRect(x: 600, y: 100, width: 400, height: 300),
    styleMask: [.titled, .closable, .resizable, .miniaturizable],
    backing: .buffered,
    defer: false
)
window2.title = "Window 2"
window2.customMenu = window2Menu
window2.makeKeyAndOrderFront(nil)

这种方式让窗口和菜单的关联更清晰,代码更易维护。

注意事项

  • 确保在应用退出前移除通知观察者,避免内存泄漏(Swift中如果用default.addObserver(self, ...),在deinit中移除即可);
  • 当所有窗口都关闭时,记得恢复默认菜单,避免菜单为空;
  • 如果你的应用支持多文档(NSDocument),可以结合NSDocument的windowControllerDidLoadNib方法,为每个文档窗口绑定专属菜单。

内容的提问来源于stack exchange,提问作者sourav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:46:12