大家好,这里有一个好的、正确的解决方案,没有任何麻烦。
首先,我想解释一下为什么在调用 wx.Frame.Show(MyFrame, False) 后,Windows GUI 进程会进入后台。
简单来说,Windows 认为窗口和应用程序是同一件事。
也就是说,MS Windows 应用程序的主要元素就是您的主 GUI 窗口。
因此,当此窗口被隐藏时,应用程序将没有更多的 GUI 并继续在后台运行。
Mac OS X 则认为应用程序就是您的应用程序,您选择放置在其中的任何窗口都是其子级。
这使您能够在没有窗口但有菜单栏的情况下运行应用程序,从中选择操作,然后生成所需的窗口。
对于编辑器非常方便,您可能同时打开了多个文件,每个文件都在自己的窗口中,当您关闭最后一个文件时,您仍然可以打开一个新文件或创建一个空文件等等。
因此,Mac OS X 应用程序的主要元素是应用程序本身,这就是为什么在隐藏最后一个窗口后它仍然保持打开状态的原因。销毁其菜单栏也无法解决问题。应用程序的名称将在 Dock、应用程序切换器和强制退出中保留。您将能够切换到它并且什么都不能做。 :D
但是,幸运的是,Mac 为我们提供了将其置于后台的函数。这个函数已经在 NSApp 对象的 setApplicationActivationPolicy() 中提到过。
问题在于 Python 的 AppKit 中它的命名是 NSApp.setActivationPolicy_()。更进一步使问题变得复杂的是,它无法直接从 Python 的交互式 shell 中使用,而必须至少从一个导入的模块中调用。
为什么?我不知道。无论如何,下面是一个在 Mac 和 Windows 上将应用程序放入后台的完整示例。
我没有在 Linux 上尝试过它,因为在呈现应用程序方面,Linux 结合了 Mac 和 Windows 的行为,所以是否仅隐藏一个窗口就足够还有待观察。
欢迎尝试并提交编辑,使示例更具跨平台性。
示例:
"""
This app will show you small window with the randomly generated code that will confirm that reopened window is still the same app returned from background,
and the button allowing you to send it to background.
After you send it to background, wait 8 seconds and application will return to foreground again.
Too prove that the application is continuing its work in the background, the app will call wx.Bell() every second.
You should hear the sound while app is in the foreground and when it is in background too.
Merry Christmas and a happy New Year!
"""
import wx
import random, sys
if sys.platform=="darwin":
from AppKit import NSBundle, NSApp, NSAutoreleasePool, NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyProhibited
info = NSBundle.mainBundle().infoDictionary()
def SendToBackground ():
info["LSUIElement"] = "1"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited)
def ReturnToForeground ():
info["LSUIElement"] = "0"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular)
else:
info = {"LSUIElement": "0"}
def SendToBackground ():
info["LSUIElement"] = "1"
def ReturnToForeground ():
info["LSUIElement"] = "0"
def IsDaemon ():
return info["LSUIElement"]=="1"
class Interface (wx.Frame):
def __init__ (self):
wx.Frame.__init__(self, None, -1, "Test", pos=(100, 100), size=(100, 100))
wx.StaticText(self, -1, "Test code: "+str(random.randint(1000, 10000)), pos=(10, 10), size=(80, 20))
b = wx.Button(self, -1, "DAEMONIZE ME", size=(80, 20), pos=(10, 50))
wx.EVT_BUTTON(self, b.GetId(), self.OnDaemonize)
self.belltimer = wx.Timer(self)
wx.EVT_TIMER(self, self.belltimer.GetId(), self.OnBellTimer)
self.belltimer.Start(1000)
if sys.platform=="darwin":
self.SetMenuBar(wx.MenuBar())
self.Show()
def OnBellTimer (self, e):
wx.Bell()
def OnDaemonize (self, e):
self.Show(False)
SendToBackground()
self.timer = wx.Timer(self)
wx.EVT_TIMER(self, self.timer.GetId(), self.OnExorcize)
self.timer.Start(8000)
def OnExorcize (self, e):
self.timer.Stop()
ReturnToForeground()
self.Show()
self.Raise()
app = wx.App()
i = Interface()
app.MainLoop()
当然,这个例子可以从终端或CLI窗口开始。在这种情况下,终端对程序的控制将保持打开状态,而应用程序将只是出现和消失。
要完成GUI守护进程,您应该使用pythonw(在Windows上)启动它,或从daemontest.pyw文件中启动它,在Mac上您应该使用:
% nohup python daemontest.py &
或者将其与py2app捆绑在一起,或使用python.org Python版本附带的Python启动器启动daemontest.py而无需终端。
注意:此示例在Mac OS X上存在与我在问题中提供的链接中提到的相同缺陷。我指的是当应用程序从后台返回时,焦点错误和菜单栏不立即出现的问题。用户必须切换并重新回到新返回的应用程序才能正常工作。我希望有人也能解决这个问题。尽快。这非常烦人。
还有一点需要注意:如果您的程序中有正在运行的线程,请在daemonizing和exorcizing时暂停它们。特别是如果它们正在使用Apple事件与另一个应用程序通信。坦白地说,还应该对wx.Timers进行一些操作。如果不小心,您可能会在程序终止时遇到围绕不存在的NSAutoreleasePool和/或SegmentationFault的泄漏问题。
info.plist
的解决方案? - Jonah Fleming