2014-03-05 35 views
1

我在寫一個wxPython應用程序,它將進行相當多的數據分析和顯示。當兩個線程同時嘗試在GUI中更改某些內容時,我迄今編寫它的方式導致了問題。我想要做的就是建立我自己的simple queue running on the main thread,這樣我就可以確保一次一個UI更新。在wxPython應用程序中實現我自己的事件循環

雖然我在設置事件循環的過程中遇到了困難。一般來說,你會做這樣的事情

while True: 
    try: 
     callback = queue.get(False) 
    except Queue.Empty: 
     break 
    callback() 

我認爲,如果我運行的代碼,然後WX將不能夠做到的事情,因爲它永遠不會收到任何事件或任何東西,因爲控制永遠不會離開我無限循環。我怎樣才能使這種結構與WX事件循環共存?或者更一般地說,在WX應用程序中,我如何確保某個任務只能在主線程上運行?

+0

使用callafter - http://wxpython.org/Phoenix/docs/html/functions.html?highlight=call%20after#CallAfter,這樣,您的通話將被添加到隊列mainloops。 – Yoriz

+0

CallAfter和CallLater在這裏是你的朋友。此外,讓這個線程「睡眠」也會給wx一個處理事情的機會。 –

+0

@Yoriz你可以把它變成答案嗎? – bdesham

回答

3

你可以使用wx.callafter,它需要一個可調用的對象,在當前和未決事件處理程序完成後在guis主循環中調用。任何額外的位置或關鍵字參數在調用時會傳遞給可調用對象。

下面是一個GUI代碼的例子,它在運行一個單獨的線程並在主線程中更新GUI時利用了wx.CallAfter。

的代碼是由安德烈Gavana這是在wxpython Phoenix docs

#!/usr/bin/env python 

# This sample shows how to take advantage of wx.CallAfter when running a 
# separate thread and updating the GUI in the main thread 

import wx 
import threading 
import time 

class MainFrame(wx.Frame): 

    def __init__(self, parent): 
     wx.Frame.__init__(self, parent, title='CallAfter example') 

     panel = wx.Panel(self) 
     self.label = wx.StaticText(panel, label="Ready") 
     self.btn = wx.Button(panel, label="Start") 
     self.gauge = wx.Gauge(panel) 

     sizer = wx.BoxSizer(wx.VERTICAL) 
     sizer.Add(self.label, proportion=1, flag=wx.EXPAND) 
     sizer.Add(self.btn, proportion=0, flag=wx.EXPAND) 
     sizer.Add(self.gauge, proportion=0, flag=wx.EXPAND) 

     panel.SetSizerAndFit(sizer) 
     self.Bind(wx.EVT_BUTTON, self.OnButton) 

    def OnButton(self, event): 
     """ This event handler starts the separate thread. """ 
     self.btn.Enable(False) 
     self.gauge.SetValue(0) 
     self.label.SetLabel("Running") 

     thread = threading.Thread(target=self.LongRunning) 
     thread.start() 

    def OnLongRunDone(self): 
     self.gauge.SetValue(100) 
     self.label.SetLabel("Done") 
     self.btn.Enable(True) 

    def LongRunning(self): 
     """This runs in a different thread. Sleep is used to 
     simulate a long running task.""" 
     time.sleep(3) 
     wx.CallAfter(self.gauge.SetValue, 20) 
     time.sleep(5) 
     wx.CallAfter(self.gauge.SetValue, 70) 
     time.sleep(4) 
     wx.CallAfter(self.OnLongRunDone) 

if __name__ == "__main__": 
    app = wx.App(0) 
    frame = MainFrame(None) 
    frame.Show() 
    app.MainLoop() 
1

只需在某個對象(通常是應用程序或主窗口)中輸入wx.Event即可。它們將按先進先出順序處理,但它們將與主線程本身發生的其他GUI事件混合在一起。當然,您還需要爲這些事件提供實際的處理程序,以實現您所需的任何邏輯。

1

爲後人發現,這裏是我使用Yoriz的答案創建的裝飾。

def run_on_main_thread(fn): 
    """Decorator. Forces the function to run on the main thread. 

    Any calls to a function that is wrapped in this decorator will 
    return immediately; the return value of such a function is not 
    available. 

    """ 
    @wraps(fn) 
    def deferred_caller(*args, **kwargs): 
     # If the application has been quit then trying to use 
     # CallAfter will trigger an assertion error via the line 
     #  assert app is not None, 'No wx.App created yet' 
     # Since assertions are optimized out when -O is used, 
     # though, it seems safest to perform the check ourselves, 
     # and also catch the exception just in case. 
     if wx.GetApp() is not None: 
      try: 
       wx.CallAfter(fn, *args, **kwargs) 
      except AssertionError: 
       pass 

    return deferred_caller 
相關問題