Mac開發基礎23-NSMenu

Mr.陳發表於2024-08-06

NSMenu 是 macOS 應用中的一個重要控制元件,用於建立應用程式的選單。這些選單通常出現在螢幕頂部的選單欄中,但也可以作為上下文選單出現。NSMenuNSMenuItem 協同工作,NSMenu 是選單容器,而 NSMenuItem 是選單項。本指南將詳細介紹 NSMenu 的常見 API 和基礎技巧。

基本使用

建立和初始化

Objective-C

#import <Cocoa/Cocoa.h>

// 建立一個 NSMenu 例項
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Main Menu"];

Swift

import Cocoa

// 建立一個 NSMenu 例項
let menu = NSMenu(title: "Main Menu")

建立選單項

Objective-C

// 建立選單項
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"First Item"
                                                  action:@selector(handleMenuAction:)
                                           keyEquivalent:@"1"];
// 設定選單項的目標物件
[menuItem setTarget:self];

// 將選單項新增到選單
[menu addItem:menuItem];

Swift

// 建立選單項
let menuItem = NSMenuItem(title: "First Item",
                          action: #selector(handleMenuAction(_:)),
                          keyEquivalent: "1")
// 設定選單項的目標物件
menuItem.target = self

// 將選單項新增到選單
menu.addItem(menuItem)

響應選單項選擇

Objective-C

// 實現選單項選擇的響應方法
- (void)handleMenuAction:(id)sender {
    NSMenuItem *selectedItem = (NSMenuItem *)sender;
    NSLog(@"Selected item: %@", selectedItem.title);
}

Swift

// 實現選單項選擇的響應方法
@objc func handleMenuAction(_ sender: Any?) {
    if let menuItem = sender as? NSMenuItem {
        print("Selected item: \(menuItem.title)")
    }
}

建立帶有子選單的選單項

Objective-C

// 建立子選單
NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@"Sub Menu"];

// 建立子選單項
NSMenuItem *subMenuItem = [[NSMenuItem alloc] initWithTitle:@"Sub Item"
                                                     action:@selector(handleSubMenuAction:)
                                              keyEquivalent:@""];
[subMenuItem setTarget:self];
[subMenu addItem:subMenuItem];

// 建立帶有子選單的選單項
NSMenuItem *mainMenuItem = [[NSMenuItem alloc] initWithTitle:@"Main Item"
                                                      action:NULL
                                               keyEquivalent:@""];
[menu addItem:mainMenuItem];
[menu setSubmenu:subMenu forItem:mainMenuItem];

Swift

// 建立子選單
let subMenu = NSMenu(title: "Sub Menu")

// 建立子選單項
let subMenuItem = NSMenuItem(title: "Sub Item", action: #selector(handleSubMenuAction(_:)), keyEquivalent: "")
subMenuItem.target = self
subMenu.addItem(subMenuItem)

// 建立帶有子選單的選單項
let mainMenuItem = NSMenuItem(title: "Main Item", action: nil, keyEquivalent: "")
menu.addItem(mainMenuItem)
menu.setSubmenu(subMenu, for: mainMenuItem)

動態更新選單項

Objective-C

// 更新選單項狀態
- (void)updateMenuItems {
    NSMenuItem *menuItem = [menu itemWithTitle:@"First Item"];
    if (menuItem) {
        [menuItem setState:NSControlStateValueOn]; // 或者使用 NSControlStateValueOff
    }
}

Swift

// 更新選單項狀態
func updateMenuItems() {
    if let menuItem = menu.item(withTitle: "First Item") {
        menuItem.state = .on // 或者使用 .off
    }
}

使用委託

NSMenuDelegate 可以幫助處理選單的顯示和隱藏等事件。

Objective-C

// 設定代理
[menu setDelegate:self];

// 實現代理方法,處理選單將顯示事件
- (void)menuWillOpen:(NSMenu *)menu {
    NSLog(@"Menu will open");
}

// 實現代理方法,處理選單將隱藏事件
- (void)menuDidClose:(NSMenu *)menu {
    NSLog(@"Menu did close");
}

Swift

// 設定代理
menu.delegate = self

// 實現代理方法,處理選單將顯示事件
func menuWillOpen(_ menu: NSMenu) {
    print("Menu will open")
}

// 實現代理方法,處理選單將隱藏事件
func menuDidClose(_ menu: NSMenu) {
    print("Menu did close")
}

高階用法

動態生成選單項

Objective-C

// 動態生成選單項
- (void)generateDynamicMenuItems {
    NSArray *items = @[@"Dynamic Item 1", @"Dynamic Item 2", @"Dynamic Item 3"];
    for (NSString *itemTitle in items) {
        NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:itemTitle
                                                          action:@selector(handleDynamicMenuAction:)
                                                   keyEquivalent:@""];
        [menuItem setTarget:self];
        [menu addItem:menuItem];
    }
}

Swift

// 動態生成選單項
func generateDynamicMenuItems() {
    let items = ["Dynamic Item 1", "Dynamic Item 2", "Dynamic Item 3"]
    for itemTitle in items {
        let menuItem = NSMenuItem(title: itemTitle, action: #selector(handleDynamicMenuAction(_:)), keyEquivalent: "")
        menuItem.target = self
        menu.addItem(menuItem)
    }
}

上下文選單

建立右鍵上下文選單是 NSMenu 的常見用法。

Objective-C

// 建立上下文選單
NSMenu *contextMenu = [[NSMenu alloc] initWithTitle:@"Context Menu"];
NSMenuItem *contextMenuItem = [[NSMenuItem alloc] initWithTitle:@"Context Item"
                                                         action:@selector(handleContextMenuAction:)
                                                  keyEquivalent:@""];
[contextMenuItem setTarget:self];
[contextMenu addItem:contextMenuItem];

// 設定檢視的上下文選單
[someView setMenu:contextMenu];

Swift

// 建立上下文選單
let contextMenu = NSMenu(title: "Context Menu")
let contextMenuItem = NSMenuItem(title: "Context Item", action: #selector(handleContextMenuAction(_:)), keyEquivalent: "")
contextMenuItem.target = self
contextMenu.addItem(contextMenuItem)

// 設定檢視的上下文選單
someView.menu = contextMenu

禁用或啟用選單項

Objective-C

// 禁用選單項
- (void)disableMenuItem {
    NSMenuItem *menuItem = [menu itemWithTitle:@"First Item"];
    [menuItem setEnabled:NO]; // 設定選單項為禁用狀態
}

// 啟用選單項
- (void)enableMenuItem {
    NSMenuItem *menuItem = [menu itemWithTitle:@"First Item"];
    [menuItem setEnabled:YES]; // 設定選單項為啟用狀態
}

Swift

// 禁用選單項
func disableMenuItem() {
    if let menuItem = menu.item(withTitle: "First Item") {
        menuItem.isEnabled = false // 設定選單項為禁用狀態
    }
}

// 啟用選單項
func enableMenuItem() {
    if let menuItem = menu.item(withTitle: "First Item") {
        menuItem.isEnabled = true // 設定選單項為啟用狀態
    }
}

自動更新選單項狀態

透過實現 NSMenuDelegatemenu:updateItem:atIndex:shouldCancel: 方法,可以自動更新選單項狀態。

Objective-C

// 設定代理
[menu setDelegate:self];

// 實現代理方法,自動更新選單項狀態
- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel {
    item.state = (index % 2 == 0) ? NSControlStateValueOn : NSControlStateValueOff;
    return YES;
}

Swift

// 設定代理
menu.delegate = self

// 實現代理方法,自動更新選單項狀態
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool {
    item.state = (index % 2 == 0) ? .on : .off
    return true
}

封裝工具類

為了更方便地使用 NSMenu,可以封裝一個工具類,提供常見功能的高層介面。

Objective-C

#import <Cocoa/Cocoa.h>

@interface NSMenuHelper : NSObject

+ (NSMenu *)createMenuWithTitle:(NSString *)title items:(NSArray<NSDictionary<NSString *, NSString *> *> *)items target:(id)target;
+ (void)addItemWithTitle:(NSString *)title action:(SEL)action toMenu:(NSMenu *)menu target:(id)target;
+ (void)addSubMenuWithTitle:(NSString *)title toMenu:(NSMenu *)menu subMenu:(NSMenu *)subMenu;

@end

@implementation NSMenuHelper

+ (NSMenu *)createMenuWithTitle:(NSString *)title items:(NSArray<NSDictionary<NSString *, NSString *> *> *)items target:(id)target {
    NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
    for (NSDictionary<NSString *, NSString *> *item in items) {
        NSString *title = item.allKeys.firstObject;
        SEL action = NSSelectorFromString(item.allValues.firstObject);
        [NSMenuHelper addItemWithTitle:title action:action toMenu:menu target:target];
    }
    return menu;
}

+ (void)addItemWithTitle:(NSString *)title action:(SEL)action toMenu:(NSMenu *)menu target:(id)target {
    NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:title action:action keyEquivalent:@""];
    [menuItem setTarget:target];
    [menu addItem:menuItem];
}

+ (void)addSubMenuWithTitle:(NSString *)title toMenu:(NSMenu *)menu subMenu:(NSMenu *)subMenu {
    NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
    [menu addItem:menuItem];
    [menu setSubmenu:subMenu forItem:menuItem];
}

@end

Swift

import Cocoa

class NSMenuHelper {
    
    // 建立 Menu 並初始化選單項
    static func createMenu(title: String, items: [String: Selector], target: AnyObject) -> NSMenu {
        let menu = NSMenu(title: title)
        for (title, action) in items {
            addItem(withTitle: title, action: action, to: menu, target: target)
        }
        return menu
    }
    
    // 新增選單項
    static func addItem(withTitle title: String, action: Selector, to menu: NSMenu, target: AnyObject) {
        let menuItem = NSMenuItem(title: title, action: action, keyEquivalent: "")
        menuItem.target = target
        menu.addItem(menuItem)
    }
    
    // 新增子選單
    static func addSubMenu(withTitle title: String, to menu: NSMenu, subMenu: NSMenu) {
        let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
        menu.addItem(menuItem)
        menu.setSubmenu(subMenu, for: menuItem)
    }
}

使用示例

Objective-C

// 建立 Menu
NSArray *items = @[
    @{@"First Item": NSStringFromSelector(@selector(handleMenuAction:))},
    @{@"Second Item": NSStringFromSelector(@selector(handleMenuAction:))}
];
NSMenu *menu = [NSMenuHelper createMenuWithTitle:@"Main Menu" items:items target:self];

// 建立子選單
NSMenu *subMenu = [NSMenuHelper createMenuWithTitle:@"Sub Menu" items:@{@{@"Sub Item": NSStringFromSelector(@selector(handleSubMenuAction:))}} target:self];
[NSMenuHelper addSubMenuWithTitle:@"Main Item with SubMenu" toMenu:menu subMenu:subMenu];

Swift

// 建立 Menu
let items: [String: Selector] = [
    "First Item": #selector(handleMenuAction(_:)),
    "Second Item": #selector(handleMenuAction(_:))
]
let menu = NSMenuHelper.createMenu(title: "Main Menu", items: items, target: self)

// 建立子選單
let subMenuItems: [String: Selector] = [
    "Sub Item": #selector(handleSubMenuAction(_:))
]
let subMenu = NSMenuHelper.createMenu(title: "Sub Menu", items: subMenuItems, target: self)
NSMenuHelper.addSubMenu(withTitle: "Main Item with SubMenu", to: menu, subMenu: subMenu)

總結

透過了解 NSMenu 的基本使用、建立選單項、響應選單項選擇、建立子選單、動態更新選單項、使用上下文選單等高階用法,以及封裝工具類,你將能夠更高效地使用 NSMenu 建立複雜的選單系統。在實際應用中,合理使用這些技巧可以顯著提升使用者介面的靈活性和使用者體驗。。

相關文章