OK người, có một giải pháp tốt, tốt đẹp và chính xác mà không có bất kỳ rối tung xung quanh.
Thứ nhất, tôi muốn giải thích lý do tại sao quá trình Windows GUI chuyển sang nền khi wx.Frame.Show (MyFrame, False) được gọi.
Giải thích rất ngắn và bỏ qua chi tiết là Windows xem Cửa sổ và ứng dụng giống như vậy.
I.e. Phần tử chính của ứng dụng MS Windows là cửa sổ GUI chính của bạn.
Vì vậy, khi cửa sổ này bị ẩn, một ứng dụng không còn GUI nữa và tiếp tục chạy ẩn.
Mac OS X coi ứng dụng là ứng dụng của bạn và bất kỳ cửa sổ nào bạn chọn để đưa vào đó đều là con của nó để nói.
Điều này cho phép bạn chạy ứng dụng trong khi không hiển thị cửa sổ nhưng là thanh trình đơn, từ đó bạn có thể chọn một hành động mà sau đó sẽ tạo ra một cửa sổ cần thiết.
Rất tiện dụng cho người chỉnh sửa nơi bạn có thể mở nhiều tệp cùng lúc, mỗi tệp trong cửa sổ riêng và khi bạn đóng cửa sổ cuối cùng, bạn vẫn có thể mở một tệp mới hoặc tạo một tệp trống, v.v.
Do đó, phần tử chính của ứng dụng Mac OS X là bản thân ứng dụng và đó là lý do tại sao ứng dụng vẫn mở sau khi cửa sổ cuối bị ẩn, một cách hợp lý. Phá hủy thanh menu của nó cũng sẽ không giúp ích gì. Tên của ứng dụng sẽ vẫn tồn tại trong Dock và trong trình chuyển đổi ứng dụng và trong Force Quit. Bạn sẽ có thể chuyển sang nó và không làm gì cả. : D Nhưng, may mắn thay, Mac cung cấp cho chúng tôi chức năng để đặt nó vào nền mặc dù. Và hàm này đã được đề cập đến setApplicationActivationPolicy() từ đối tượng NSApp.
Sự cố là đặt tên của nó trong AppKit của Python, là NSApp.setActivationPolicy_(). Để làm phức tạp thêm các vấn đề, nó không có sẵn trực tiếp từ trình tương tác của Python nhưng nó phải được gọi ít nhất là từ một mô-đun được nhập khẩu.
Tại sao? Tôi không có ý kiến. Dù sao ở đây là một ví dụ hoàn chỉnh cho việc ném một ứng dụng vào nền mà sẽ làm việc trên Mac và Windows.
Tôi đã không thử trên Linux, kết hợp hành vi của Mac và Windows trong vấn đề trình bày một ứng dụng, vì vậy, cho dù chỉ ẩn một cửa sổ sẽ là đủ vẫn còn để được nhìn thấy.
Hãy thử và gửi bản chỉnh sửa để làm ví dụ về nhiều nền tảng hơn.
Ví dụ:
"""
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
# Use Info.plist values to know whether our process started as daemon
# Also, change this dict in case anyone is later checking it (e.g. some module)
# Note: Changing this dict doesn't change Info.plist file
info = NSBundle.mainBundle().infoDictionary()
def SendToBackground():
# Change info, just in case someone checks it later
info["LSUIElement"] = "1"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited)
def ReturnToForeground():
# Change info, just in case someone checks it later
info["LSUIElement"] = "0"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular)
else:
# Simulate Mac OS X App - Info.plist
info = {"LSUIElement": "0"} # Assume non background at startup
# If programmer chose not to display GUI at startup then she/he should change this before calling ReturnToForeground()
# To preserve consistency and allow correct IsDaemon() answer
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)
# On Mac OS X, you wouldn't be able to quit the app without the menu bar:
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()
Tất nhiên, ví dụ này có thể được bắt đầu từ thiết bị đầu cuối hoặc có cửa sổ CLI. Trong trường hợp này, điều khiển thiết bị đầu cuối trên chương trình của bạn sẽ vẫn mở trong khi ứng dụng chỉ xuất hiện và biến mất.
Để hoàn daemon GUI của bạn, bạn nên bắt đầu với nó pythonw (trên Windows) hoặc khởi động nó từ tập tin daemontest.pyw,
và trên máy Mac, bạn nên sử dụng:
% nohup python daemontest.py &
hoặc bó nó với py2app hoặc sử dụng trình khởi chạy Python đi kèm với python.org phiên bản Python để khởi động daemontest.py mà không cần thiết bị đầu cuối.
Lưu ý: Ví dụ này bị lỗ hổng tương tự trên Mac OS X được đề cập trên các liên kết tôi đã cung cấp trong câu hỏi của mình. Tôi đề cập đến vấn đề tập trung sai và thanh trình đơn không xuất hiện ngay lập tức khi ứng dụng đến từ nền. Người dùng phải chuyển đổi và quay lại ứng dụng mới được trả về để ứng dụng hoạt động bình thường. Tôi hy vọng ai đó cũng sẽ giải quyết vấn đề này. Và sớm thôi. Nó khá là khó chịu.
Một lưu ý nữa: Nếu bạn có chủ đề đang chạy trong chương trình của mình, hãy tạm dừng các chủ đề trong khi thực hiện quá trình sao chép và loại trừ. Đặc biệt là nếu họ đang giao tiếp với một ứng dụng khác bằng cách sử dụng các sự kiện của Apple. Thành thật mà nói, một cái gì đó về wx.Timers nên được thực hiện quá. Nếu bạn không cẩn thận, bạn có thể gặp sự cố rò rỉ xung quanh NSAutoreleasePool và/hoặc SegmentationFault không tồn tại khi chấm dứt chương trình.
P.S Tại sao bạn không thực hiện giải pháp 'info.plist'? –
Bởi vì điều đó có nghĩa là quá trình bắt đầu ở chế độ nền và bất kỳ GUI nào tôi muốn hiển thị đều OK, nhưng tôi sẽ không thể chuyển sang ứng dụng này sau khi tôi mất tiêu điểm, tức là biểu tượng Dock sẽ không xuất hiện. Điều đó có thể được sửa chữa bằng cách sử dụng TransformProcessType() mà tôi không biết làm thế nào để gọi từ Python một lần nữa, và nơi nó được ẩn, nếu nó được thực hiện ở tất cả. Nhưng, ngay cả khi tôi làm điều này, nó sẽ có nghĩa là tôi phải có 2 quy trình. Một daemon, vẫn giữ daemon và một GUI, gọi lại ứng dụng khi được yêu cầu và yêu cầu nó chạy như một daemon. – Dalen
Nếu tôi làm điều này, tôi sẽ phải sử dụng một số phương pháp IPC để thay đổi các thông số của phần daemon của ứng dụng hoặc giết nó và khởi động lại nó với các tham số mới bất cứ khi nào người dùng thay đổi một số thiết lập trong phần GUI.Chưa kể đến báo cáo lỗi. Tôi sẽ phải sử dụng IPC hoặc hệ thống thoát mã hoặc tín hiệu hoặc tôi không biết những gì để nhận ra điều này. Nhiều biến chứng bao gồm cả rằng tôi cần wx cho dịch vụ "daemon" để hoạt động chính xác. Nó không phải là không thể, và tôi sẽ làm một cái gì đó như thế nếu tôi phải, nhưng tôi sẽ cố gắng trả lời của bạn đầu tiên. Tôi sẽ cho bạn biết những gì đã xảy ra. – Dalen