wxPython學習筆記(三)

要理解事件,咱們須要知道哪些術語?

事件(event):在你的應用程序期間發生的事情,它要求有一個響應。程序員

事件對象(event object):在wxPython中,它具體表明一個事件,其中包括了事件的數據等屬性。它是類wx.Event或其子類的實例,子類如wx.CommandEventwx.MouseEvent編程

事件類型(event type)wxPython分配給每一個事件對象的一個整數ID。事件類型給出了關於該事件自己更多的信息。例如,wx.MouseEvent的事件類型標識了該事件是一個鼠標單擊仍是一個鼠標移動。app

事件源(event source):任何wxPython對象都能產生事件。例如按鈕、菜單、列表框和任何別的窗口部件。框架

事件驅動(event-driven):一個程序結構,它的大部分時間花在等待或響應事件上。函數

事件隊列(event queue):已發生的但未處理的事件的一個列表。工具

事件處理器(event handler):響應事件時所調用的函數或方法。也稱做處理器函數或處理器方法。oop

事件綁定器(event binder):一個封裝了特定窗口部件,特定事件類型和一個事件處理器wxPython對象。爲了被調用,全部事件處理器必須用一個事件綁定器註冊。佈局

wx.EvtHandler:一個wxPython類,它容許它的實例在一個特定類型,一個事件源,和一個事件處理器之間建立綁定。注意,這個類與先前定義的事件處理函數或方法不是同一個東西。學習

什麼是事件驅動編程?

事件驅動程序結構的主要特色:spa

一、在初始化設置以後,程序的大部分時間花在了一個空閉的循環之中。進入這個循環就標誌着程序與用戶交互的部分的開始,退出這個循環就標誌結束。在wxPython中,這個循環的方法是:wx.App.MainLoop(),而且在你的腳本中顯式地被調用。當全部的頂級窗口關閉時,主循環退出。

二、程序包含了對應於發生在程序環境中的事情的事件。事件一般由用戶的行爲觸發,可是也能夠由系統的行爲或程序中其餘任意的代碼。在wxPython中,全部的事件都是類wx.Event或其子類的一個實例。每一個事件都有一個事件類型屬性,它使得不一樣的事件可以被辨別。例如,鼠標釋放和鼠示按下事件都被認爲是同一個類的實例,但有不一樣的事件類型。

三、做爲這個空閉的循環部分,程序按期檢查是否有任何請求響應事情發生。有兩種機制使得事件驅動系統能夠獲得有關事件的通知。最常被wxPython使用的方法是,把事件傳送到一箇中心隊列,由該隊列觸發相應事件的處理。另外一種方法是使用輪詢的方法,全部可能引起事件的事件主被主過程按期查詢並詢問是否有沒有處理的事件。

四、當事件發生時,基於事件的系統試着肯定相關代碼來處理該事件,若是有,相關代碼被執行。在wxPython中,原系統事件被轉換爲wx.Event實例,而後使用wx.EvtHandler.ProcessEvent()方法將事件分派給適當的處理器代碼。下圖呈現了這個過程:

事件機制的組成部分是事件綁定器對象和事件處理器。事件綁定器是一個預約義的wxPython對象。每一個事件都有各自的事件綁定器。事件處理器是一個函數或方法,它要求一個wxPython事件實例做爲參數。當用戶觸發了適當的事件時,一個事件處理器被調用。

編寫事件處理器

在你的wxPython代碼中,事件和事件處理器是基於相關的窗口部件的。例如,一個按鈕被單擊被分派給一個基於該按鈕的專用的事件處理器。爲了要把一個來自特定窗口部件的事件綁定到一個特定的處理器方法,你要使用一個綁定器對象來管理這個鏈接。例如:

self.Bind(wx.EVT_BUTTON, self.OnClick, aButton)

上例使用了預約義的事件綁定器對象wx.EVT_BUTTON來將aButton對象上的按鈕單擊事件與方法self.OnClick相關聯起來。這個Bind()方法wx.EvtHandler的一個方法,wx.EvtHandler是全部可顯示對象的父類。所以上例代碼行能夠被放置在任何顯示類。

設計事件驅動程序

對於事件驅動程序的設計,因爲沒有假設事件什麼時候發生,因此程序員將大量的控制交給了用戶。你的wxPython程序中的大多數代碼經過用戶或系統的行爲被直接或間接地執行。例如在用戶選擇了一個菜單項、或按下一個工具欄按鈕、或按下了特定的按鍵組合後,你的程序中有關保存工做的代碼被執行了。

另外一方面,事件驅動體系一般是分散性的。響應一個窗口部件事件的代碼一般不是定義在該部件的定義中的。例如,響應一個按鈕單擊事件的代碼沒必要是該按鈕定義的一部分,而能夠存在在該按鈕所附的框架中或其它地方。

事件觸發

wx.CloseEvent:當一個框架關閉時觸發。這個事件的類型分爲一個一般的框架關閉和一個系統關閉事件。 wx.CommandEvent:與窗口部件的簡單的各類交互都將觸發這個事件,如按鈕單擊、菜單項選擇、單選按鈕選擇。這些交互有它各自的事件類型。許多更復雜的窗口部件,如列表等則定義wx.CommandEvent的子類。事件處理系統對待命令事件與其它事件不一樣。 wx.KeyEvent:按鍵事件。這個事件的類型分按下按鍵、釋放按鍵、整個按鍵動做。 wx.MouseEvent:鼠標事件。這個事件的類型分鼠標移動和鼠標敲擊。對於哪一個鼠標按鈕被敲擊和是單擊仍是雙擊都有各自的事件類型。 wx.PaintEvent:當窗口的內容須要被重畫時觸發。wx.SizeEvent:當窗口的大小或其佈局改變時觸發。 wx.TimerEvent:能夠由類wx.Timer類建立,它是按期的事件。

如何將事件綁定處處理器?

事件綁定器被用於將一個wxPython窗口部件與一個事件對象和一個處理器函數鏈接起來。這個鏈接使得wxPython系統可以經過執行處理器函數中的代碼來響應相應窗口部件上的事件。 

使用wx.EvtHandler的方法工做

常用的wx.EvtHandler的方法是Bind(),它建立事件綁定。該方法的用法以下:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

Bind()函數將一個事件和一個對象與一個事件處理器函數關聯起來。參數event是必選的,參數handler也是必選的,它是一個可調用的Python對象,一般是一個被綁定的方法或函數。參數handler能夠是None,這種狀況下,事件沒有關聯的處理器。參數source是產生該事件的源窗口部件,這個參數在觸發事件的窗口部件與用做事件處理器的窗口部件不相同時使用。一般狀況下這個參數使用默認值None,這是由於你通常使用一個定製的wx.Frame類做爲處理器,而且綁定來自於包含在該框架內的窗口部件的事件。父窗口的__init__是一個用於聲明事件綁定的方便的位置。可是若是父窗口包含了多個按鈕敲擊事件源(好比OK按鈕和Cancel按鈕),那麼就要指定source參數以便wxPython區分它們(?)。下面是該方法的一個例子:

self.Bind(wx.EVT_BUTTON, self.OnClick, button)

演示了使用參數source和不使用參數source的方法:

def __init__(self, parent, id): 
    wx.Frame.__init__(self, parent, id, 'Frame With Button',
            size=(300, 100)) 
    panel = wx.Panel(self, -1)                              
    button = wx.Button(panel, -1, "Close", pos=(130, 15),   
            size=(40, 40)) 
    self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) #1 綁定框架關閉事件   
    self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) #2 綁定按鈕事件  

    def OnCloseMe(self, event): 
        self.Close(True) 
    def OnCloseWindow(self, event):
        self.Destroy() 

說明:

#1 這行綁定框架關閉事件到self.OnCloseWindow方法。因爲這個事件經過該框架觸發且用於幀,因此不須要傳遞一個source參數。

#2 這行未來自按鈕對象的按鈕敲擊事件綁定到self.OnCloseMe方法。這樣作是爲了讓wxPython可以區分在這個框架中該按鈕和其它按鈕所產生的事件。

Bind()方法中的參數idid2使用ID號指定了事件的源。通常狀況下這不必,由於事件源的ID號能夠從參數source中提取。可是某些時候直接使用ID是合理的。例如,若是你在使用一個對話框的ID號,這比使用窗口部件更容易。若是你同時使用了參數idid2,你就可以以窗口部件的ID號形式將這兩個ID號之間範圍的窗口部件綁定到事件。這僅適用於窗口部件的ID號是連續的。

wxPython是如何處理事件的?

代碼以下:

import wx 

class MouseEventFrame(wx.Frame): 
    
    def __init__(self, parent, id): 
        wx.Frame.__init__(self, parent, id, 'Frame With Button',
                size=(300, 100)) 
        self.panel = wx.Panel(self)                             
        self.button = wx.Button(self.panel,
            label="Not Over", pos=(100, 15)) 
        self.Bind(wx.EVT_BUTTON, self.OnButtonClick,   
            self.button)    #1 綁定按鈕事件                 
        self.button.Bind(wx.EVT_ENTER_WINDOW,   
            self.OnEnterWindow)     #2 綁定鼠標位於其上事件          
        self.button.Bind(wx.EVT_LEAVE_WINDOW, 
            self.OnLeaveWindow)     #3 綁定鼠標離開事件
 
    def OnButtonClick(self, event): 
        self.panel.SetBackgroundColour('Green') 
        self.panel.Refresh() 
        
    def OnEnterWindow(self, event): 
        self.button.SetLabel("Over Me!") 
        event.Skip() 
        
    def OnLeaveWindow(self, event): 
        self.button.SetLabel("Not Over") 
        event.Skip() 

if __name__ == '__main__': 
    app = wx.App() 
    frame = MouseEventFrame(parent=None, id=-1)
    frame.Show() 
    app.MainLoop()

 

說明

MouseEventFrame包含了一個位於中間的按鈕。在其上敲擊鼠標將致使框架的背景色改變爲綠色。#1綁定了鼠標敲擊事件。當鼠標指針位於這個按鈕上時,按鈕上的標籤將改變,這用#2綁定。當鼠標離開這個按鈕時,標籤變回原樣,這用#3綁定。

經過觀察上面的鼠標事件例子,咱們引出了在wxPython中的事件處理的一些問題。#1中,按鈕事件由附着在框架上的按鈕觸發,那麼wxPython怎麼知道在框架對象中查找綁定而不是在按鈕對象上呢?在#2和#3中,鼠標的進入和離開事件被綁定到了按鈕,爲何這兩個事件不能被綁到框架上呢。這些問題將經過檢查wxPython用來決定如何響應事件的過程來獲得回答。

Skip():在wxPython中,若是一個動做會觸發多個事件,那麼應該使用Skip方法來保證每一個都被處理到。其實爲了保證不遺漏,在每一個事件處理的方法中都調用Skip方法應該是一種良好的習慣。

理解事件處理過程

第一步,建立事件

第二步,肯定事件對象是否被容許處理事件

第三步 定位綁定器對象

第四步 決定是否繼續處理

第五步 決定是否展開 

使用Skip()方法

事件的第一個處理器函數被發現並執行完後,該事件處理將終止,除非在處理器返回以前調用了該事件的Skip()方法。調用Skip()方法容許另外被綁定的處理器被搜索,在某些狀況下,你想繼續處理事件,以便原窗口部件的默認行爲和你定製的處理能被執行。

# 同時響應鼠標按下和按鈕敲擊
import wx

class DoubleEventFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Frame With Button',
                          size=(300, 100))
        self.panel = wx.Panel(self, -1)
        self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15))
        self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
                  self.button)  # 1 綁定按鈕敲擊事件
        self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)  # 2 綁定鼠標左鍵按下事件

    def OnButtonClick(self, event):
        self.panel.SetBackgroundColour('Green')
        self.panel.Refresh()

    def OnMouseDown(self, event):
        self.button.SetLabel("Again!")
        event.Skip()  # 3 確保繼續處理


if __name__ == '__main__':
    app = wx.App()
    frame = DoubleEventFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop() 

#1 這行綁定按鈕敲擊事件OnButtonClick()處理器,這個處理器改變框架的背景色。

#2 這行綁定鼠標左鍵按下事件到OnMouseDown()處理器,這個處理器改變按鈕的標籤文本。因爲鼠標左鍵按下事件不是命令事件,因此它必須被綁定到按鈕(self.button.Bind)而非框架(self.Bind)。

分析:鼠標按下的時候沒有變色,文本框內容改變,說明調用了OnMouseDown方法;鼠標按下再釋放的時候,變色,說明調用了OnButtonClick方法。若是在OnMouseDown方法中沒有Skip(),則事件會被終止,即不會變色。

當用戶在按鈕上敲擊鼠標時,經過直接與底層操做系統交互,鼠標左鍵按下事件首先被產生。一般狀況下,鼠標左鍵按下事件改變按鈕的狀態,隨着鼠標左鍵的釋放,產生了wx.EVT_BUTTON敲擊事件。因爲行#3的Skip()語句,DoubleEventFrame維持處理。沒有Skip()語句,事件處理規則發如今#2建立的綁定,而在按鈕能產生wx.EVT_BUTTON事件以前中止。因爲Skip()的調用,事件處理照常繼續,而且按鈕敲擊被建立。

記住,當綁定低級事件時如鼠標按下或釋放,wxPython指望捕獲這些低級事件以便生成進一步的事件,爲了進一步的事件處理,你必須調用Skip()方法,不然進一步的事件處理將被阻止

在應用程序對象中還包含哪些其它的屬性?

未學習

如何建立本身的事件?

建立自定義事件的步驟

一、定義一個新的事件類,它是wxPythonwx.PyEvent類的子類。若是你想這個事件被做爲命令事件,你能夠建立wx.PyCommandEvent的子類。像許多wxPython中的覆蓋同樣,一個類的py版本使得wxWidget系統明白用Python寫的覆蓋C++方法的方法。

二、建立一個事件類型和一個綁定器對象去綁定該事件到特定的對象。

三、添加可以建造這個新事件實例的代碼,而且使用ProcessEvent()方法將這個實例引入事件處理系統。一旦該事件被建立,你就能夠像使用其它的wxPython事件同樣建立綁定和處理器方法。

import wx


class TwoButtonEvent(wx.PyCommandEvent):  # 1 定義事件
    def __init__(self, evtType, id):
        wx.PyCommandEvent.__init__(self, evtType, id)
        self.clickCount = 0

    def GetClickCount(self):
        return self.clickCount

    def SetClickCount(self, count):
        self.clickCount = count


myEVT_TWO_BUTTON = wx.NewEventType()  # 2 建立一個事件類型
EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1)  # 3 建立一個綁定器對象


class TwoButtonPanel(wx.Panel):
    def __init__(self, parent, id=-1, leftText="Left",
                 rightText="Right"):
        wx.Panel.__init__(self, parent, id)
        self.leftButton = wx.Button(self, label=leftText)
        self.rightButton = wx.Button(self, label=rightText,
                                     pos=(100, 0))
        self.leftClick = False
        self.rightClick = False
        self.clickCount = 0
        # 4 下面兩行綁定更低級的事件
        self.leftButton.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
        self.rightButton.Bind(wx.EVT_LEFT_DOWN, self.OnRightClick)

    def OnLeftClick(self, event):
        self.leftClick = True
        self.OnClick()
        event.Skip()  # 5 繼續處理

    def OnRightClick(self, event):
        self.rightClick = True
        self.OnClick()
        event.Skip()  # 6 繼續處理

    def OnClick(self):
        self.clickCount += 1
        if self.leftClick and self.rightClick:
            self.leftClick = False
            self.rightClick = False
            evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId())  # 7 建立自定義事件
            evt.SetClickCount(self.clickCount)  # 添加數據到事件
            self.GetEventHandler().ProcessEvent(evt)  # 8 處理事件


class CustomEventFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Click Count: 0',
                          size=(300, 100))
        panel = TwoButtonPanel(self)
        self.Bind(EVT_TWO_BUTTON, self.OnTwoClick, panel)  # 9 綁定自定義事件

    def OnTwoClick(self, event):  # 10 定義一個事件處理器函數
        self.SetTitle("Click Count: %s" % event.GetClickCount())


if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = CustomEventFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop() 

說明

#1 這個關於事件類的構造器聲明爲wx.PyCommandEvent的一個子類。 wx.PyEventwx.PyCommandEventwxPython特定的結構,你能夠用來建立新的事件類而且能夠把C++類和你的Python代碼鏈接起來。若是你試圖直接使用wx.Event,那麼在事件處理期間wxPython不能明白你的子類的新方法,由於C++事件處理不瞭解該Python子類。若是你wx.PyEvent,一個對該Python實例的引用被保存,而且之後被直接傳遞給事件處理器,使得該Python代碼能被使用。

#2 全局函數wx.NewEventType()的做用相似於wx.NewId();它返回一個惟一的事件類型ID。這個惟一的值標識了一個應用於事件處理系統的事件類型。

#3 這個綁定器對象的建立使用了這個新事件類型做爲一個參數。這第二個參數的取值位於[0,2]之間,它表明wxId標識號,該標識號用於wx.EvtHandler.Bind()方法去肯定哪一個對象是事件的源。

#4 爲了建立這個新的更高級的命令事件,程序必需響應特定的用戶事件,例如,在每一個按鈕對象上的鼠標左鍵按下。依據哪一個按鈕被敲擊,該事件被綁定到OnLeftClick()OnRightClick()方法。處理器設置了布爾值,以代表按鍵是否被敲擊。

#5 #6 Skip()的調用容許在該事件處理完成後的進一步處理。在這裏,這個新的事件不須要skip調用;它在事件處理器完成以前被分派了(self.OnClick())。可是全部的鼠標左鍵按下事件須要調用Skip(),以便處理器不把最後的按鈕敲擊掛起。這個程序沒有處理按鈕敲擊事件,可是因爲使用了Skip()wxPython在敲擊期間使用按鈕敲擊事件來正確地繪製按鈕。若是被掛起了,用戶將不會獲得來自按鈕按下的反饋。

#7 若是兩個按鈕都被敲擊了,該代碼建立這個新事件的一個實例。事件類型和兩個按鈕的ID做爲構造器的參數。一般,一個事件類能夠有多個事件類型,儘管本例中不是這樣。

#8 ProcessEvent()的調用將這個新事件引入到事件處理系統中。GetEventHandler()調用返回wx.EvtHandler的一個實例。大多數狀況下,返回的實例是窗口部件對象自己,可是若是其它的wx.EvtHandler()方法已經被壓入了事件處理器堆棧,那麼返回的將是堆棧項的項目。

#9 該自定義的事件的綁定如同其它事件同樣,在這裏使用#3所建立的綁定器。

#10 這個例子的事件處理器函數改變窗口的標題以顯示敲擊數。

總結

一、wxPython應用程序使用基於事件的控制流。應用程序的大部分時間花費在一個主循環中,等待事件並分派它們到適當的處理器函數。

二、全部的wxPython事件是wx.Event類的子類。低級的事件,如鼠標敲擊,被用來創建高級的事件,如按鈕敲擊或菜單項選擇。這些由wxPython窗口部件引發的高級事件是類wx.CommandEvent的子類。大多的事件類經過一個事件類型字段被進一步分類,事件類型字段區分事件

三、爲了捕獲事件和函數之間的關聯,wxPython使用類wx.PyEventBinder的實例。類wx.PyEventBinder有許多預約義的實例,每一個都對應於一個特定的事件類型。每一個wxPython窗口部件都是類wx.EvtHandler的子類。類wx.EvtHandler有一個方法Bind(),它一般在初始化時被調用,所帶參數是一個事件綁定器實例和一個處理器函數。根據事件的類型,別的wxPython對象的ID可能也須要被傳遞給Bind()調用。

四、事件一般被髮送給產生它們的對象,以搜索一個綁定對象,這個綁定對象綁定事件到一個處理器函數。若是事件是命令事件,這個事件沿容器級向上傳遞直到一個窗口部件被發現有一個針對該事件類型的處理器。一旦一個事件處理器被發現,對於該事件的處理就中止,除非這個處理器調用了該事件的Skip()方法。你能夠容許多個處理器去響應一個事件,或去核查該事件的全部默認行爲。主循環的某些方面可使用wx.App的方法來控制。

五、在wxPython中能夠建立自定義事件,並做爲定製(自定義)的窗口部件的行爲的一部分。自定義的事件是類wx.PyEvent的子類,自定義的命令事件是類wx.PyCommandEvent的子類。爲了建立一個自定義事件,新的類必須被定義,而且關於每一個事件類型(這些事件類型被這個新類所管理)的綁定器必須被建立。最後,這個事件必須在系統的某處被生成,這經過經由ProcessEvent()方法傳遞一個新的實例給事件處理器系統來實現。

相關文章
相關標籤/搜索