1. 程式人生 > >wxPython學習]使用PubSub機制來更新檢視

wxPython學習]使用PubSub機制來更新檢視

最近忙於作一個生成測試用的XML報文的工具。在這個工具中我有幾個面板,它們是同時存在的。其中一個面板是用來管理資料字典的,它可以進行編輯,在其它的面板上有這些資料字典的一個顯示,不能進行編輯,它們反映的是同一個資料來源。同時,這個工具還可以根據交易報文匯入交易表中,同時會把每個交易欄位加入到資料字典中(如果這個欄位在資料字典中不存在的話),這樣,資料字典就還有可能發生變化。面板之間可以相互切換。那麼我面臨的一個問題就是如何處理當資料字典變化時,不同的視窗(或稱為檢視)進行更新。

下面給出兩個可能的情景。面板A用於資料字典的維護。面板B一方面可以顯示資料字典,同時還可以匯入交易資訊,從而也會修改資料字典。面板C也可以顯示資料字典。

情景一是當面板A維護了資料字典後,它要通知面板B和麵板C中的相應的UI元件執行重新整理過程。

情景二是當面板B匯入交易資訊後,修改了資料字典,它一方面要通知自身的顯示資料字典的元件,另外還要通知面板A和麵板C的相應的UI元件來更新。

以前我可以想到的方法就是:

1. 直接呼叫法

當面板A的資料字典發生變化,我主動呼叫面板B和麵板C的相應元件的重新整理處理方法。當面板B改動了資料字典後,由面板B來呼叫面板A和麵板C的相應元件的重新整理方法。但這種方法需要知道有哪些元件要重新整理,並且可以得到需要執行重新整理動作的物件方法。當一個應用UI元素很多,得到目標物件並不容易。而且程式碼看上去也很難看。

2. 通過傳送事件

可以考慮使用事件機制,利用GUI的事件迴圈,直接傳遞到指定視窗,這也已經與第一種差不多了。

正好我看到了在 wxPython 中談到了 pubsub 功能,可以非常好的解決我的問題。

3. 釋出/訂閱機制(pubsub)

pubsub 就是釋出和訂閱機制,可能大家有所耳聞。首先我有一個公共的釋出渠著,誰都可以通過它釋出某個主題的資訊。這樣我可以制定一些主題,用來描述某些狀態的變化。可以由產生變化的一方發起。然後有一些會對這些變化進行處理的訂閱者,它們可以收到自已感興趣的主題,當有資訊出現時,它們就可以進行各自的處理。這裡只是給大家講一下大概的原理。不同的環境實現起來差別挺大。比如說可以用在一個網路環境,而這裡我感興趣的是一個應用程式之內如何做到。

wxPython 提供了這種機制,原始碼在 wx.lib.pubsub.py 中,有使用說明,有完整的測試用例和程式碼。那麼它的思想其實說白了也還是直接的呼叫,但它封裝得很好,讓你感受不到。那麼簡單來講,它實現了以下東西:

主題釋出

通過呼叫 Publisher()可以得到一個Singleton的物件(只有一個例項的物件)。然後呼叫這個物件的SendMessage(topic, message=None)即可釋出主題。還可以順便帶一個訊息。可以沒有。

資訊訂閱

通過呼叫 Publisher() 得到釋出者物件,然後呼叫這個物件的subscribe(callable, topic)方法來訂閱某個主題。topic可以是分級結構,如果是這種情況,要使用tuple來存放主題,如('subject1', 'subject2')。一個訂閱者可以同時訂閱多個主題。

這樣,當某個事件發生了,由想要釋出這種變化的處理首先呼叫SendMessage()釋出相關的主題。然後訂閱者會收到這樣的事件被呼叫,從而進行相應的處理。

這樣的好處是釋出者不用關心哪些檢視要進行更新,它只是進行通知而已。而訂閱者也不用關心是誰釋出的,只要有呼叫就進行處理。

仔細看pubsub的實現,其實就是通過 Python 的特性,把pubsub模組匯入後,這個模組的資訊就會成為全域性資訊。通過Publisher()可以得到對應的唯一全域性物件。然後,每個訂閱處理會把一個可供呼叫的函式,類方法或可以呼叫的例項放到這個全域性物件中。訂閱者將根據主題被組織成樹狀結構。(而且結點好象是使用的弱引用,從而節省一些資源,沒太懂。)當執行SendMessage()時,會根據釋出的主題,在這個物件的主題樹中查詢,找到匹配的主題,由取出它的訂閱方法,然後一個個的執行。這麼看來,這種pubsub並不是一種非同步的呼叫,只是直接呼叫。只不過給你的感覺有些象。其實就是在SendMessage()中進行呼叫。因此,這種方法要求訂閱時要提供一個可供執行的物件方法、函式或可執行的物件。並且至少要有一個引數用來接收訊息。因此它對訂閱者函式的引數個數是有要求的。

為什麼上面說主題是樹呢?因為主題是可以分層次的,如:('subject',) ('subject', 'sport') 這定義了兩個主題。如果一個訂閱者兩個都訂閱了,那麼當傳送主題為('subject', 'sport')是,它會被呼叫兩次。因為('subject', 'sport')還匹配('subject',)。

下面給出一個最簡單的程式碼片:

import wx.lib.pubsub as pubsub  #匯入模組
... 略
class A:
    def __init__():
        publisher = pubsub.Publisher()
        publisher.subscribe(self.OnSubscribe, 'metadata_update')
    def OnSubscribe(self, message):
        print 'A message'
        ...

class B:
    def __init__():
        publisher = pubsub.Publisher()
        publisher.subscribe(self.OnSubscribe, 'metadata_update')
    def OnSubscribe(self, message):
        print 'B Message'
        ...

上面A和B分別定閱了同一個主題。

publisher = pubsub.Publisher()
publisher.sendMessage('metadata_update')

這樣就釋出了一個主題。這樣,A和B物件的方法都會被呼叫。如果有很多物件,會依次進行呼叫。但這些處理全是自動的,你感覺不到。

pubsub可以非常方便地簡單化程式之間的關係。

更詳細的內容建議還是看一看原始碼後的示例,有些比較複雜的呼叫。

如果你覺得不好,可以利用全域性物件自已實現一個這樣的pubsub功能。不過,最後提醒一點,還有別的pubsub處理方式,特別是在網路通訊中,完全可能是非同步,而不象這個根本就是直接呼叫(因為是在一個程式內實現的)。其實利用事件迴圈機制也可以實現,如釋出者維護訂閱者的控制代碼。當釋出主題時,找到所有的訂閱者控制代碼,然後每一個發一個訊息。只要想通了道理,實現方法其實是挺多的。只不過我介紹的這個相對簡單,容易實現。不過它不是 Python 自帶的模組,而是wxPython中提供的,要注意。有興趣可以改造下或寫個簡單的用在其它的專案中。