2014-10-24 2 views
5

Wenn App Store-Updates hat, zeigt es ein Inline-Stil-Element im Menüpunkt, wie '1 neu' in der Abbildung unten:Wie ein Inline-Stil-Label (oder Taste) ziehen innen NSMenuItem

enter image description here

Ein anderer Ort, an dem wir diese Art von Menü sehen können, ist 10.10 Yosemite's Share-Menü. Wenn Sie eine App installieren, die eine neue Freigabeerweiterung hinzufügt, wird im Menü "Teilen" im Menü "Mehr" neben dem App Store-Menü "N neu" angezeigt.

Der 'App Store ...' Artikel scheint ein normaler NSMenuItem zu sein. Gibt es eine einfache Möglichkeit, dies zu implementieren, oder gibt es APIs, die dies unterstützen, ohne eine benutzerdefinierte Ansicht für den Menüeintrag einzurichten?

+0

+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

+0

Haben Sie Glück dabei? – mileusna

+0

@mileusna Ich habe BonzaiThePenguin Lösung aus der Antwort noch nicht versucht, die gut funktionieren könnte. –

Antwort

2

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

Verwandte Themen