"Cocoa" NSMenus sind eigentlich vollständig auf Carbon gebaut, also, während die Cocoa APIs nicht viel Funktionalität aussetzen, können Sie in Carbon-Land eintauchen und viel mehr Power bekommen. Das ist, was Apple tut, sowieso - die Apple-Menüpunkte werden subclassed von IBCarbonMenuItem
, wie hier zu sehen:
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Resources/English.lproj/StandardMenus.nib/objects.xib
Leider ist die Kohlenstoff-64-Bit-APIs mit Bugs und fehlenden Funktionen gespickt zu sein scheinen, was es macht viel schwerer zu installieren als eine 32-Bit-Version. Hier ist eine Hacky Version kam ich mit:
#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];
}
Zuerst registriert sie für eine BeginTracking Benachrichtigung, als _NSGetCarbonMenu
nur einen gültigen Handle zurückgibt, nachdem das Menü aufgebaut wurde und BeginTracking aufgerufen wird, bevor das Menü gezogen wird.
Dann verwendet es den Benachrichtigungs-Callback, um den Carbon MenuRef zu erhalten und einen Standard-Carbon-Event-Handler an das Menü anzuhängen.
Normalerweise könnten wir einfach den Ereignisparameter kEventParamMenuContextHeight
nehmen und den CGContextRef spiegeln und mit dem Zeichnen beginnen, aber dieser Parameter ist nur im 32-Bit-Modus verfügbar. Apples Dokumentation empfiehlt, die Höhe des aktuellen Ports zu verwenden, wenn dieser Wert nicht verfügbar ist, aber auch das ist nur im 32-Bit-Modus verfügbar.
Da der uns zur Verfügung gestellte Grafikstatus nutzlos ist, kopieren Sie ihn vom Stapel und verwenden Sie den vorherigen Grafikstatus. Es stellt sich heraus, dass dieser neue Status in den virtuellen oberen Bereich des Menüs übersetzt wird, der unter Verwendung von GetMenuTrackingData.virtualMenuTop
abgerufen werden kann. Der kEventParamVirtualMenuTop
Wert ist auch falsch im 64-Bit-Modus, so dass es GetMenuTrackingData
verwenden muss.
Es ist hacky und absurd, aber es ist besser als die Verwendung von setView und die Neuimplementierung des gesamten Menüelements Verhalten. Die Menü-APIs auf OS X sind ein Bit eines Chaos.
+1. Ich suche auch nach der Antwort. Es sieht so aus, als ob Sie NSView so einrichten können, dass es anstelle des regulären NSMenuItem-Titels angezeigt wird. Aber das ist nicht die Art, die ich "einfach" nennen würde. – UJey
Haben Sie Glück dabei? – mileusna
@mileusna Ich habe BonzaiThePenguin Lösung aus der Antwort noch nicht versucht, die gut funktionieren könnte. –