python和Cocoa:关于状态栏脚本

时间:2013-07-02 20:41:49

标签: python cocoa statusbar nsrunloop

我正在创建一个小脚本,用于检查我的Gmail帐户中的邮件数量并将其打印在 状态栏。函数gmail()返回新电子邮件的数量。我有几个问题,但首先这是我到目前为止编写的代码(显然我是新手):

class MyApplicationAppDelegate(NSObject):

var = 1

def applicationDidFinishLaunching_(self, sender):
    NSLog("Application did finish launching.")

    global ngmail

    self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)

    while var == 1 :  
        ngmail2 = gmail();
        if  ngmail2 !=ngmail:
            self.statusItem.setTitle_("loading")
            self.statusItem.setTitle_(ngmail2)
            ngmail = ngmail2
        time.sleep(6)

1)为什么我需要“self.statusItem.setTitle _(”loading“)”这一行?没有那条线就不会自我更新。我真的不知道为什么。

2)它按原样运行,但每当我接近状态栏中的数字时,就会出现旋转轮。
我想原因是因为我正在使用while,而我应该使用像nsrunloop之类的东西。有人可以就此提出建议吗?

3)如果我让我的Mac进入睡眠状态并将其唤醒,脚本将停止工作。有解决方案吗也许这与上面的问题2)有关。

谢谢!

1 个答案:

答案 0 :(得分:0)

你所有的问题都来自你阻止主线程的事实。

在Cocoa或几乎任何其他GUI框架中,主线程运行一个循环,等待下一个事件,调用事件处理程序,并重复直到退出。

您的事件处理程序applicationDidFinishLaunching_永远不会返回。这意味着Cocoa永远无法处理下一个事件。最终,操作系统会注意到你没有回应并提起沙滩球。

对于Cocoa,有时候每次你给它一个机会就会偷偷摸摸一些其他的事件,比如在setTitle_来电时,即使你没有回应,操作系统也可以伪造一些东西,比如保持窗口重绘,因此您的应用程序无法响应并不总是很明显。但这并不意味着你不需要解决问题。

有很多方法可以做到这一点,但最简单的方法是使用后台线程。然后,applicationDidFinishLaunching_可以启动后台线程然后立即返回,允许主线程返回其作业处理事件。

唯一棘手的问题是在后台线程上运行的代码无法调用UI对象。那么,你怎么办呢?

这就是performSelectorOnMainThread_withObject_waitUntilDone_的用途。


以下是一个例子:

class MyApplicationAppDelegate(NSObject):

    var = 1

    def background_work(self):
        global ngmail

        while var == 1 :  
            ngmail2 = gmail();
            if  ngmail2 !=ngmail:
                self.statusItem.setTitle_("loading")
                self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
            time.sleep(6)

    def applicationDidFinishLaunching_(self, sender):
        NSLog("Application did finish launching.")
        self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
        self.background_worker = threading.Thread(target=self.background_work)
        self.background_worker.start()    

唯一棘手的一点是你必须使用ObjC名称作为选择器(setTitle:),而不是Python名称(setTitle_)。


但是,您的代码还有另一个微妙的错误:var实际上并未同步,因此您可以在主线程中更改其值,而无需后台线程注意到。

最重要的是,执行sleep(6)意味着退出应用最多需要6秒钟,因为后台线程无法访问检查var的代码,直到完成睡眠。

您可以使用Condition来解决这两个问题。

class MyApplicationAppDelegate(NSObject):

    var = 1
    condition = threading.Condition()

    def background_work(self):
        global ngmail

        with condition:
            while var == 1:
                ngmail2 = gmail();
                if ngmail2 != ngmail:
                    self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
                condition.wait(6)

    @classmethod
    def shutdown_background_threads(cls):
        with condition:
            var = 0
            condition.notify_all()

(我假设您有意使用了var的类属性而不是实例属性,因此我同样使条件成为类属性,将关闭方法作为类方法。)