NSMenus "Cocoa" thực sự được xây dựng hoàn toàn trên Carbon, vì vậy trong khi API Cocoa không hiển thị nhiều chức năng bạn có thể nhúng xuống vào vùng đất Carbon và nhận quyền truy cập nhiều hơn. Đó là những gì Apple đã làm, dù sao - các mục menu Apple đang subclassed từ IBCarbonMenuItem
, như có thể thấy ở đây:
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Resources/English.lproj/StandardMenus.nib/objects.xib
Đáng tiếc là các API Carbon 64-bit dường như thủng với lỗi và chức năng bị mất, mà làm cho nó nhiều khó cài đặt trình xử lý vẽ hoạt động hơn so với phiên bản 32 bit. Dưới đây là một phiên bản hacky tôi đến với:
#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];
}
Đầu tiên nó đăng ký cho một thông báo BeginTracking, như _NSGetCarbonMenu
chỉ trả về một xử lý hợp lệ sau khi trình đơn đã được xây dựng và BeginTracking được gọi trước khi thực đơn được rút ra.
Sau đó, nó sử dụng gọi lại thông báo để nhận MenuRef Carbon và đính kèm trình xử lý sự kiện Carbon tiêu chuẩn vào menu.
Thông thường chúng ta chỉ cần lấy tham số sự kiện kEventParamMenuContextHeight
và lật CGContextRef và bắt đầu vẽ, nhưng thông số đó chỉ có sẵn ở chế độ 32 bit. Tài liệu của Apple khuyến nghị sử dụng chiều cao của cổng hiện tại khi giá trị đó không có sẵn, nhưng điều đó cũng chỉ có ở chế độ 32 bit.
Vì vậy, vì trạng thái đồ họa được cung cấp cho chúng tôi là vô ích, hãy bật từ ngăn xếp và sử dụng trạng thái đồ họa trước đó. Nó chỉ ra rằng trạng thái mới này được dịch sang đầu ảo của menu, có thể được lấy ra bằng cách sử dụng GetMenuTrackingData.virtualMenuTop
. Giá trị kEventParamVirtualMenuTop
cũng không chính xác ở chế độ 64 bit nên nó phải sử dụng GetMenuTrackingData
.
Đó là hacky và vô lý, nhưng nó tốt hơn so với sử dụng setView và reimplementing toàn bộ hành vi mục menu. API trình đơn trên OS X là một số bit của một mớ hỗn độn.
+1. Ngoài ra tìm kiếm câu trả lời. Có vẻ như bạn có thể thiết lập NSView để hiển thị thay cho tiêu đề NSMenuItem thông thường. Nhưng đây không phải là cách tôi gọi là 'dễ dàng'. – UJey
Bất kỳ may mắn nào với điều này? – mileusna
@mileusna Tôi chưa thử giải pháp của BonzaiThePenguin từ câu trả lời, điều này có thể hoạt động tốt. –