Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Objective c 切换NSStatusItem';s菜单使用热键打开/关闭-代码执行排队/阻止_Objective C_Cocoa_Nsmenu_Nsstatusitem - Fatal编程技术网

Objective c 切换NSStatusItem';s菜单使用热键打开/关闭-代码执行排队/阻止

Objective c 切换NSStatusItem';s菜单使用热键打开/关闭-代码执行排队/阻止,objective-c,cocoa,nsmenu,nsstatusitem,Objective C,Cocoa,Nsmenu,Nsstatusitem,我之所以编辑这个问题,是因为我认为我可能过于简化了状态项菜单的打开方式。对于这样一个简单的函数来说,它是非常复杂的 我的状态项支持左键和右键单击操作。用户可以更改每次单击的类型。此外,由于以下原因,当有2个或更多屏幕/显示器连接且垂直排列时,我必须做一些额外的特殊工作 我使用通过系统范围的热键打开NSStatusItem的菜单(“⌘ ⌥ 我发现一旦菜单被打开,就不可能用热键来关闭它。我试图将菜单从关闭切换到打开,反之亦然。但是,当菜单打开时,代码执行被阻止。有什么办法可以解决这个问题吗?我发现这

我之所以编辑这个问题,是因为我认为我可能过于简化了状态项菜单的打开方式。对于这样一个简单的函数来说,它是非常复杂的

我的状态项支持左键和右键单击操作。用户可以更改每次单击的类型。此外,由于以下原因,当有2个或更多屏幕/显示器连接且垂直排列时,我必须做一些额外的特殊工作

我使用通过系统范围的热键打开NSStatusItem的菜单(“⌘ ⌥ 我发现一旦菜单被打开,就不可能用热键来关闭它。我试图将菜单从关闭切换到打开,反之亦然。但是,当菜单打开时,代码执行被阻止。有什么办法可以解决这个问题吗?我发现这似乎是一个类似的问题,但遗憾的是没有找到答案

提前感谢您的帮助

更新:


当用户执行指定热键以显示状态项菜单时,将运行以下命令:

[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
     {
         if (!self.statusMenuOpen)
         {
             [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
         }
         else
         {
             [self.statusMenu cancelTracking];
         }
     }];
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{     
     // CREATE AND CONFIGURE THE STATUS ITEM
     self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
     [self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];
     [self.statusItem.button setAction: @selector(statusItemClicked:)];
     self.statusMenu.delegate = self;
}

- (IBAction) statusItemClicked: (id) sender
{
     // Logic exists here to determine if the status item click was a left or right click 
     // and whether the menu should show based on user prefs and click type

     if (menuShouldShow)
     {
          [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
     }
}

- (IBAction) showStatusMenu: (id) sender
{
     // macOS 10.15 introduced an issue with some status item menus not appearing 
     // properly when two or more screens/displays are arranged vertically
     // Logic exists here to determine if this issue is present on the current system

     if (@available(*, macOS 10.15))
     {
          if (verticalScreensIssuePresent)
          {
               [self performSelector:@selector(popUpStatusItemMenu) withObject:nil afterDelay:0.05];
          }
          else // vertical screens issues not present
          {
               // DISPLAY THE MENU NORMALLY
               self.statusItem.menu = self.statusMenu;
               [self.statusItem.button performClick:nil];
          }                    
     }
     else // not macOS 10.15+
     {
        // DISPLAY THE MENU NORMALLY
        self.statusItem.menu = self.statusMenu;
        [self.statusItem.button performClick:nil];
     }
}

- (void) popUpStatusItemMenu
{
      // Logic exists here to determine how wide the menu is
      // If the menu is too wide to fit on the right, display
      // it on the left side of the status item

     // menu is too wide for screen, need to open left side
     if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
     {
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake((-menuWidth + self.statusItem.button.superview.frame.size.width), -5)
                                             inView:[self.statusItem.button superview]];

    }
    else // not too wide
    {
        
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake(0, -5)
                                             inView:[self.statusItem.button superview]];

    }
}
- (void) menuDidClose : (NSMenu *) aMenu
{
    // HIDE THE MENU ITEM FOR HOTKEY CLOSE MENU 
    self.closeMenuItem.hidden = YES;
        
    self.showMenu.shortcutValue = [self.showMenuTemp copy];
    self.showMenuTemp = nil;
        
    [self setupOpenCloseMenuHotKey];
}
还有其他相关代码:

[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
     {
         if (!self.statusMenuOpen)
         {
             [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
         }
         else
         {
             [self.statusMenu cancelTracking];
         }
     }];
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{     
     // CREATE AND CONFIGURE THE STATUS ITEM
     self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
     [self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];
     [self.statusItem.button setAction: @selector(statusItemClicked:)];
     self.statusMenu.delegate = self;
}

- (IBAction) statusItemClicked: (id) sender
{
     // Logic exists here to determine if the status item click was a left or right click 
     // and whether the menu should show based on user prefs and click type

     if (menuShouldShow)
     {
          [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
     }
}

- (IBAction) showStatusMenu: (id) sender
{
     // macOS 10.15 introduced an issue with some status item menus not appearing 
     // properly when two or more screens/displays are arranged vertically
     // Logic exists here to determine if this issue is present on the current system

     if (@available(*, macOS 10.15))
     {
          if (verticalScreensIssuePresent)
          {
               [self performSelector:@selector(popUpStatusItemMenu) withObject:nil afterDelay:0.05];
          }
          else // vertical screens issues not present
          {
               // DISPLAY THE MENU NORMALLY
               self.statusItem.menu = self.statusMenu;
               [self.statusItem.button performClick:nil];
          }                    
     }
     else // not macOS 10.15+
     {
        // DISPLAY THE MENU NORMALLY
        self.statusItem.menu = self.statusMenu;
        [self.statusItem.button performClick:nil];
     }
}

- (void) popUpStatusItemMenu
{
      // Logic exists here to determine how wide the menu is
      // If the menu is too wide to fit on the right, display
      // it on the left side of the status item

     // menu is too wide for screen, need to open left side
     if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
     {
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake((-menuWidth + self.statusItem.button.superview.frame.size.width), -5)
                                             inView:[self.statusItem.button superview]];

    }
    else // not too wide
    {
        
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake(0, -5)
                                             inView:[self.statusItem.button superview]];

    }
}
- (void) menuDidClose : (NSMenu *) aMenu
{
    // HIDE THE MENU ITEM FOR HOTKEY CLOSE MENU 
    self.closeMenuItem.hidden = YES;
        
    self.showMenu.shortcutValue = [self.showMenuTemp copy];
    self.showMenuTemp = nil;
        
    [self setupOpenCloseMenuHotKey];
}

我可以证实你的观察

我试图将菜单从关闭切换到打开,反之亦然。 菜单打开时,代码执行被阻止

原因是当打开时,
NSMenu
接管应用程序的
NSEvent
s处理(它是内部的
\uu NSHLTBMenuEventProc
处理),而不是标准的
[NSApplication run]
队列

最终实际触发快捷方式处理的事件是
NSEventTypeSystemDefined
,子类型为6(9是下面的keyUp,在这里并不真正相关)

当菜单打开时,那些
NSEventTypeSystemDefined
根本不会触发。某些机制正在推迟它们的启动,直到菜单被取消,应用程序返回到
[NSApplication run]
队列。A尝试了很多技巧和黑客来规避这一点,但都无济于事

MASShortcut使用传统的Carbon API安装此自定义事件处理程序。我可以将它插入到NSMenu内部事件调度程序(当菜单未打开时它会工作),但它并不能解决问题,因为前面提到的
NSEvent
s一开始就没有触发(直到菜单关闭)

我有根据地猜测,是MacOS Windows服务器控制了这一点(因为它知道诸如按下控制键之类的事情)

不管怎样,我很高兴你找到了解决办法

如果有人想调试这些事件(我想这是我能提供的最佳起点),下面是我使用的代码:

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    
    Class clazz = NSApplication.class;

    SEL selectorNextEventMatchingEventMask = NSSelectorFromString(@"_nextEventMatchingEventMask:untilDate:inMode:dequeue:");
    Method method = class_getInstanceMethod(clazz, selectorNextEventMatchingEventMask);
    const char *typesSelectorNextEventMatchingMask  = method_getTypeEncoding(method);
    IMP genuineSelectorNextEventMatchingMask = method_getImplementation(method);
    
    IMP test = class_replaceMethod(clazz, selectorNextEventMatchingEventMask, imp_implementationWithBlock(^(__unsafe_unretained NSApplication* self, NSEventMask mask, NSDate* expiration, NSRunLoopMode mode, BOOL deqFlag) {

        NSEvent* (*genuineSelectorNextEventMatchingMaskTyped)(id, SEL, NSEventMask, NSDate*, NSRunLoopMode, BOOL) = (void *)genuineSelectorNextEventMatchingMask;
        NSEvent* event = genuineSelectorNextEventMatchingMaskTyped(self, selectorNextEventMatchingEventMask, mask, expiration, mode, deqFlag);
        
        if (event.type == NSEventTypeSystemDefined) {
            if (event.subtype == 6l) {
                NSLog(@"⚪️ %@ %i %@", event, mask, mode);
            }
            else if (event.subtype == 9l) {
                NSLog(@"⚪️⚪️ %@ %i %@", event, mask, mode);
            }
            else if (event.subtype == 7l) {

                NSLog(@"I ended up solving this issue by programmatically assigning an NSMenuItem's keyEquivalent to be the same hot key as the MASShortcut hot key value. This allows the user to use the same hot key to perform a different function (close the NSMenu.)

When setting up the hot key:

-(void) setupOpenCloseMenuHotKey
{
    [[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
    {
        // UNHIDES THE NEW "CLOSE MENU" MENU ITEM
        self.closeMenuItem.hidden = NO; 
                
        // SET THE NEW "CLOSE MENU" MENU ITEM'S KEY EQUIVALENT TO BE THE SAME
        // AS THE MASSHORTCUT VALUE
        [self.closeMenuItem setKeyEquivalentModifierMask: self.showMenu.shortcutValue.modifierFlags];
        [self.closeMenuItem setKeyEquivalent:self.showMenu.shortcutValue.keyCodeString];
            
        self.showMenuTemp = [self.showMenu.shortcutValue copy];
        self.showMenu.shortcutValue = nil;
    
        dispatch_async(dispatch_get_main_queue(), ^{
            [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
        });
    }];
}
int main(int argc,const char*argv[]{
@自动释放池{
//可能创建自动释放对象的设置代码如下所示。
}
Class clazz=NSApplication.Class;
SEL selectorNextEventMatchingEventMask=NSSelectorFromString(@“\u nextEventMachingeVentMASK:untilDate:inMode:dequeue:”);
Method=class\u getInstanceMethod(clazz,selectorExtEventMatchingEventMask);
const char*typesSelectorNextEventMatchingMask=method\u getTypeEncoding(method);
IMP genuineSelectorNextEventMatchingMask=方法\获取实现(方法);
IMP test=class_replaceMethod(clazz,SelectorExtEventMatchingEventMask,IMP_实现带块(^(uuu unsafe_u未维护的NSApplication*self,NSEventMask,NSDate*过期,nsrunlopmode模式,BOOL develag){
NSEvent*(*GenuineSelectorExtEventMatchingMask类型)(id、SEL、NSEventMask、NSDate*、NSRunLoopMode、BOOL)=(void*)GenuineSelectorExtEventMatchingMask;
NSEvent*event=genuineSelectorNextEventMatchingMaskTyped(self、selectorNextEventMatchingEventMask、mask、expiration、mode、deqFlag);
if(event.type==NSEventTypeSystemDefined){
如果(event.subtype==6l){
NSLog(@)⚪️ %@ %i%@”,事件、掩码、模式);
}
else if(event.subtype==9l){
NSLog(@)⚪️⚪️ %@ %i%@”,事件、掩码、模式);
}
else if(event.subtype==7l){

NSLog(@“我最终解决了这个问题,通过编程方式将NSMenuItem的键等效为与MASShortcut热键值相同的热键。这允许用户使用相同的热键执行不同的功能(关闭NSMenu)

设置热键时:

[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
     {
         if (!self.statusMenuOpen)
         {
             [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
         }
         else
         {
             [self.statusMenu cancelTracking];
         }
     }];
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{     
     // CREATE AND CONFIGURE THE STATUS ITEM
     self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
     [self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];
     [self.statusItem.button setAction: @selector(statusItemClicked:)];
     self.statusMenu.delegate = self;
}

- (IBAction) statusItemClicked: (id) sender
{
     // Logic exists here to determine if the status item click was a left or right click 
     // and whether the menu should show based on user prefs and click type

     if (menuShouldShow)
     {
          [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
     }
}

- (IBAction) showStatusMenu: (id) sender
{
     // macOS 10.15 introduced an issue with some status item menus not appearing 
     // properly when two or more screens/displays are arranged vertically
     // Logic exists here to determine if this issue is present on the current system

     if (@available(*, macOS 10.15))
     {
          if (verticalScreensIssuePresent)
          {
               [self performSelector:@selector(popUpStatusItemMenu) withObject:nil afterDelay:0.05];
          }
          else // vertical screens issues not present
          {
               // DISPLAY THE MENU NORMALLY
               self.statusItem.menu = self.statusMenu;
               [self.statusItem.button performClick:nil];
          }                    
     }
     else // not macOS 10.15+
     {
        // DISPLAY THE MENU NORMALLY
        self.statusItem.menu = self.statusMenu;
        [self.statusItem.button performClick:nil];
     }
}

- (void) popUpStatusItemMenu
{
      // Logic exists here to determine how wide the menu is
      // If the menu is too wide to fit on the right, display
      // it on the left side of the status item

     // menu is too wide for screen, need to open left side
     if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
     {
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake((-menuWidth + self.statusItem.button.superview.frame.size.width), -5)
                                             inView:[self.statusItem.button superview]];

    }
    else // not too wide
    {
        
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake(0, -5)
                                             inView:[self.statusItem.button superview]];

    }
}
- (void) menuDidClose : (NSMenu *) aMenu
{
    // HIDE THE MENU ITEM FOR HOTKEY CLOSE MENU 
    self.closeMenuItem.hidden = YES;
        
    self.showMenu.shortcutValue = [self.showMenuTemp copy];
    self.showMenuTemp = nil;
        
    [self setupOpenCloseMenuHotKey];
}

然后,当菜单关闭时:

[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
     {
         if (!self.statusMenuOpen)
         {
             [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
         }
         else
         {
             [self.statusMenu cancelTracking];
         }
     }];
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{     
     // CREATE AND CONFIGURE THE STATUS ITEM
     self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
     [self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];
     [self.statusItem.button setAction: @selector(statusItemClicked:)];
     self.statusMenu.delegate = self;
}

- (IBAction) statusItemClicked: (id) sender
{
     // Logic exists here to determine if the status item click was a left or right click 
     // and whether the menu should show based on user prefs and click type

     if (menuShouldShow)
     {
          [self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
     }
}

- (IBAction) showStatusMenu: (id) sender
{
     // macOS 10.15 introduced an issue with some status item menus not appearing 
     // properly when two or more screens/displays are arranged vertically
     // Logic exists here to determine if this issue is present on the current system

     if (@available(*, macOS 10.15))
     {
          if (verticalScreensIssuePresent)
          {
               [self performSelector:@selector(popUpStatusItemMenu) withObject:nil afterDelay:0.05];
          }
          else // vertical screens issues not present
          {
               // DISPLAY THE MENU NORMALLY
               self.statusItem.menu = self.statusMenu;
               [self.statusItem.button performClick:nil];
          }                    
     }
     else // not macOS 10.15+
     {
        // DISPLAY THE MENU NORMALLY
        self.statusItem.menu = self.statusMenu;
        [self.statusItem.button performClick:nil];
     }
}

- (void) popUpStatusItemMenu
{
      // Logic exists here to determine how wide the menu is
      // If the menu is too wide to fit on the right, display
      // it on the left side of the status item

     // menu is too wide for screen, need to open left side
     if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
     {
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake((-menuWidth + self.statusItem.button.superview.frame.size.width), -5)
                                             inView:[self.statusItem.button superview]];

    }
    else // not too wide
    {
        
          [self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
                                         atLocation:CGPointMake(0, -5)
                                             inView:[self.statusItem.button superview]];

    }
}
- (void) menuDidClose : (NSMenu *) aMenu
{
    // HIDE THE MENU ITEM FOR HOTKEY CLOSE MENU 
    self.closeMenuItem.hidden = YES;
        
    self.showMenu.shortcutValue = [self.showMenuTemp copy];
    self.showMenuTemp = nil;
        
    [self setupOpenCloseMenuHotKey];
}

嗨,卡米尔-非常感谢你发布这个答案!遗憾的是,这对我来说不起作用。我可能把我的应用程序的状态项菜单打开的方式过于简化了。对于一个简单的功能来说,这简直是太复杂了。我将重写我的问题,以更好地代表我面临的问题。再次感谢你,我道歉感谢您没有做好充分的准备!顺便说一句,AppKit中的库存快捷方式处理不够好,您求助于第三方的原因是什么?嗨,Kamil-感谢您的评论。我已经更新了问题,以便大家能够更好地了解如何/何时进行每个呼叫。我使用MASShortcut是因为它允许“全局”操作“/系统范围的热键。我不认为AppKit提供了此功能,但可能是错误的。如果(!self.statusMenuOpen)出现,请在
toAction:
block
->的第一行上放置断点。”
并查看它是否被击中。您需要确定执行过程中最后一个仍然可以捕获断点的点。或者,如果可行,请上传一个复制该问题的最小项目。Kamil-很好的建议。谢谢。我会这样做,并发布我的结果+指向示例项目的链接。