Appkit: 菜单是如何工作的

引言

大家好, 我是一牛,很高兴又和大家见面了。在开发Mac应用时,菜单作为用户与应用交互的入口,发挥着不可或缺的作用。今天让我们来学习菜单的基础知识 - 菜单是如何工作的。

菜单分类

AppKit , NSMenu 和 NSMenu 是构建各类菜单的基础组件。NSMenu的实例管理它包含的菜单项和如何绘制这些菜单项。而NSMenuItem的实例代表单个菜单项,包含绘制信息和状态,但不直接处理绘制和事件。

Mac应用中,有以下几类。

  • 应用菜单 - 在屏幕的最上方
  • 弹出式菜单 - 可出现在窗口的任何位置
  • 状态栏菜单 - 适用于没有主窗口的应用
  • 上下文菜单 - 用户右键点击视图时出现
  • 程序坞菜单 - 右键点击程序坞应用图标时出现

在常规Mac应用开发中,最常用的是应用菜单栏、上下文菜单和弹出式菜单。

创建上下文菜单示例

objective-c 复制代码
// 创建上下文菜单
-(NSMenu *)contextMenu {
    NSMenu *contextMenu = [[NSMenu alloc] initWithTitle:@"上下文菜单"];
    [contextMenu addItemWithTitle:@"复制" action:@selector(copyAction:) keyEquivalent:@""];
    [contextMenu addItemWithTitle:@"粘贴" action:@selector(pasteAction:) keyEquivalent:@""];
    [contextMenu addItem:[NSMenuItem separatorItem]];
    [contextMenu addItemWithTitle:@"删除" action:@selector(deleteAction:) keyEquivalent:@""];
    return contextMenu;
}
// 弹出上下文菜单
-(void)rightMouseDown:(NSEvent *)event {
    NSLog(@"%@",self.view.window.firstResponder);
    [NSMenu popUpContextMenu:[self contextMenu] withEvent:event forView:self.view];
}

NSButton类似,这里没有设置动作消息的目标,因此当右键点击视图弹出菜单时,第一个处理消息的响应者就是该视图。如果视图不处理该消息,消息会沿着响应者链传递,直到有响应者处理或最终被丢弃。

菜单项状态

菜单项有三种状态,NSOnStateNSOffStateNSMixedState。如果菜单项的状态是NSOnState,那么该菜单项旁边会出现勾选标志;如果菜单项的状态是NSOffState,该菜单项不会显示任何图标;如果该菜单项是和NSMixedState, 那么该菜单项旁边会出现一个短横线。对于一个整体中只有部分是一种状态时,NSMixedState非常有用。我们可以使用代码设置菜单项的状态。

objective-c 复制代码
[menuItem setState:NSMixedState];

菜单项启用机制

  • 自动启用

    当菜单能找到响应菜单项动作消息的对象时,菜单项会自动启用。如需更精细的控制,可以实现validateUserInterfaceItem方法。

  • 手动启动

    使用setEnabled:方法可单独启用或禁用菜单项:

默认情况下,菜单会自动启用或禁用其包含的菜单项。可通过setAutoenablesItems:切换为手动模式。

自动启用菜单的工作机制

当自动启用生效时,用户事件(如点击菜单)会触发菜单项的自动更新。系统会检查:

  1. 菜单项的目标是否实现了对应动作方法
  2. 目标是否实现了validateUserInterfaceItem:协议方法

具体规则:

  1. 当设置了菜单项的目标后,菜单首先会检查该目标有没有实现菜单项的动作消息。如果没有实现,那么该菜单项会被禁用。如果已经实现了,菜单会检查该目标是否实现了validateUserInterfaceItem:。如果没有实现这个方法,菜单项被启用。如果实现了这个方法,会根据该方法返回的布尔值决定菜单项的启用或者禁用
  2. 如果菜单项的目标没有设置,并且菜单不是上下文菜单时,这个时候响应者链会启用。菜单会使用响应者链找到响应动作消息的目标。如果在响应者链没有找到目标,该菜单项会被禁用。如果找到了目标,和前面一条规则一样,会检查该目标是否实现了validateUserInterfaceItem:
  3. 当菜单项没有设置目标且菜单是上下文菜单,响应者链会从菜单关联的视图开始。后续处理流程和规则2一样。

我们创建一个包含Storyboard新工程,给Help 菜单增加一个菜单项-打开帮助网站,并且将它的动作消息连接到第一响应者的openHelpLink(点击第一响应者,增加一个动作方法)。我们可以AppDelegate中实现

objective-c 复制代码
-(void)openHelpLink:(id)sender {
    NSLog(@"打开帮助文档");
}
-(BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
  // 根据需要启用还是禁用菜单项
    return true;
}

由于该菜单是应用菜单,且它的目标没有设置,它会从第一响应者开始,使用响应者链来寻找实现动作消息的目标。此时由于我们没有设置第一响应者,第一响应者默认是窗口。最终消息会沿着响应者链传递到App Delegate,菜单项自动启用。

结语

通过本文,我们深入了解了Mac应用开发中菜单系统的工作原理和实现方式。掌握这些知识将帮助您创建更专业、更符合用户期待的Mac应用程序。同时我们也要掌握好响应者链的知识,进而掌握自动启用菜单项。希望这些内容对您的开发工作有所帮助!

相关推荐
I烟雨云渊T1 小时前
iOS 门店营收表格功能的实现
ios
weixin_387545641 小时前
如何把 Mac Finder 用得更顺手?——高效文件管理定制指南
macos
itme2685 小时前
解决cocos 2dx/creator2.4在ios18下openURL无法调用的问题
macos·objective-c·cocoa
明月看潮生7 小时前
青少年编程与数学 01-011 系统软件简介 07 iOS操作系统
ios·青少年编程·操作系统·系统软件
90后的晨仔9 小时前
RxSwift 框架解析
前端·ios
qq_2794561510 小时前
CADisplayLink、NSTimer、GCD定时器
macos·objective-c·cocoa
可爱小仙子13 小时前
ios苹果系统,js 滑动屏幕、锚定无效
前端·javascript·ios
未来猫咪花14 小时前
# Flutter状态管理对比:view_model vs Riverpod
flutter·ios·android studio
RR133515 小时前
macOS 升级 bash 到最新版本
macos
我在北京coding15 小时前
Tableau for mac 驱动
macos