2014-10-24 4 views
5

Когда App Store есть обновления, он показывает элемент встроенного стиля в пункте меню, как «1 нового» на скриншоте ниже:Как нарисовать этикетку встроенного стиля (или кнопку) внутри NSMenuItem

enter image description here

Другое место, где мы можем видеть это меню, - это меню 10.10 Yosemite. Когда вы устанавливаете любое приложение, которое добавляет новое расширение общего доступа, элемент «Больше» из меню общего доступа будет показывать «N новый» так же, как меню магазина приложений.

Элемент «Магазин приложений ...» выглядит нормальным NSMenuItem. Есть ли простой способ реализовать это или есть какие-либо API-интерфейсы, поддерживающие его, без настройки пользовательского представления для элемента меню?

+0

+1. Также ища ответ. Похоже, вы можете настроить NSView для отображения вместо обычного названия NSMenuItem. Но это не так, как я бы назвал «легким». – UJey

+0

Любое повезло с этим? – mileusna

+0

@mileusna Я еще не пробовал решение BonzaiThePenguin из ответа, которое может работать хорошо. –

ответ

2

«Какао» NSMenus на самом деле полностью построен на углероде, поэтому, поскольку API-интерфейсы Cocoa не предоставляют много функциональности, вы можете окунуться в углеродную землю и получить доступ к гораздо большей мощности. Это то, что Apple, делает, во всяком случае - пункты меню Apple являются подклассами IBCarbonMenuItem, как можно видеть здесь:

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

К сожалению, 64-битные интерфейсы углерода, кажется, пронизана ошибок и недостающих функций, что делает его гораздо сложнее установить рабочий обработчик, чем по сравнению с 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.

Это хаки и абсурд, но это лучше, чем использование setView и переопределение всего поведения элемента меню. API-интерфейсы меню на OS X - это бит беспорядка.

 Смежные вопросы

  • Нет связанных вопросов^_^