NSPopover from NSMenuItem (Button) does not accept mouse events

Viewed 182

I wonder if anyone knows why an NSPopover does not respond to mouse events (hover, click) when it is opened from a Button inside an NSMenuItem inside an NSMenu.

And if there’s any solution for this problem?

Here’s a little sample view controller that attaches an NSMenu to its view. The menu has a button that opens the popover on click, and some dummy items that are just there to show that they still receive the mouse events even though the popover is above them:

@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMenu *menu = [NSMenu new];
    /// Some Dummy Items to show that the popover doesn’t accept the mouse.
    NSArray *dummyItems = @[@"item1", @"item2", @"item3"];
    for (NSString *itemName in dummyItems) {
        NSMenuItem *item = [NSMenuItem new];
        item.title = itemName;
        item.target = self;
        item.action = @selector(doNothing:);
        [menu addItem:item];
    }
    
    /// The button which presents the popover on click
    NSMenuItem *item = [NSMenuItem new];
    NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 30)];
    button.target = self;
    button.action = @selector(showPopover:);
    button.title = @"Show Popover";
    item.view = button;
    [menu addItem:item];
    
    [self.view setMenu:menu];
}

- (void)showPopover:(NSButton *)sender {
    /// This popover does not receive the mouse events for some reason.
    NSPopover *popover = [NSPopover new];
    NSViewController *popoverVC = [NSViewController new];
    NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 140, 25)];
    button.title = @"I’m not clickable";
    button.target = self;
    button.action = @selector(showPopover:);
    popoverVC.view = button;
    [popover setContentViewController:popoverVC];
    [popover showRelativeToRect:sender.frame ofView:sender.superview preferredEdge:NSMaxYEdge];
    [button.window makeKeyWindow];   /// Does not help
    [button.window becomeKeyWindow]; /// Does not help
}

- (void)doNothing:(id)sender {}

@end
1 Answers

There is a solution, but it may not be useful depending on what you are trying to do. I was unable to run the code that you posted, but can see that you added a button to both a menu and the popover. The problem appears to be the rectangle that is used to display the popover. For reasons unknown to me, if we try to display the popover based on the frame of the menu item button it is not possible to click on the popover button. However, if popover display is based on the frame of a window contentView based control such as NSPopUpButton or the rectangle of an NSView positioned near the menu the popover button becomes clickable. Menus dropped from the menubar present more of a challenge, due to lack of a nearby rectangle. This demo may be run in an Xcode objc project by deleting the pre-existing main.m code and copy/pasting the following into it. It is also necessary to delete the pre-existing AppDelegate files to avoid duplicate symbols. The demo illustrates the use of both an NSView rectangle and NSPopUpButton frame to display the popover, either of which will allow a clickable popover button. Code that fails is REMmed out.

#import <Cocoa/Cocoa.h>

@interface PopoverController: NSViewController
@end

@implementation PopoverController
@end


@interface AppDelegate : NSObject <NSApplicationDelegate> {
 NSWindow *window;
 NSPopover *popover;
 NSView *popoverView;
 NSMenuItem *menuItem;
 NSMenu *menu;
 PopoverController *popoverController;
 NSPopUpButton *pullDwn;
 NSView *menuView;
}

- (void) showPopoverAction:(id)sender;
- (void) menuAction:(id)sender;
- (void) myBtnAction:(id)sender;
- (void) buildMenu;
- (void) buildWnd;
@end

@implementation AppDelegate


- (void) showPopoverAction:(id)sender {
// **** This works **** //
//[popover showRelativeToRect:pullDwn.bounds ofView:pullDwn preferredEdge:NSMaxXEdge];
[popover showRelativeToRect:menuView.bounds ofView:menuView preferredEdge:NSMaxXEdge];
// **** This doesn't **** //
//[popover showRelativeToRect:[menuItem.view bounds] ofView:menuItem.view preferredEdge:NSMaxXEdge];
}

- (void) myBtnAction: (id)sender {
 NSBeep();
 NSLog(@"Popover button hit.");
 NSLog(@"===================");
}

- (void) menuAction: (id)sender {
[popover showRelativeToRect:pullDwn.bounds ofView:pullDwn preferredEdge:NSMaxXEdge];
}

- (void) buildMenu {
 NSMenu *menubar = [NSMenu new];
 NSMenuItem *menuBarItem = [NSMenuItem new];
 [menubar addItem:menuBarItem];
 [NSApp setMainMenu:menubar];
 NSMenu *appMenu = [NSMenu new];
 NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
 [appMenu addItem:quitMenuItem];
 [menuBarItem setSubmenu:appMenu];
}

- (void) buildWnd {

#define _wndW  500
#define _wndH  350

window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH )
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing: NSBackingStoreBuffered defer: NO];

[window center];
[window setTitle: @"NSPopUpButton Menu"];
[window makeKeyAndOrderFront: nil];

// ******* Popover controller ********* //
popoverController = [[PopoverController alloc] init];

// **** Menu View **** //
menuView = [[NSView alloc]initWithFrame:NSMakeRect( 100, _wndH - 242, 130, 30 ) ];
[[window contentView] addSubview:menuView];

// ****** Pull-Down NSPopUpButton ******* //
// First array element (0) becomes the title
NSArray *menuItems = [NSArray arrayWithObjects: @"Pull-Down", @"Item 1", @"Item 2",@"Item 3",nil];
pullDwn = [[NSPopUpButton alloc] initWithFrame:NSMakeRect( 100, _wndH - 142, 130, 30 )];
 menu = pullDwn.menu;
 menuItem = [NSMenuItem new];
 NSButton *myBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, 30, 95, 30 )];
[myBtn setBezelStyle:NSBezelStyleRounded ];
[myBtn setTitle: @"Popover"];
[myBtn setAction: @selector (showPopoverAction:)];
menuItem.view = myBtn;
[pullDwn setPullsDown:YES];
[pullDwn addItemsWithTitles:menuItems];
[pullDwn setTarget:self];
[pullDwn setAction:@selector(menuAction:)];
[menu addItem:menuItem];
[[window contentView]addSubview:pullDwn];

// **** NSPopover **** //
 popover = [[NSPopover alloc] init];
 [popover setContentViewController:popoverController];
 [popover setContentSize:NSMakeSize(150, 100)];
 //[popover setBehavior:NSPopoverBehaviorTransient];
 [popover setBehavior:NSPopoverBehaviorSemitransient];
 //[popover setBehavior:NSPopoverBehaviorApplicationDefined];
 [popover setAnimates: YES];
 [popover setDelegate: self];
 
// ****** Popover view ****** //
 popoverView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 150, 100)];

//******* Popover text field ******* //
NSTextField *ef = [[NSTextField alloc] initWithFrame:NSMakeRect( 20, 20, 110, 24 )];
[ef setStringValue:@"EditFld"];
[ef setAlignment:NSTextAlignmentCenter];
[ef setDrawsBackground:NO];
[popoverView addSubview:ef];

// **** Popover Button **** //
NSButton *popoverBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 20, 50, 95, 24 )];
[popoverBtn setBezelStyle:NSBezelStyleRounded ];
[popoverBtn setTitle: @"Push me"];
popoverBtn.target = self;
popoverBtn.action = @selector(myBtnAction:);
[popoverView addSubview: popoverBtn];
[popoverController setView:popoverView];

// ***** Quit btn ***** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: @"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:@selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}

- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
[self buildWnd];
}

- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
@end

int main () {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

Related