不为 NSMenu 调用 validateMenuItem 或 menuWillOpen
validateMenuItem or menuWillOpen not called for NSMenu
我的 Mac 应用程序有一个 NSMenu,其委托函数 validateMenuItem
和 menuWillOpen
从未被调用。到目前为止 none 的在线解决方案已提供帮助。
看来我做的一切都是对的:
- 菜单项的选择器在同一个 class。
- 管理它的class继承自NSMenuDelegate
我想描述我的问题的最好方法是 post 相关代码。任何帮助将不胜感激。
import Cocoa
class UIManager: NSObject, NSMenuDelegate {
var statusBarItem = NSStatusBar.system().statusItem(withLength: -2)
var statusBarMenu = NSMenu()
var titleMenuItem = NSMenuItem()
var descriptionMenuItem = NSMenuItem()
// ...
override init() {
super.init()
createStatusBarMenu()
}
// ...
func createStatusBarMenu() {
// Status bar icon
guard let icon = NSImage(named: "iconFrame44")
else { NSLog("Error setting status bar icon image."); return }
icon.isTemplate = true
statusBarItem.image = icon
// Create Submenu items
let viewOnRedditMenuItem = NSMenuItem(title: "View on Reddit...", action: #selector(viewOnRedditAction), keyEquivalent: "")
let saveThisImageMenuItem = NSMenuItem(title: "Save This Image...", action: #selector(saveThisImageAction), keyEquivalent: "")
// Add to title submenu
let titleSubmenu = NSMenu(title: "")
titleSubmenu.addItem(descriptionMenuItem)
titleSubmenu.addItem(NSMenuItem.separator())
titleSubmenu.addItem(viewOnRedditMenuItem)
titleSubmenu.addItem(saveThisImageMenuItem)
// Create main menu items
titleMenuItem = NSMenuItem(title: "No Wallpaperer Image", action: nil, keyEquivalent: "")
titleMenuItem.submenu = titleSubmenu
getNewWallpaperMenuItem = NSMenuItem(title: "Update Now", action: #selector(getNewWallpaperAction), keyEquivalent: "")
let preferencesMenuItem = NSMenuItem(title: "Preferences...", action: #selector(preferencesAction), keyEquivalent: "")
let quitMenuItem = NSMenuItem(title: "Quit Wallpaperer", action: #selector(quitAction), keyEquivalent: "")
// Add to main menu
let statusBarMenu = NSMenu(title: "")
statusBarMenu.addItem(titleMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(getNewWallpaperMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(preferencesMenuItem)
statusBarMenu.addItem(quitMenuItem)
statusBarItem.menu = statusBarMenu
}
// ...
// Called whenever the menu is about to show. we use it to change the menu based on the current UI mode (offline/updating/etc)
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
NSLog("Validating menu item")
if (menuItem == getNewWallpaperMenuItem) {
if wallpaperUpdater!.state == .Busy {
DispatchQueue.main.async {
self.getNewWallpaperMenuItem.title = "Updating Wallpaper..."
}
return false
} else if wallpaperUpdater!.state == .Offline {
DispatchQueue.main.async {
self.getNewWallpaperMenuItem.title = "No Internet Connection"
}
return false
} else {
DispatchQueue.main.async {
self.preferencesViewController.updateNowButton.title = "Update Now"
}
return true
}
}
return true
}
// Whenever the menu is opened, we update the submitted time
func menuWillOpen(_ menu: NSMenu) {
NSLog("Menu will open")
if !noWallpapererImageMode {
DispatchQueue.main.async {
self.descriptionMenuItem.title = "Submitted \(self.dateSimplifier(self.updateManager!.thisPost.attributes.created_utc as Date)) by \(self.updateManager!.thisPost.attributes.author) to /r/\(self.updateManager!.thisPost.attributes.subreddit)"
}
}
}
// ...
// MARK: User-initiated actions
func viewOnRedditAction() {
guard let url = URL(string: "http://www.reddit.com\(updateManager!.thisPost.permalink)")
else { NSLog("Could not convert post permalink to URL."); return }
NSWorkspace.shared().open(url)
}
// Present a save panel to let the user save the current wallpaper
func saveThisImageAction() {
DispatchQueue.main.async {
let savePanel = NSSavePanel()
savePanel.makeKeyAndOrderFront(self)
savePanel.nameFieldStringValue = self.updateManager!.thisPost.id + ".png"
let result = savePanel.runModal()
if result == NSFileHandlingPanelOKButton {
let exportedFileURL = savePanel.url!
guard let lastImagePath = UserDefaults.standard.string(forKey: "lastImagePath")
else { NSLog("Error getting last post ID from persistent storage."); return }
let imageData = try! Data(contentsOf: URL(fileURLWithPath: lastImagePath))
if (try? imageData.write(to: exportedFileURL, options: [.atomic])) == nil {
NSLog("Error saving image to user-specified folder.")
}
}
}
}
func getNewWallpaperAction() {
updateManager!.refreshAndReschedule(userInitiated: true)
}
func preferencesAction() {
preferencesWindow.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
func quitAction() {
NSApplication.shared().terminate(self)
}
}
menuWillOpen:
属于NSMenuDelegate
协议;要调用相关菜单,需要委托人:
let statusBarMenu = NSMenu(title: "")
statusBarMenu.delegate = self
validateMenuItem:
属于NSMenuValidation
非正式协议;要调用相关菜单 items 必须有一个 target
。以下段落摘自 Apple 的 Application Menu and Pop-up List Programming Topics 文档:
When you use automatic menu enabling, NSMenu updates the status of every menu item whenever a user event occurs. To update the status of a menu item, NSMenu first determines the target of the item and then determines whether the target implements validateMenuItem: or validateUserInterfaceItem: (in that order).
let myMenuItem = NSMenuItem()
myMenuItem.target = self
myMenuItem.action = #selector(doSomething)
上述(已接受的)答案指出必须设置目标 ,这有点误导。不需要设定目标。您也可以(例如)成为第一响应者,而无需明确设置目标。
详细信息可以在appel文档中找到,可以在这里找到:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html
虽然使用 swift 时有一个棘手的部分:
如果 validateMenuItem 没有被调用,那么确保你的 class 不仅声明符合 NSMenuDelegate,而且 符合 NSMenuItemValidation。
class SomeClass: NSMenuDelegate, NSMenuItemValidation {
...
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
return true // or whatever, on whichever condition
}
}
我的 Mac 应用程序有一个 NSMenu,其委托函数 validateMenuItem
和 menuWillOpen
从未被调用。到目前为止 none 的在线解决方案已提供帮助。
看来我做的一切都是对的:
- 菜单项的选择器在同一个 class。
- 管理它的class继承自NSMenuDelegate
我想描述我的问题的最好方法是 post 相关代码。任何帮助将不胜感激。
import Cocoa
class UIManager: NSObject, NSMenuDelegate {
var statusBarItem = NSStatusBar.system().statusItem(withLength: -2)
var statusBarMenu = NSMenu()
var titleMenuItem = NSMenuItem()
var descriptionMenuItem = NSMenuItem()
// ...
override init() {
super.init()
createStatusBarMenu()
}
// ...
func createStatusBarMenu() {
// Status bar icon
guard let icon = NSImage(named: "iconFrame44")
else { NSLog("Error setting status bar icon image."); return }
icon.isTemplate = true
statusBarItem.image = icon
// Create Submenu items
let viewOnRedditMenuItem = NSMenuItem(title: "View on Reddit...", action: #selector(viewOnRedditAction), keyEquivalent: "")
let saveThisImageMenuItem = NSMenuItem(title: "Save This Image...", action: #selector(saveThisImageAction), keyEquivalent: "")
// Add to title submenu
let titleSubmenu = NSMenu(title: "")
titleSubmenu.addItem(descriptionMenuItem)
titleSubmenu.addItem(NSMenuItem.separator())
titleSubmenu.addItem(viewOnRedditMenuItem)
titleSubmenu.addItem(saveThisImageMenuItem)
// Create main menu items
titleMenuItem = NSMenuItem(title: "No Wallpaperer Image", action: nil, keyEquivalent: "")
titleMenuItem.submenu = titleSubmenu
getNewWallpaperMenuItem = NSMenuItem(title: "Update Now", action: #selector(getNewWallpaperAction), keyEquivalent: "")
let preferencesMenuItem = NSMenuItem(title: "Preferences...", action: #selector(preferencesAction), keyEquivalent: "")
let quitMenuItem = NSMenuItem(title: "Quit Wallpaperer", action: #selector(quitAction), keyEquivalent: "")
// Add to main menu
let statusBarMenu = NSMenu(title: "")
statusBarMenu.addItem(titleMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(getNewWallpaperMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(preferencesMenuItem)
statusBarMenu.addItem(quitMenuItem)
statusBarItem.menu = statusBarMenu
}
// ...
// Called whenever the menu is about to show. we use it to change the menu based on the current UI mode (offline/updating/etc)
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
NSLog("Validating menu item")
if (menuItem == getNewWallpaperMenuItem) {
if wallpaperUpdater!.state == .Busy {
DispatchQueue.main.async {
self.getNewWallpaperMenuItem.title = "Updating Wallpaper..."
}
return false
} else if wallpaperUpdater!.state == .Offline {
DispatchQueue.main.async {
self.getNewWallpaperMenuItem.title = "No Internet Connection"
}
return false
} else {
DispatchQueue.main.async {
self.preferencesViewController.updateNowButton.title = "Update Now"
}
return true
}
}
return true
}
// Whenever the menu is opened, we update the submitted time
func menuWillOpen(_ menu: NSMenu) {
NSLog("Menu will open")
if !noWallpapererImageMode {
DispatchQueue.main.async {
self.descriptionMenuItem.title = "Submitted \(self.dateSimplifier(self.updateManager!.thisPost.attributes.created_utc as Date)) by \(self.updateManager!.thisPost.attributes.author) to /r/\(self.updateManager!.thisPost.attributes.subreddit)"
}
}
}
// ...
// MARK: User-initiated actions
func viewOnRedditAction() {
guard let url = URL(string: "http://www.reddit.com\(updateManager!.thisPost.permalink)")
else { NSLog("Could not convert post permalink to URL."); return }
NSWorkspace.shared().open(url)
}
// Present a save panel to let the user save the current wallpaper
func saveThisImageAction() {
DispatchQueue.main.async {
let savePanel = NSSavePanel()
savePanel.makeKeyAndOrderFront(self)
savePanel.nameFieldStringValue = self.updateManager!.thisPost.id + ".png"
let result = savePanel.runModal()
if result == NSFileHandlingPanelOKButton {
let exportedFileURL = savePanel.url!
guard let lastImagePath = UserDefaults.standard.string(forKey: "lastImagePath")
else { NSLog("Error getting last post ID from persistent storage."); return }
let imageData = try! Data(contentsOf: URL(fileURLWithPath: lastImagePath))
if (try? imageData.write(to: exportedFileURL, options: [.atomic])) == nil {
NSLog("Error saving image to user-specified folder.")
}
}
}
}
func getNewWallpaperAction() {
updateManager!.refreshAndReschedule(userInitiated: true)
}
func preferencesAction() {
preferencesWindow.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
func quitAction() {
NSApplication.shared().terminate(self)
}
}
menuWillOpen:
属于NSMenuDelegate
协议;要调用相关菜单,需要委托人:
let statusBarMenu = NSMenu(title: "")
statusBarMenu.delegate = self
validateMenuItem:
属于NSMenuValidation
非正式协议;要调用相关菜单 items 必须有一个 target
。以下段落摘自 Apple 的 Application Menu and Pop-up List Programming Topics 文档:
When you use automatic menu enabling, NSMenu updates the status of every menu item whenever a user event occurs. To update the status of a menu item, NSMenu first determines the target of the item and then determines whether the target implements validateMenuItem: or validateUserInterfaceItem: (in that order).
let myMenuItem = NSMenuItem()
myMenuItem.target = self
myMenuItem.action = #selector(doSomething)
上述(已接受的)答案指出必须设置目标 ,这有点误导。不需要设定目标。您也可以(例如)成为第一响应者,而无需明确设置目标。
详细信息可以在appel文档中找到,可以在这里找到:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html
虽然使用 swift 时有一个棘手的部分:
如果 validateMenuItem 没有被调用,那么确保你的 class 不仅声明符合 NSMenuDelegate,而且 符合 NSMenuItemValidation。
class SomeClass: NSMenuDelegate, NSMenuItemValidation {
...
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
return true // or whatever, on whichever condition
}
}