对状态栏项的 NSMenu 进行逆向工程

Posted

技术标签:

【中文标题】对状态栏项的 NSMenu 进行逆向工程【英文标题】:Reverse engineering an NSMenu for a Status Bar Item 【发布时间】:2011-05-03 03:19:36 【问题描述】:

我想为状态栏项目创建一个菜单,就像在 Tapbot 的 PastebotSync 应用程序中看到的那样:

有没有人知道如何在菜单顶部实现与顶部齐平的自定义区域?

我已经尝试/想到了几种可能的方法:

带有视图的标准 NSMenuItem - 不与菜单顶部齐平 一些 hack-ish 代码将 NSWindow 放置在菜单顶部的区域上 - 不是很好,因为它不会在菜单关闭时很好地淡出 完全放弃 NSMenu 并改用 NSView - 还没有尝试过,但我真的不想制作一些假按钮或充当 NSMenuItems 的东西

谁有更好的想法或建议?

谢谢!

【问题讨论】:

【参考方案1】:

如果有人来看,我在Gap above NSMenuItem custom view 发布了一个解决方案

代码如下:

@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

像这样使用它:

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】:

我在 HoudahSpot 2 的早期版本中也有同样的需求。我确实得到了它的一个限制:我的代码在底部留下了方角的菜单。

我已经放弃了这个设置,因为 HoudahSpot 中的 BlitzSearch 功能需要更复杂的 UI,我在 NSMenu 中使用 NSViews 时遇到了其他限制。

不管怎样,这里是处理那些额外 3 个像素的原始代码:

- (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;

对不起,我忘记了:

- (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;

【讨论】:

代码看起来很有希望,但我还不能让它工作。为我在 Xcode 中构建时会产生很多错误和警告,也许现在所有这些都已弃用? 在将 [menu carbonMenuRef] 替换为 _NSGetCarbonMenu(menu) 后,我实际上已经成功构建了它,但它似乎对菜单没有影响。 重要的一行是“metrics.top = 0;” 我也无法让它工作,似乎[menu carbonMenuRef] 返回 NULL。 嗨,我正在寻找相同的解决方案。我试过你的代码,但它给出了错误。请为我们提供上述代码的示例项目。我会很感激你的。谢谢

以上是关于对状态栏项的 NSMenu 进行逆向工程的主要内容,如果未能解决你的问题,请参考以下文章

如何实现窗口指向状态栏项的UI效果?

在 Swift 中调整导航栏项的位置

更改活动导航项的颜色并显示相对于导航栏项的页面

NSMenu 未调用 validateMenuItem 或 menuWillOpen

阻塞主线程的状态项(NSMenu 阻止 NSSpeechRecognizer 检测声音)

使用动作选择器预设将项目添加到 NSMenu