2014-10-24 21 views
5

Quando App Store ha aggiornamenti, si vede un elemento di stile in linea nella voce di menu, come '1 nuovo' nello screenshot qui sotto:Come disegnare un'etichetta stile in linea (o il pulsante) all'interno NSMenuItem

enter image description here

Un altro posto in cui possiamo vedere questo tipo di menu è 10,10 menu di condivisione di Yosemite. Quando installi qualsiasi app che aggiunge una nuova estensione di condivisione, la voce "Altro" dal menu di condivisione mostrerà "N nuovo" proprio come il menu dell'app store.

L'elemento 'App Store ...' sembra essere un normale NSMenuItem. C'è un modo semplice per implementare questo o ci sono API che lo supportano senza impostare una visualizzazione personalizzata per la voce di menu?

+0

+1. Anche cercando la risposta. Sembra che tu possa configurare NSView per essere visualizzato al posto del normale titolo NSMenuItem. Ma questo non è il modo in cui chiamerei "facile". – UJey

+0

Hai avuto fortuna con questo? – mileusna

+0

@mileusna Non ho ancora provato la soluzione di BonzaiThePenguin dalla risposta, che potrebbe funzionare bene. –

risposta

2

"Cocoa" Gli NSMenus sono in realtà costruiti interamente in Carbonio, quindi mentre le API Cocoa non espongono molta funzionalità, puoi immergerti in Carbon-land e avere accesso a molta più energia. Questo è quello che fa Apple, in ogni caso - le voci di menu Apple sono una sottoclasse di IBCarbonMenuItem, come si può vedere qui:

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

Purtroppo le API Carbon 64 bit sembrano essere crivellato di bug e funzionalità mancanti, che lo rende molto più difficile installare un gestore di disegnare di lavoro rispetto a una versione a 32 bit. Ecco una versione hacky mi si avvicinò con:

#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]; 
} 

In primo luogo si registra per una notifica BeginTracking, come _NSGetCarbonMenu restituisce solo un handle valido dopo il menu è stato costruito e BeginTracking viene chiamato prima viene disegnato il menu.

Quindi utilizza la richiamata di notifica per ottenere Carbon MenuRef e associare un gestore di eventi Carbon standard al menu.

In genere, è sufficiente prendere il parametro evento kEventParamMenuContextHeight e capovolgere CGContextRef e iniziare a disegnare, ma tale parametro è disponibile solo in modalità a 32 bit. La documentazione di Apple consiglia di utilizzare l'altezza della porta corrente quando quel valore non è disponibile, ma anche quello è disponibile solo in modalità a 32 bit.

Quindi, poiché lo stato di grafica che ci è stato fornito è inutile, estrarlo dallo stack e utilizzare lo stato precedente della grafica. Si scopre che questo nuovo stato viene tradotto nella parte superiore virtuale del menu, che può essere recuperato utilizzando GetMenuTrackingData.virtualMenuTop. Anche il valore kEventParamVirtualMenuTop non è corretto in modalità 64 bit, pertanto deve utilizzare GetMenuTrackingData.

È hacky e assurdo, ma è meglio che usare setView e reimplementare l'intero comportamento delle voci di menu. Le API di menu su OS X sono un bit di un pasticcio.