【问题标题】:Problem showing main window from NSStatusItem从 NSStatusItem 显示主窗口的问题
【发布时间】:2020-08-26 12:01:43
【问题描述】:

我有一个只有NSStatusItem 的小型应用程序,带有一个设置窗口。从NSStatusItem 的菜单中,我想重新激活主窗口。

应用程序保留一个 NSWindow 实例,该实例设置为在关闭时不释放。 关闭窗口后,我将应用程序的激活策略更改为accessory,因此 Dock 图标对用户隐藏。

当用户重新打开窗口时,我将激活策略改回regular并再次显示窗口。

这是隔离问题的示例应用中我的应用委托的全部内容:

import Cocoa
import SwiftUI

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!
    var statusItem: NSStatusItem!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the window and set the content view.
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: EmptyView())
        window.makeKeyAndOrderFront(nil)
        window.isReleasedWhenClosed = false
        window.delegate = self

        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
        statusItem.button?.title = "X"
        statusItem.button?.target = self
        statusItem.button?.action = #selector(toggleWindow(sender:))

        NSApp.setActivationPolicy(.accessory)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    @objc func toggleWindow(sender: AnyObject) {
        if window.isVisible {
            window.close()
        } else {
            window.makeKeyAndOrderFront(nil)
            NSApp.activate(ignoringOtherApps: true)
            NSApp.setActivationPolicy(.regular)
        }
    }
}

extension AppDelegate: NSWindowDelegate {

    func windowWillClose(_ notification: Notification) {
        NSApp.setActivationPolicy(.accessory)
    }

}

该应用是 Xcode 11 的默认模板,但我将主视图更改为 EmptyView(),因此不需要额外的类。

如果我用信号量按钮关闭窗口,然后用状态栏图标重新打开它,它工作正常。

但是,如果我通过 CMD-W 关闭它并重新打开它,我会返回窗口,但菜单仍然处于选中状态,就好像之前的关闭命令没有完成一样。

由于菜单处于这种奇怪的状态,应用程序不会响应事件,直到菜单问题得到解决(例如,通过单击另一个菜单项)。

更新:

作为一种解决方法,我发现如果我在几毫秒后更改激活策略,菜单问题就解决了:

    func windowWillClose(_ notification: Notification) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            NSApp.setActivationPolicy(.accessory)
        }
    }

但是,一旦窗口出现,它仍然不能正确处理事件(错过了第一次点击)。

【问题讨论】:

    标签: macos cocoa


    【解决方案1】:

    以下在我的系统上运行正常,并且没有显示有问题的行为;也许我误解了这个问题。这是一个可以在 Xcode 中运行的程序化演示;出于某种原因,从命令行运行时所有菜单都不起作用。

    import Cocoa
    
    class AppDelegate: NSObject, NSApplicationDelegate {
     var window: NSWindow!
     var statusItem: NSStatusItem!
    
    @objc func showMainWindow() {
        window.makeKeyAndOrderFront(nil)
    }
    
    @objc func hideMainWindow() {
        window.orderOut(nil)
    }
    
    @objc func myBtnAction(_ sender:AnyObject ) {
      NSSound.beep()
    }
    
    @objc func myMenuAction(_ sender:AnyObject ) {
      NSSound.beep()
    }
    
    func buildMenu() {
     let mainMenu = NSMenu()
     NSApp.mainMenu = mainMenu
     // **** App menu **** //
     let appMenuItem = NSMenuItem()
     mainMenu.addItem(appMenuItem)
     let appMenu = NSMenu()
     appMenuItem.submenu = appMenu
     appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
     // **** File menu **** //
     let fileMenuItem = NSMenuItem()
     let fileMenu = NSMenu(title: "File")
     fileMenu.addItem(withTitle: "Beep", action:#selector(self.myMenuAction), keyEquivalent: "b")
     fileMenuItem.submenu = fileMenu
     mainMenu.addItem(fileMenuItem)
    }
    
    func buildWnd() {
    
    let _wndW : CGFloat = 400
    let _wndH : CGFloat = 300
    
     window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .miniaturizable, .resizable], backing:.buffered, defer:false)
     window.center()
     window.title = "Swift Test Window"
    
     // **** TextView **** //
     let scrlView = NSScrollView (frame:NSMakeRect( 10, 60, _wndW - 20, _wndH - 70 ))
     window.contentView!.addSubview (scrlView)
     scrlView.hasVerticalScroller = true
     scrlView.autoresizingMask = [.height, .width]
     let txtView = NSTextView (frame:NSMakeRect( 0, 0, _wndW - 20, _wndH - 70 ))
     txtView.font = NSFont(name:"Menlo Bold", size: 13.0)
     scrlView.documentView = txtView
    
     // **** Button **** //
     let myBtn = NSButton (frame:NSMakeRect( 150, 15, 95, 30 ))
     myBtn.bezelStyle = .rounded
     myBtn.autoresizingMask = [.minXMargin,.maxYMargin]
     myBtn.title = "Beep"
     myBtn.action = #selector(self.myBtnAction(_:))
     window.contentView!.addSubview (myBtn)
    
    // **** Quit btn **** //
    let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
     quitBtn.bezelStyle = .circular
     quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
     quitBtn.title = "Q"
     quitBtn.action = #selector(NSApplication.terminate)
     window.contentView!.addSubview(quitBtn)
    }
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
     buildMenu()
     buildWnd()
     statusItem = NSStatusBar.system.statusItem(withLength: 100)
     statusItem.button!.title = "foobar"
     let menu = NSMenu()
     let showMenuItem = NSMenuItem(title: "Show Window", action:#selector(self.showMainWindow), keyEquivalent: "")
     menu.addItem(showMenuItem)
    let hideMenuItem = NSMenuItem(title: "Hide Window", action:#selector(self.hideMainWindow), keyEquivalent: "")
     menu.addItem(hideMenuItem)
     menu.addItem(.separator())
     menu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
     statusItem.menu = menu
    
    }
    }
    let appDelegate = AppDelegate()
    
    // **** main.swift **** //
    let app = NSApplication.shared
    app.setActivationPolicy(.regular)
    app.delegate = appDelegate
    app.activate(ignoringOtherApps:true)
    app.run()
    
    

    要在 Xcode 中运行,请创建一个 Swift 应用程序并添加一个 main.swift 文件(文件/新建/文件)。将 import Foundation 更改为导入 Cocoa。从演示中复制/粘贴 main.swift 代码。完全删除旧的 AppDelegate 并复制/粘贴演示的 AppDelegate。点击运行。应该能够从状态栏中显示/隐藏主窗口。文本视图始终保持其焦点在我的系统上。没试过用xib。

    【讨论】:

    • 谢谢。您的示例有点过于简单了......我的应用程序有一个用于菜单的笔尖,并使用 SwiftUI 作为其主视图。我使用关闭按钮(在您的示例中不存在)关闭窗口,所以我不确定它是否命中了相同的代码路径。我会尝试更好地隔离它并发布更新。
    • 我最初在主窗口上有一个关闭按钮,但是当它关​​闭时,如果用户尝试从状态栏中再次显示它,应用程序将会崩溃。我以为你在窗口中使用显示/隐藏。
    • 我刚刚更新了我的问题,提供了更多详细信息以及我当前的解决方法。再次感谢您,@apodidae。
    • 基于文档的应用程序应允许用户重复打开和关闭窗口,同时保留功能正常的菜单栏。
    • 是的...但这不是基于文档的应用程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-20
    • 2021-02-22
    • 1970-01-01
    相关资源
    最近更新 更多