2014年10月24日 星期五

wxpython thread

最近寫python需要程式一直在迴圈裡面跑
如果用while true的話會當機,沒有辦法按下停止鍵
看了下面兩個非常好的reference來練習一下怎麼寫wxpython thread

Reference :


wxpython下有三個最安全的方式來寫thread
wx.PostEvent
wx.CallAfter
wx.CallLater

wx.PostEvent是最低階的方式,那那那那就先用它來寫寫看吧!!!!!

這個練習是使用者按下start,表示開始執行另一個thread,然後右邊會反覆出現紅色與綠色的按鍵還有說明,最下面顯示這個迴圈已經被跑了幾次,每次跑的時候先從txt中讀出上次已經跑的次數,然後再繼續累加(所以要先在同一個資料夾下面準備一個test_cnt.txt的檔案),順便練習讀寫file還有把file放在textctrl中顯示,按下stop則停止這個thread

wxglade建立好視窗之後要加入 from threading import Thread


這邊寫thread最主要的關鍵就是EVT_RESULT_ID


wx.frame中再用ResultEvent來跟thread溝通



wx.Frame中如果使用者按下start,則會連到thread class來執行run function


run function,先把txt中的值讀出來,int()string轉成int來累加,利用wx.PostEvent(self._notify_window,ResultEvent("Green")) 來告訴wx.Frame classGUI上要做甚麼變化,如果使用者按下STOP則會傳到abort,然後傳到run中停止執行緒




test.py
import wx

# begin wxGlade: dependencies
import gettext
# end wxGlade

# begin wxGlade: extracode
# end wxGlade

############### Definitions ###############
from threading import Thread
import time
import os
###########################################

############### Threads #######################

# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    """Simple event to carry arbitrary result data."""
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

class WorkerThread(Thread):
    def __init__(self,notify_window):
        Thread.__init__(self)
        self._notify_window = notify_window
        self._want_abort = 0
        self.start()

    def run(self):
        
        print("Read test_cnt.txt ")
        fread = file('test_cnt.txt','r')
        while True:           
            text = fread.readline()
            if len(text) == 0:
                break;
            print text
            cnt = int(text)
        fread.close()

        while True:
            time.sleep(1)
            if self._want_abort:
                wx.PostEvent(self._notify_window,ResultEvent(None))
                return

            wx.PostEvent(self._notify_window,ResultEvent("Green"))
            time.sleep(1)
            wx.PostEvent(self._notify_window,ResultEvent("Red"))
            time.sleep(1)
            fwrite = file('test_cnt.txt','w')
            cnt += 1                    
            fwrite.write("%s"%cnt)
            fwrite.close()
            time.sleep(1)
            wx.PostEvent(self._notify_window,ResultEvent("cnt"))

            
    def abort(self):
        self._want_abort = 1
        
################################################

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.window_1 = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D | wx.SP_BORDER)
        self.window_1_pane_1 = wx.Panel(self.window_1, wx.ID_ANY)
        self.start = wx.Button(self.window_1_pane_1, wx.ID_ANY, _("Start"))
        self.stop = wx.Button(self.window_1_pane_1, wx.ID_ANY, _("Stop"))
        self.window_1_pane_2 = wx.Panel(self.window_1, wx.ID_ANY)
        self.light = wx.Button(self.window_1_pane_2, wx.ID_ANY, _(""))
        self.status = wx.StaticText(self.window_1_pane_2, wx.ID_ANY, _("Status\n"))
        self.cnt = wx.TextCtrl(self.window_1_pane_2, wx.ID_ANY, "")

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

        ################# init function ##################
        rd_log = open(os.path.join('', 'test_cnt.txt'), 'r')
        self.cnt.SetValue(rd_log.read())
        rd_log.close()

        ################# function binding ###############
        self.Bind(wx.EVT_BUTTON,self.EVTSTART,self.start)
        self.Bind(wx.EVT_BUTTON,self.EVTSTOP,self.stop)
        EVT_RESULT(self,self.updateDisplay)
        self.worker = None
        ###################################################


        ################# function binding ###############
    def EVTSTART(self,event):
        print("Start testing")
        self.worker = WorkerThread(self)
        

    def EVTSTOP(self,event):
        print("End testing")
        self.worker.abort()

    def updateDisplay(self,event):
        if event.data is "Green":
            self.light.SetBackgroundColour(wx.Colour(0, 255, 127))
            self.status.SetLabel('Green')

        if event.data is "Red":
            self.light.SetBackgroundColour(wx.Colour(255, 0, 61))
            self.status.SetLabel('Red')

        if event.data is "cnt":
            rd_log = open(os.path.join('', 'test_cnt.txt'), 'r')
            self.cnt.SetValue(rd_log.read())
            rd_log.close()
        ###################################################

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle(_("Test"))
        self.start.SetMinSize((100, 80))
        self.stop.SetMinSize((100, 80))
        self.window_1_pane_1.SetMinSize((192, 257))
        self.light.SetMinSize((100, 40))
        self.window_1_pane_2.SetMinSize((188, 257))
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_2 = wx.BoxSizer(wx.VERTICAL)
        sizer_5 = wx.BoxSizer(wx.VERTICAL)
        sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_4 = wx.BoxSizer(wx.VERTICAL)
        sizer_4.Add(self.start, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 20)
        sizer_4.Add(self.stop, 0, wx.LEFT | wx.TOP, 20)
        sizer_3.Add(sizer_4, 1, wx.EXPAND, 0)
        self.window_1_pane_1.SetSizer(sizer_3)
        sizer_5.Add(self.light, 0, wx.LEFT | wx.TOP, 40)
        sizer_5.Add(self.status, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, 40)
        sizer_5.Add(self.cnt, 0, wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, 30)
        self.window_1_pane_2.SetSizer(sizer_5)
        self.window_1.SplitVertically(self.window_1_pane_1, self.window_1_pane_2)
        sizer_2.Add(self.window_1, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_2)
        sizer_2.Fit(self)
        self.Layout()
        # end wxGlade

# end of class MyFrame
if __name__ == "__main__":
    gettext.install("app") # replace with the appropriate catalog name

    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    Test = MyFrame(None, wx.ID_ANY, "")
    app.SetTopWindow(Test)
    Test.Show()
    app.MainLoop()



如果用callafter則如下
import wx

# begin wxGlade: dependencies
import gettext
# end wxGlade

# begin wxGlade: extracode
# end wxGlade

############### Definitions ###############
from threading import Thread
from wx.lib.pubsub import Publisher
import time
import os
###########################################

############### Threads #######################

class WorkerThread(Thread):
    def __init__(self,notify_window):      
        Thread.__init__(self)
        self._notify_window = notify_window
        self._want_abort = 0
        self.start()

    def run(self):
        
        print("Read test_cnt.txt ")
        fread = file('test_cnt.txt','r')
        while True:           
            text = fread.readline()
            if len(text) == 0:
                break;
            print text
            cnt = int(text)
        fread.close()

        while True:
            time.sleep(1)
            if self._want_abort:
                wx.CallAfter(Publisher().sendMessage, "update", None)
                return
            wx.CallAfter(Publisher().sendMessage, "update", "Green")
            time.sleep(1)
            wx.CallAfter(Publisher().sendMessage, "update", "Red")
            time.sleep(1)
            fwrite = file('test_cnt.txt','w')
            cnt += 1                    
            fwrite.write("%s"%cnt)
            fwrite.close()
            time.sleep(1)
            wx.CallAfter(Publisher().sendMessage, "update", "cnt")
    
    def abort(self):
        self._want_abort = 1

################################################

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.window_1 = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D | wx.SP_BORDER)
        self.window_1_pane_1 = wx.Panel(self.window_1, wx.ID_ANY)
        self.start = wx.Button(self.window_1_pane_1, wx.ID_ANY, _("Start"))
        self.stop = wx.Button(self.window_1_pane_1, wx.ID_ANY, _("Stop"))
        self.window_1_pane_2 = wx.Panel(self.window_1, wx.ID_ANY)
        self.light = wx.Button(self.window_1_pane_2, wx.ID_ANY, _(""))
        self.status = wx.StaticText(self.window_1_pane_2, wx.ID_ANY, _("Status\n"))
        self.cnt = wx.TextCtrl(self.window_1_pane_2, wx.ID_ANY, "")

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

        ################# init function ##################
        rd_log = open(os.path.join('', 'test_cnt.txt'), 'r')
        self.cnt.SetValue(rd_log.read())
        rd_log.close()

        ################# function binding ###############
        self.Bind(wx.EVT_BUTTON,self.EVTSTART,self.start)
        self.Bind(wx.EVT_BUTTON,self.EVTSTOP,self.stop)
        Publisher().subscribe(self.updateDisplay, "update")
        self.worker = None
        ###################################################


        ################# function binding ###############
    def EVTSTART(self,event):
        print("Start testing")
        self.worker = WorkerThread(self)
        

    def EVTSTOP(self,event):
        print("End testing")
        self.worker.abort()

    def updateDisplay(self,event):
        if event.data is "Green":
            self.light.SetBackgroundColour(wx.Colour(0, 255, 127))
            self.status.SetLabel('Green')

        if event.data is "Red":
            self.light.SetBackgroundColour(wx.Colour(255, 0, 61))
            self.status.SetLabel('Red')

        if event.data is "cnt":
            rd_log = open(os.path.join('', 'test_cnt.txt'), 'r')
            self.cnt.SetValue(rd_log.read())
            rd_log.close()
        ###################################################

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle(_("Test"))
        self.start.SetMinSize((100, 80))
        self.stop.SetMinSize((100, 80))
        self.window_1_pane_1.SetMinSize((192, 257))
        self.light.SetMinSize((100, 40))
        self.window_1_pane_2.SetMinSize((188, 257))
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_2 = wx.BoxSizer(wx.VERTICAL)
        sizer_5 = wx.BoxSizer(wx.VERTICAL)
        sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_4 = wx.BoxSizer(wx.VERTICAL)
        sizer_4.Add(self.start, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 20)
        sizer_4.Add(self.stop, 0, wx.LEFT | wx.TOP, 20)
        sizer_3.Add(sizer_4, 1, wx.EXPAND, 0)
        self.window_1_pane_1.SetSizer(sizer_3)
        sizer_5.Add(self.light, 0, wx.LEFT | wx.TOP, 40)
        sizer_5.Add(self.status, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, 40)
        sizer_5.Add(self.cnt, 0, wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, 30)
        self.window_1_pane_2.SetSizer(sizer_5)
        self.window_1.SplitVertically(self.window_1_pane_1, self.window_1_pane_2)
        sizer_2.Add(self.window_1, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_2)
        sizer_2.Fit(self)
        self.Layout()
        # end wxGlade

# end of class MyFrame
if __name__ == "__main__":
    gettext.install("app") # replace with the appropriate catalog name

    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    Test = MyFrame(None, wx.ID_ANY, "")
    app.SetTopWindow(Test)
    Test.Show()
    app.MainLoop()



沒有留言:

張貼留言