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

如何更便捷地监听Xamarin.Mac对象的事件?

问题

我正在使用纯C#和Xamarin.Mac开发一款菜单栏应用(不使用Xcode及Storyboard),发现监听NSMenu对象事件的现有方式较为繁琐。目前我是通过以下代码实现事件监听:

public class MenuDelegate : NSMenuDelegate {
    public class HighlightChangedEventArgs : EventArgs {
        public NSMenuItem? Item { get; set; }
    }
    public event EventHandler<HighlightChangedEventArgs?>? HighlightChanged;
    public override void MenuWillHighlightItem(NSMenu menu, NSMenuItem item) { HighlightChanged.Invoke(menu, new HighlightChangedEventArgs() { Item = item }); }
    public event EventHandler? Opened;
    public override void MenuWillOpen(NSMenu menu) { Opened.Invoke(menu, null); }
    public event EventHandler? Closed;
    public override void MenuDidClose(NSMenu menu) { Closed.Invoke(menu,null); }
}

var menuDelegate = new MenuDelegate();
sbIcon.Menu.Delegate = menuDelegate;
menuDelegate.HighlightChanged += (object sender, MenuDelegate.HighlightChangedEventArgs args) => {
    // 处理逻辑
};

但这种需要单独定义Delegate类的方式太繁琐,想知道有没有更简便的实现方案?


回答

方案1:直接使用匿名NSMenuDelegate子类

Xamarin.Mac对NSDelegate类型提供了语法糖支持,可以直接通过匿名子类的方式重写方法并绑定处理逻辑,省去单独定义Delegate类的步骤:

sbIcon.Menu.Delegate = new NSMenuDelegate {
    MenuWillHighlightItem = (menu, item) => {
        // 处理菜单高亮事件
        Console.WriteLine($"高亮菜单选项:{item.Title}");
    },
    MenuWillOpen = (menu) => {
        // 处理菜单打开事件
        Console.WriteLine("菜单即将打开");
    },
    MenuDidClose = (menu) => {
        // 处理菜单关闭事件
        Console.WriteLine("菜单已关闭");
    }
};

这种方式直接把事件逻辑嵌入到Delegate赋值中,代码更紧凑,不需要额外定义事件类和Invoke逻辑。

方案2:封装扩展方法实现复用

如果需要在多个菜单中复用类似的监听逻辑,可以封装一个扩展方法,将事件处理逻辑作为参数传入:

public static class NSMenuExtensions {
    public static void AttachMenuHandlers(this NSMenu menu, 
        Action<NSMenu, NSMenuItem>? onItemHighlighted = null,
        Action<NSMenu>? onMenuOpened = null,
        Action<NSMenu>? onMenuClosed = null)
    {
        menu.Delegate = new NSMenuDelegate {
            MenuWillHighlightItem = (m, item) => onItemHighlighted?.Invoke(m, item),
            MenuWillOpen = m => onMenuOpened?.Invoke(m),
            MenuDidClose = m => onMenuClosed?.Invoke(m)
        };
    }
}

// 使用时只需调用扩展方法,传入对应逻辑
sbIcon.Menu.AttachMenuHandlers(
    onItemHighlighted: (menu, item) => {
        // 处理高亮逻辑
    },
    onMenuOpened: menu => {
        // 处理打开逻辑
    },
    onMenuClosed: menu => {
        // 处理关闭逻辑
    }
);

这种方式既简化了单次使用的代码,又能实现逻辑复用,避免重复编写Delegate类。

方案3:弱引用Delegate避免内存泄漏(可选)

如果菜单对象生命周期较长,需要注意Delegate持有外部对象导致的内存泄漏问题,可以用弱引用封装Delegate:

public class WeakMenuDelegate : NSMenuDelegate {
    private readonly WeakReference<Action<NSMenu, NSMenuItem>> _onHighlight;
    private readonly WeakReference<Action<NSMenu>> _onOpen;
    private readonly WeakReference<Action<NSMenu>> _onClose;

    public WeakMenuDelegate(Action<NSMenu, NSMenuItem>? onHighlight = null, 
                           Action<NSMenu>? onOpen = null, 
                           Action<NSMenu>? onClose = null)
    {
        _onHighlight = new WeakReference<Action<NSMenu, NSMenuItem>>(onHighlight);
        _onOpen = new WeakReference<Action<NSMenu>>(onOpen);
        _onClose = new WeakReference<Action<NSMenu>>(onClose);
    }

    public override void MenuWillHighlightItem(NSMenu menu, NSMenuItem item) {
        if (_onHighlight.TryGetTarget(out var action))
            action(menu, item);
    }

    public override void MenuWillOpen(NSMenu menu) {
        if (_onOpen.TryGetTarget(out var action))
            action(menu);
    }

    public override void MenuDidClose(NSMenu menu) {
        if (_onClose.TryGetTarget(out var action))
            action(menu);
    }
}

// 使用方式
sbIcon.Menu.Delegate = new WeakMenuDelegate(
    onHighlight: (menu, item) => { /* 处理逻辑 */ },
    onOpen: menu => { /* 处理逻辑 */ },
    onClose: menu => { /* 处理逻辑 */ }
);

这种方式适合需要长期监听且对内存占用敏感的场景,避免Delegate持有外部对象导致的内存无法回收。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 17:09:58