如何在 NSMenuItem 内绘制内联样式标签(或按钮)

2024-02-06

当 App Store 有更新时,它会在菜单项中显示一个内联样式元素,如下面屏幕截图中的“1 new”:

另一个我们可以看到这种菜单的地方是10.10 Yosemite的分享菜单。当您安装任何添加新共享扩展的应用程序时,共享菜单中的“更多”项目将显示“N new”,就像应用程序商店菜单一样。

“App Store...”项目看起来很正常NSMenuItem。有没有一种简单的方法来实现这一点,或者是否有任何 API 支持它,而无需为菜单项设置自定义视图?


“Cocoa”NSMenus 实际上完全基于 Carbon 构建,因此虽然 Cocoa API 没有公开太多功能,但您可以深入研究 Carbon-land 并获得更多功能。无论如何,这就是苹果所做的——苹果菜单项是从IBCarbonMenuItem,如下所示:

/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Resources/English.lproj/StandardMenus.nib/objects.xib

不幸的是,64 位 Carbon API 似乎充满了错误和缺失的功能,这使得安装工作绘制处理程序比 32 位版本困难得多。这是我想出的一个 hacky 版本:

#import <Carbon/Carbon.h>

OSStatus eventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData) {
  OSStatus ret = 0;

  if (GetEventClass(inEvent) == kEventClassMenu) {
    if (GetEventKind(inEvent) == kEventMenuDrawItem) {
      // draw the standard menu stuff
      ret = CallNextEventHandler(inHandlerRef, inEvent);

      MenuTrackingData tracking_data;
      GetMenuTrackingData(menuRef, &tracking_data);

      MenuItemIndex item_index;
      GetEventParameter(inEvent, kEventParamMenuItemIndex, typeMenuItemIndex, nil, sizeof(item_index), nil, &item_index);

      if (tracking_data.itemSelected == item_index) {
        HIRect item_rect;
        GetEventParameter(inEvent, kEventParamMenuItemBounds, typeHIRect, nil, sizeof(item_rect), nil, &item_rect);

        CGContextRef context;
        GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, nil, sizeof(context), nil, &context);

        // first REMOVE a state from the graphics stack, instead of pushing onto the stack
        // this is to remove the clipping and translation values that are completely useless without the context height value
        extern void *CGContextCopyTopGState(CGContextRef);
        void *state = CGContextCopyTopGState(context);

        CGContextRestoreGState(context);

        // draw our content on top of the menu item
        CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.5);
        CGContextFillRect(context, CGRectMake(0, item_rect.origin.y - tracking_data.virtualMenuTop, item_rect.size.width, item_rect.size.height));

        // and push a dummy graphics state onto the stack so the calling function can pop it again and be none the wiser
        CGContextSaveGState(context);
        extern void CGContextReplaceTopGState(CGContextRef, void *);
        CGContextReplaceTopGState(context, state);

        extern void CGGStateRelease(void *);
        CGGStateRelease(state);
      }
    }
  }
}

- (void)beginTracking:(NSNotification *)notification {
  // install a Carbon event handler to custom draw in the menu
  if (menuRef == nil) {
    extern MenuRef _NSGetCarbonMenu(NSMenu *);
    extern EventTargetRef GetMenuEventTarget(MenuRef);

    menuRef = _NSGetCarbonMenu(menu);
    if (menuRef == nil) return;

    EventTypeSpec events[1];
    events[0].eventClass = kEventClassMenu;
    events[0].eventKind = kEventMenuDrawItem;

    InstallEventHandler(GetMenuEventTarget(menuRef), NewEventHandlerUPP(&eventHandler), GetEventTypeCount(events), events, nil, nil);
  }

  if (menuRef != nil) {
    // set the kMenuItemAttrCustomDraw attrib on the menu item
    // this attribute is needed in order to receive the kMenuEventDrawItem event in the Carbon event handler
    extern OSStatus ChangeMenuItemAttributes(MenuRef, MenuItemIndex, MenuItemAttributes, MenuItemAttributes);
    ChangeMenuItemAttributes(menuRef, item_index, kMenuItemAttrCustomDraw, 0);
  }
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  menu = [[NSMenu alloc] initWithTitle:@""];

  // register for the BeginTracking notification so we can install our Carbon event handler as soon as the menu is constructed
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(beginTracking:) name:NSMenuDidBeginTrackingNotification object:menu];
}

首先它注册一个 BeginTracking 通知,如下_NSGetCarbonMenu仅在菜单构建后返回有效句柄,并且在绘制菜单之前调用 BeginTracking。

然后它使用通知回调来获取 Carbon MenuRef 并将标准 Carbon 事件处理程序附加到菜单。

通常我们可以简单地采取kEventParamMenuContextHeight事件参数并翻转 CGContextRef 并开始绘制,但该参数仅在 32 位模式下可用。 Apple 文档建议在当前端口的高度不可用时使用该值,但这也仅在 32 位模式下可用。

所以既然给我们的图形状态没有用,就把它从堆栈中弹出并使用之前的图形状态。事实证明,这个新状态被转换为菜单的虚拟顶部,可以使用以下命令检索该菜单GetMenuTrackingData.virtualMenuTop. The kEventParamVirtualMenuTop值在 64 位模式下也不正确,因此必须使用GetMenuTrackingData.

这是很hacky和荒谬的,但它比使用setView并重新实现整个菜单项行为要好。 OS X 上的菜单 API 是bit一团糟。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 NSMenuItem 内绘制内联样式标签(或按钮) 的相关文章

随机推荐

  • 东向北转纬度经度

    我有东向 北向格式的位置坐标 但我需要将其转换为正确的经纬度 以使其在 bing 地图中居中 有任何公式或详细信息如何将东距 北距转换为纬度 经度吗 编辑 更具体地说 我需要将 SVY21 坐标转换为 WGS84 东距和北距分别是基点向东和
  • EMR-5.32.0 上的 Spark 未生成请求的执行程序

    我在 EMR 版本 5 32 0 上的 Py Spark 中遇到了一些问题 大约一年前 我在 EMR 集群上运行了相同的程序 我认为版本一定是 5 29 0 然后我可以使用配置我的 PySpark 程序spark submit正确地论证 但
  • 正在验证 MVC 隐藏字段

    我的页面上有一些字段 它们的显示和消失取决于您在页面上所做的下拉选择 所以 举例来说 我有 section Html LabelFor model gt model AuctionTypeId div Html DropDownList A
  • 在我的下一个 Android 应用程序更新中使用新的数据库版本覆盖现有的已发布 Sqlite DB

    我想覆盖旧应用程序版本附带的现有数据库 并在下一个应用程序更新中使用新完全填充的数据库 然而 onUpgrade 永远不会被调用 尽管我尝试在将 DB version 传递给 SQLiteOpenHelper 类时更改它 public cl
  • FTDI 的 libMPSSE 上“遇到 NULL 表达式”

    我的问题是针对 FTDI 的 libMPSSE 库在 Linux 上与 USB 转串口 SPI I2C 等 适配器配合使用的问题 当我执行与该库链接的任何程序时 会调用方法 Init libMPSSE 无需显式调用 并抛出以下消息 Infr
  • 如何在 Python 中的 Opencv Cam 窗口中提供启动、停止、捕获和关闭按钮

    如何在视频捕获窗口中提供开始 停止 捕获和关闭按钮来启动 停止 拍摄快照 关闭窗口 我使用以下代码打开相机进行视频流 import cv2 cv as cv cv NamedWindow camera 1 capture cv Captur
  • 3ds Max .NET SDK 和创建参考制作器

    我有 Net DLL for Max 和 ui 我想对视口中某些节点的参数更改做出反应 我想到的最简单的解决方案是创建 ReferenceMaker 插件并为我想要观看的节点设置参考 根据文档应该是 public class Referen
  • Valgrind 导致长双精度数字问题

    我的代码中有以下函数 用于检查数字是否具有允许的值 在日志空间中 template
  • ASP.NET MVC 3 模型绑定资源

    我正在寻找一个很好的资源 它非常全面地描述了模型绑定如何与 ASP NET MVC 3 或在较小程度上 MVC 2 和不同的方法一起工作 除了零碎的内容之外 我找不到关于这个主题的任何好的资源 网上的信息更多的是关于 如何做 X 而不是解释
  • ASP.NET MVC3 - 您如何处理探测请求?

    我们的网站上线了 当然 我们开始收到大量的探测请求 喜欢 blog wp login php admin admin php etc 所以问题是 你用它们做什么 现在 在每种情况下都会抛出 404 错误 并且 elmah 会发送有关该错误的
  • 为什么我们不能在某个进程上接受()套接字并从其子进程中接收()数据?

    我正在尝试在 Linux 上实现一个简单的 Web 服务器 它连接到客户端 浏览器 接收来自客户端的一些请求 例如 GET 然后用所需的文件发回响应 我正在使用套接字通信 我想在服务器启动时创建一个工作进程 子进程 池 其工作是处理传入的请
  • 如何将肥皂基本身份验证请求添加到 WSDL

    我怎样才能对 WSDL 进行 Soap AUTH BASIC 身份验证 以便阅读 WSDL 的人知道我需要针对特定 方法进行该操作 使用下面的示例 我成功地将 SOAP 基本身份验证传递到另一端的 php Web 服务 PHP net So
  • PHP imageftbbox imagettftext - 简单的字母间距/字距调整?

    有谁知道使用 imagettftext 进行字母间距 字距调整的简单方法 我的脚本按照我现在的需要工作 但我确实可以使用具有 CSS 样式的生成文本 letter spacing 0 01em 所以它与页面上的标准文本相匹配 但我没有看到任
  • 如何选择列表中所有无序的元素?

    这个问题源于评论里的讨论这个答案 https stackoverflow com questions 1390832 how to sort nearly sorted array in the fastest time possible
  • 如何使用executeReader()方法检索一个单元格的值

    我需要执行以下命令并将结果传递给标签 我不知道如何使用 Reader 来做到这一点 有人可以帮我吗 String sql SELECT FROM learer WHERE learer id index SqlCommand cmd new
  • 使用 CoreData 嵌套撤消组

    我想将撤消管理器添加到 coredata 支持的 iPhone 应用程序中 当用户尝试添加新对象 通过点击 按钮 时 我加载一个新的模式视图控制器并在 viewDidLoad 中启动一个新的撤消组 当用户按下 取消 按钮时 我想回滚 can
  • 删除 Spark 数据框中重复的所有记录

    我有一个包含多列的 Spark 数据框 我想找出并删除列中具有重复值的行 其他列可能不同 我尝试使用dropDuplicates col name 但它只会删除重复的条目 但仍会在数据框中保留一条记录 我需要的是删除最初包含重复条目的所有条
  • Google 街景中像素距地面的高度/标高

    我正在寻找谷歌街景中每个像素距地面的高度 我知道可以计算的几件事是 像素间距 https stackoverflow com questions 21591462 get heading and pitch from pixels on s
  • 删除特定的kafka消息

    我想指示 kafka 尽可能删除一条消息 如果使用键和日志压缩 可以将键设置为消息 ID 并将消息内容设置为 null 但我寻找更直接的东西 不依赖于设置密钥 例如通过消息 ID None
  • 如何在 NSMenuItem 内绘制内联样式标签(或按钮)

    当 App Store 有更新时 它会在菜单项中显示一个内联样式元素 如下面屏幕截图中的 1 new 另一个我们可以看到这种菜单的地方是10 10 Yosemite的分享菜单 当您安装任何添加新共享扩展的应用程序时 共享菜单中的 更多 项目