当App Store有更新时,它会在菜单项中显示一个内联样式元素,例如' 1 new'在下面的屏幕截图中:
另一个我们可以看到这种菜单的地方是10.10 Yosemite的分享菜单。当您安装添加新共享扩展程序的任何应用时,“更多”'共享菜单中的项目将显示&n; N new'就像应用商店菜单一样。
应用商店......' item看起来是正常的NSMenuItem
。有没有一种简单的方法来实现它,或者有没有支持它的API而没有为菜单项设置自定义视图?
答案 0 :(得分:2)
"可可及#34; NSMenus实际上完全基于Carbon构建,因此虽然Cocoa API不会暴露很多功能,但您可以深入到Carbon-land并获得更多功能。这就是Apple所做的事情 - Apple菜单项是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
检索。 kEventParamVirtualMenuTop
值在64位模式下也不正确,因此必须使用GetMenuTrackingData
。
它的hacky和荒谬,但它比使用setView并重新实现整个菜单项行为更好。 OS X上的菜单API是一个混乱的位。