仅在NSStatusBar按钮上显示NSMenu右键单击?



我有以下代码:(可以复制粘贴到新的macOS项目(

import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.menu = statusBarMenu
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type ==  NSEvent.EventType.rightMouseUp {
print("Right click!")
} else {
print("Left click!")
}
}
@objc func orderAnApple() {
print("Ordering a apple!")
}

@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}

实际行为:菜单在左键和右键单击时打开,状态栏按钮单击未触发。
删除此行后:

statusBarItem.menu = statusBarMenu

状态栏按钮单击左键时触发,菜单未显示(按预期(

所需行为:右键单击时菜单打开,左键单击菜单未打开,操作已触发。
我如何实现它?

编辑

在评论的帮助下,我设法实现了@red_menace行为:

import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
menu = statusBarMenu
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type ==  NSEvent.EventType.rightMouseUp {
statusBarItem.popUpMenu(menu)
} else {
print("Left click!")
}
}
@objc func orderAnApple() {
print("Ordering a apple!")
}

@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}

但是 Xcode 说openMenufunc 在 10.14 中被弃用,并告诉我Use the menu property instead.我有没有办法使用新的 API 实现所需的行为?

显示菜单的常用方法是将菜单分配给状态项,单击状态项按钮时将显示该菜单。 由于popUpMenu已弃用,因此需要另一种方式在不同条件下显示菜单。 如果希望右键单击使用实际状态项菜单,而不仅仅是在状态项位置显示上下文菜单,则可以将状态项菜单属性保留为零,直到您想要显示它。

我已经调整了您的代码以将statusBarItemstatusBarMenu引用分开,仅将菜单添加到单击的操作方法中的状态项。 在操作方法中,添加菜单后,对状态按钮执行正常单击以删除菜单。 由于状态项在单击按钮时将始终显示其菜单,因此添加了NSMenuDelegate方法,用于在菜单关闭时将菜单属性设置为nil,从而还原原始操作:

import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
// keep status item and menu separate
var statusBarItem: NSStatusItem!
var statusBarMenu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.delegate = self
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type ==  NSEvent.EventType.rightMouseUp {
print("Right click!")
statusBarItem.menu = statusBarMenu // add menu to button...
statusBarItem.button?.performClick(nil) // ...and click
} else {
print("Left click!")
}
}
@objc func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil // remove menu so button works as before
}
@objc func orderAnApple() {
print("Ordering a apple!")
}
@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}

这是可能的方法。菜单位置可能会有更准确的计算,包括考虑userInterfaceLayoutDirection的可能差异,但想法保持不变 - 在手动控制下处理可能的事件,并自行决定对每个事件执行的操作。

代码中注释的重要位置。(在 Xcode 11.2、macOS 10.15 上测试(

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp]) // << send action in both cases
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.button?.menu = statusBarMenu // << store menu in button, not item
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type ==  NSEvent.EventType.rightMouseUp {
print("Right click!")
if let button = statusBarItem.button { // << pop up menu programmatically
button.menu?.popUp(positioning: nil, at: CGPoint(x: -1, y: button.bounds.maxY + 5), in: button)
}
} else {
print("Left click!")
}
}
@objc func orderAnApple() {
print("Ordering a apple!")
}

@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}

我在macOS Catalina上使用此代码。10.15.2. ( Xcode 11.3(.
左键单击它触发操作。右键单击它显示菜单。

//HEADER FILE
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@protocol MOSMainStatusBarDelegate
- (void) menuBarControllerStatusChanged: (BOOL) active;
@end

@interface MOSMainStatusBar : NSObject
@property (strong) NSMenu *menu;
@property (strong, nonatomic) NSImage *image;
@property (unsafe_unretained, nonatomic) id<MOSMainStatusBarDelegate> delegate;
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu;
- (NSStatusBarButton *) statusItemView;
- (void) showStatusItem;
- (void) hideStatusItem;

@end
//IMPLEMANTION FILE. 
#import "MOSMainStatusBar.h"
@interface MOSMainStatusBar ()
@property (strong, nonatomic) NSStatusItem *statusItem;
@end
@implementation MOSMainStatusBar
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu {
self = [super init];
if (self) {
self.image = image;
self.menu = menu;
}
return self;
}
- (void) setImage: (NSImage *) image {
_image = image;
self.statusItem.button.image = image;
}
- (NSStatusBarButton *) statusItemView {
return self.statusItem.button;
}
- (void) showStatusItem {
if (!self.statusItem) {
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self initStatusItem10];
}
}
- (void) hideStatusItem {
if (self.statusItem) {
[[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
self.statusItem = nil;
}
}

- (void) initStatusItem10 {
self.statusItem.button.image = self.image;

self.statusItem.button.imageScaling =  NSImageScaleAxesIndependently;
self.statusItem.button.appearsDisabled = NO;
self.statusItem.button.target = self;
self.statusItem.button.action = @selector(leftClick10:);
__unsafe_unretained MOSMainStatusBar *weakSelf = self;
[NSEvent addLocalMonitorForEventsMatchingMask:
(NSEventMaskRightMouseDown | NSEventModifierFlagOption | NSEventMaskLeftMouseDown) handler:^(NSEvent *incomingEvent) {
if (incomingEvent.type == NSEventTypeLeftMouseDown) {
weakSelf.statusItem.menu = nil;
}
if (incomingEvent.type == NSEventTypeRightMouseDown || [incomingEvent modifierFlags] & NSEventModifierFlagOption) {
weakSelf.statusItem.menu = weakSelf.menu;
}
return incomingEvent;
}];
}
- (void)leftClick10:(id)sender {
[self.delegate menuBarControllerStatusChanged:YES];
}

我在卡塔琳娜身上使用了这段代码。我不像其他一些答案所建议的那样performClick,而是必须手动定位弹出窗口。这适用于我使用外部显示器。

func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button?.action = #selector(onClick)
statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])

menu = NSMenu()
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
menu.delegate = self

}

@objc func onClick(sender: NSStatusItem) {

let event = NSApp.currentEvent!

if event.type == NSEvent.EventType.rightMouseUp {
// right click, show quit menu
statusItem.menu = menu;
menu.popUp(positioning: nil, 
at: NSPoint(x: 0, y: statusItem.statusBar!.thickness),
in: statusItem.button)
} else {
// main click 
}
}
@objc func menuDidClose(_ menu: NSMenu) {
// remove menu when closed so we can override left click behavior
statusItem.menu = nil
}

从这里已经给出的伟大答案中学习,我想出了一个替代方案。

此方法的优点是您只需设置一次NSMenu,而不必再忙于设置或删除它。

设置NSStatusBarButton

let status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
status.button!.image = NSImage(named: "status")
status.button!.target = self
status.button!.action = #selector(triggerStatus)
status.button!.menu = /* your NSMenu */
status.button!.sendAction(on: [.leftMouseUp, .rightMouseUp])

接收操作

@objc private func triggerStatus(_ button: NSStatusBarButton) {
guard let event = NSApp.currentEvent else { return }

switch event.type {
case .rightMouseUp:
NSMenu.popUpContextMenu(button.menu!, with: event, for: button)
case .leftMouseUp:
/* show your Popup or any other action */
default:
break
}
}

最新更新