Reverse engineering an NSMenu for a Status Bar Item

烂漫一生 提交于 2019-12-17 17:54:09

问题


I'm want to create a menu for a status bar item like the one seen in Tapbot's PastebotSync application:

Does anyone have any ideas how to achieve the custom area at the top of the menu which is flush with the top?

I've tried/thought of a few potential ways of doing it:

  • Standard NSMenuItem with a view - isn't flush with the top of the menu
  • Some hack-ish code to place an NSWindow over the area at the top of the menu - not great as it doesn't fade out nicely with the menu when it closes
  • Abandoning an NSMenu entirely and using an NSView instead - haven't tried this yet but I don't really want to have to make some fake buttons or something that act as NSMenuItems

Anyone have any better ideas or suggestions?

Thanks!


回答1:


In case anyone comes looking, I posted a solution to this at Gap above NSMenuItem custom view

Here's the code:

@interface FullMenuItemView : NSView
@end

@implementation FullMenuItemView
- (void) drawRect:(NSRect)dirtyRect
{
    NSRect fullBounds = [self bounds];
    fullBounds.size.height += 4;
    [[NSBezierPath bezierPathWithRect:fullBounds] setClip];

    // Then do your drawing, for example...
    [[NSColor blueColor] set];
    NSRectFill( fullBounds );
}
@end

Use it like this:

CGFloat menuItemHeight = 32;

NSRect viewRect = NSMakeRect(0, 0, /* width autoresizes */ 1, menuItemHeight);
NSView *menuItemView = [[[FullMenuItemView alloc] initWithFrame:viewRect] autorelease];
menuItemView.autoresizingMask = NSViewWidthSizable;

yourMenuItem.view = menuItemView;



回答2:


I had the same need in early versions of HoudahSpot 2. I did get it working with one limitation: my code leaves the menu with square corners at the bottom.

I have since abandonned this setup, as the BlitzSearch feature in HoudahSpot grew to need a complexer UI, I ran into other limitations with using NSViews in a NSMenu.

Anyway, here is the original code taking care of those extra 3 pixels:

- (void)awakeFromNib
{
 HIViewRef contentView;
 MenuRef menuRef = [statusMenu carbonMenuRef];
 HIMenuGetContentView (menuRef, kThemeMenuTypePullDown, &contentView);

 EventTypeSpec hsEventSpec[1] = {
  { kEventClassMenu, kEventMenuCreateFrameView }
 };

 HIViewInstallEventHandler(contentView,
          NewEventHandlerUPP((EventHandlerProcPtr)hsMenuCreationEventHandler),
          GetEventTypeCount(hsEventSpec),
          hsEventSpec,
          NULL,
          NULL);
}


#pragma mark -
#pragma mark Carbon handlers

static OSStatus hsMenuContentEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
 OSStatus  err;

 check( GetEventClass( event ) == kEventClassControl );
 check( GetEventKind( event ) == kEventControlGetFrameMetrics );

 err = CallNextEventHandler( caller, event );
 if ( err == noErr )
 {
  HIViewFrameMetrics  metrics;

  verify_noerr( GetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics, NULL,
          sizeof( metrics ), NULL, &metrics ) );

  metrics.top = 0;

  verify_noerr( SetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics,
          sizeof( metrics ), &metrics ) );
 }

 return err;
}

static OSStatus hsMenuCreationEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
 OSStatus  err = eventNotHandledErr;

 if ( GetEventKind( event ) == kEventMenuCreateFrameView)
 {
  err = CallNextEventHandler( caller, event );
  if ( err == noErr )
  {
   static const EventTypeSpec  kContentEvents[] =
   {
    { kEventClassControl, kEventControlGetFrameMetrics }
   };

   HIViewRef          frame;
   HIViewRef          content;

   verify_noerr( GetEventParameter( event, kEventParamMenuFrameView, typeControlRef, NULL,
           sizeof( frame ), NULL, &frame ) );
   verify_noerr( HIViewFindByID( frame, kHIViewWindowContentID, &content ) );
   HIViewInstallEventHandler( content, hsMenuContentEventHandler, GetEventTypeCount( kContentEvents ),
            kContentEvents, 0, NULL );
  }
 }

 return err;
}

Sorry, I forgot that bit:

- (MenuRef) carbonMenuRef
{
    MenuRef carbonMenuRef = NULL;

    if (carbonMenuRef == NULL) {
        extern MenuRef _NSGetCarbonMenu(NSMenu *);
        carbonMenuRef = _NSGetCarbonMenu(self);

        if (carbonMenuRef == NULL) {
            NSMenu *theMainMenu = [NSApp mainMenu];
            NSMenuItem *theDummyMenuItem = [theMainMenu addItemWithTitle: @"sub"  action: NULL keyEquivalent: @""];

            if (theDummyMenuItem != nil) {
                [theDummyMenuItem setSubmenu:self];
                [theDummyMenuItem setSubmenu:nil];
                [theMainMenu removeItem:theDummyMenuItem];

                carbonMenuRef = _NSGetCarbonMenu(self);
            }
        }
    }

    if (carbonMenuRef == NULL) {
        extern MenuRef _NSGetCarbonMenu2(NSMenu *);
        carbonMenuRef = _NSGetCarbonMenu2(self);
    }

    return carbonMenuRef;
}


来源:https://stackoverflow.com/questions/4064386/reverse-engineering-an-nsmenu-for-a-status-bar-item

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!