wxPython學習筆記(三)


要理解事件,我們需要知道哪些術語?

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

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

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

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

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

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

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

事件綁定器(event binder):一個封裝了特定窗口部件,特定事件類型和一個事件處理器wxPython對象。為了被調用,所有事件處理器必須用一個事件綁定器注冊。

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

什么是事件驅動編程?

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

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

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

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

4、當事件發生時,基於事件的系統試着確定相關代碼來處理該事件,如果有,相關代碼被執行。在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()方法,否則進一步的事件處理將被阻止

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

未學習

如何創建自己的事件?

創建自定義事件的步驟

1、定義一個新的事件類,它是wxPythonwx.PyEvent類的子類。如果你想這個事件被作為命令事件,你可以創建wx.PyCommandEvent的子類。像許多wxPython中的覆蓋一樣,一個類的py版本使得wxWidget系統明白用Python寫的覆蓋C++方法的方法。

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

3、添加能夠建造這個新事件實例的代碼,並且使用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 這個例子的事件處理器函數改變窗口的標題以顯示敲擊數。

總結

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

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

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

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

5、在wxPython中可以創建自定義事件,並作為定制(自定義)的窗口部件的行為的一部分。自定義的事件是類wx.PyEvent的子類,自定義的命令事件是類wx.PyCommandEvent的子類。為了創建一個自定義事件,新的類必須被定義,並且關於每個事件類型(這些事件類型被這個新類所管理)的綁定器必須被創建。最后,這個事件必須在系統的某處被生成,這通過經由ProcessEvent()方法傳遞一個新的實例給事件處理器系統來實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM