Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display contextual NSMenu without blocking main thread

I had some unexpected results when displaying a contextual NSMenu. I'm presenting the menu myself, not using the menu property of an NSResponder. It turned out, presenting an NSMenu is a blocking operation.

In a new template project I only added the following code in applicationDidLaunch:

NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Menu"];
[menu addItemWithTitle:@"Item 1" action:@selector(selected:) keyEquivalent:@""];
[menu addItemWithTitle:@"Item 2" action:@selector(selected:) keyEquivalent:@""];
[menu addItemWithTitle:@"Item 3" action:@selector(selected:) keyEquivalent:@""];
[menu popUpMenuPositioningItem:[menu itemAtIndex:0] 
                    atLocation:CGPointMake(500, 500) 
                        inView:nil];
NSLog(@"Context menu shown.");

The popUpMenuPositioningItem:atLocation:inView: call doesn't return until I select an item or click outside to automatically dismiss the menu. The log is only printed once the menu is closed.

Why does this happen? And how can I prevent it? I found a couple of mentions of this problem, but the "solutions" always consisted of executing the other things in the background, instead of preventing NSMenu from blocking.

EDIT:

If I present the menu in a background thread, the menu becomes unresponsive, doesn't react to clicks inside or outside.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    [[menu popUpMenuPositioningItem:[menu itemAtIndex:0] 
                         atLocation:CGPointMake(500, 500) 
                             inView:nil];
});
like image 312
DrummerB Avatar asked Apr 21 '13 13:04

DrummerB


1 Answers

I don't think what you ask for is possible, however, there are a few things you can do that look darned similar:

1) Use an NSPopover instead of a menu. That is asynchronous and intended to be used in many contexts where a popup menu could be used. If you need to support OS versions that don't have popovers, you can also always create an NSWindow with buttons in it.

2) If you are trying to run timers, or other operations that are scheduled on the run loop (e.g. downloads), you can schedule these in NSEventTrackingRunLoopMode. By default, they're only scheduled for NSDefaultRunLoopMode (which makes sense, because you don't want to cancel the user's menu selection when a timer triggers that shows an alert).

To be honest, what you're trying to do seems kinda pointless, confusing, backwards and an abuse of menus. But that's probably just because I don't know what you're trying to achieve. What's your high-level task? Why does what need to happen on the main thread while a menu is up. Can't you have that other thing (in your example the NSLog()) happening on a second thread and leave the menu on the main thread?

like image 156
uliwitness Avatar answered Sep 23 '22 04:09

uliwitness