如何在NSMenuItem内绘制内联样式标签(或按钮) [英] How to draw an inline style label (or button) inside NSMenuItem

查看:106
本文介绍了如何在NSMenuItem内绘制内联样式标签(或按钮)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When App Store has updates, it shows an inline style element in the menu item, like '1 new' in the screenshot below:

另一个可以看到这种菜单的地方是10.10优胜美地的共享菜单.当您安装任何添加了新的共享扩展名的应用程序时,共享菜单中的更多"项将显示为"N new",就像应用程序商店菜单中一样.

Another place we can see this kind of menu is 10.10 Yosemite's share menu. When you install any app that adds a new share extension, the 'More' item from the share menu will show 'N new' just as the app store menu.

"App Store ..."项看起来像是普通的NSMenuItem.是否有一种简便的方法来实现此功能,或者是否有任何API支持它而无需为菜单项设置自定义视图?

The 'App Store...' item looks to be a normal NSMenuItem. Is there an easy way to implement this or are there any APIs supporting it without setting up a custom view for the menu item?

推荐答案

"Cocoa" NSMenu实际上完全建立在Carbon上,因此尽管Cocoa API并未公开太多功能,但您可以深入研究Carbon-land并获得访问权限更大的力量.无论如何,这就是Apple所做的– Apple菜单项是IBCarbonMenuItem的子类,如此处所示:

"Cocoa" NSMenus are actually built entirely on Carbon, so while the Cocoa APIs don't expose much functionality you can dip down into Carbon-land and get access a lot more power. That's what Apple does, anyway – the Apple Menu items are subclassed from IBCarbonMenuItem, as can be seen here:

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

不幸的是,64位Carbon API似乎布满了错误和缺少的功能,这使得与32位版本相比,安装有效的绘图处理程序要困难得多.这是我想出的一个hacky版本:

Unfortunately the 64-bit Carbon APIs seem to be riddled with bugs and missing functions, which makes it much harder to install a working draw handler than compared to a 32-bit version. Here's a hacky version I came up with:

#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.

First it registers for a BeginTracking notification, as _NSGetCarbonMenu only returns a valid handle after the menu has been constructed and BeginTracking is called before the menu is drawn.

然后,它使用通知回调获取Carbon菜单引用,并将标准的Carbon事件处理程序附加到菜单上.

Then it uses the notification callback to get the Carbon MenuRef and attach a standard Carbon event handler to the menu.

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

Normally we could simply take the kEventParamMenuContextHeight event parameter and flip the CGContextRef and begin drawing, but that parameter is only available in 32-bit mode. Apple's documentation recommends using the height of the current port when that value is not available, but that too is only available in 32-bit mode.

因此,由于提供给我们的图形状态是无用的,因此请从堆栈中将其弹出并使用以前的图形状态.事实证明,此新状态已转换为菜单的虚拟顶部,可以使用GetMenuTrackingData.virtualMenuTop进行检索. kEventParamVirtualMenuTop值在64位模式下也不正确,因此必须使用GetMenuTrackingData.

So since the graphics state given to us is useless, pop it from the stack and use the previous graphics state. It turns out that this new state is translated to the virtual top of the menu, which can be retrieved using GetMenuTrackingData.virtualMenuTop. The kEventParamVirtualMenuTop value is also incorrect in 64-bit mode so it has to use GetMenuTrackingData.

这很笨拙,但是比使用setView并重新实现整个菜单项行为要好. OS X上的菜单API有点混乱.

It's hacky and absurd, but it's better than using setView and reimplementing the entire menu item behavior. The menu APIs on OS X are a bit of a mess.

这篇关于如何在NSMenuItem内绘制内联样式标签(或按钮)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆