May 21, 2008

透過 Python 體驗 QtWebKit 快速開發

稍早寫過一篇文章 [QtWebKit: 將 Web 2.0 技術帶入行動通訊的系統設計],談及 Trolltech 對於 Qt Framework 與 WebKit 的整合,提供獨到的設計,不僅可很容易在應用程式中嵌入 WebKit 所帶來的 Web 2.0 網路服務外,還可作無接縫 (seamless) 的整合。不過前文並未解說整合細節,這裡就帶出具體而微的範例,體驗 QtWebKit 的技術突破與先進的特徵,恰好下個月要出席 [PycTW2008],那麼程式語言選用 Python 作為練習。

日前 Trolltech 正式釋出 Qt 4.4,業已整合 QtWebKit,與 Qt framework 銜接的 [PyQt] 日前也推出 v4.4.2,即可透過 Python 來釋放 QtWebKit 的威力。Qt framework 一向最為人知的賣點就是 "signals-slots" 機制,自然在 PyQt 也少不了,而且還透過 [SIP] Python 模組,免去了許多語言層面的繁文縟節。首先,要存取 PyQt 的模組很容易,只要如此宣告:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
在這個範例,筆者設想的操作情境為,展示如何透過 WebKit 顯示 HTML element 與 JavaScript,以及透過 iframe tag 嵌入 Google 首頁,在使用者互動的部份,透過 Javascript 建立對話框,並試著與 Python 的程式通訊。於是,我們建立一個 class 來專門描繪網頁,程式碼如下:
class BrowserScreen(QWebView):
    def __init__(self):
        QWebView.__init__(self)

        self.resize(800, 600)
        self.show()
        self.setHtml("""
           <script>function message() { return "Clicked!"; }</script>
           <h1>QtWebKit + Python sample program</h1>
           <input type="button" value="Click JavaScript!" 
                  onClick="alert('[javascript] ' + message())"/>
           <input type="button" value="Click Python!" 
                  onClick="python.alert('[python] ' +
                                        python.message())"/>
           <br />
           <iframe src="http://www.google.com/"
                   width="750" height="500"
                   scrolling="no"
                   frameborder="0"
                   align="center"></iframe>
        """)
我們定義的 class 繼承自 QtWebKit 中的 class QWebView,在初始化時,即呼叫 resize(), show(), setHTML() 等 method。此外,與其說上述程式列表為 Python 程式語言,不如說就是 HTML 網頁原始碼。熟悉網頁程式設計者,一眼就可發現我們在兩個 input button 上建立 Javascript 事件關聯,其中一個呼叫 alert() method 來顯示對話框,而另一個則比較特別:
onClick="python.alert('[python] ' +
         python.message())
這邊的 "python.alert" 與 "python.message" 就使用了 PyQt + QtWebKit 的專有功能,意思是按下 button 時,會呼叫 python 物件的 message method,而這個 "python" 物件可動態在欲嵌入 WebKit 的 Python 程式中傳入物件,當然,可有頗多變化。筆者這裡僅作字串回傳顯示的動作,不過即使如此,還是有兩項技術細節要思考:
  • QtWebKit 的 class QWebView,其最主要的目標是走訪 HTML 個別 element 並描繪網頁,也就是內部維護著 DOM (Document Object Model),包含我們剛剛看見的那兩個 input button 也在其中
  • Javascript (或 ECMAscript) 內部有自己的字串與物件表示,Python 也有字串,而 Qt framework 更有 class QString,那麼,該如何建立起彼此的關聯呢?至少,在筆者設計的情境中,就得考慮字串與物件遊走於這三方所需面對的議題
實際上,得面對的問題不只如此,不過 QtWebKit + PyQt 都幫我們處理掉,所以,筆者只要另行提供 Python 物件並交予 QtWebKit 即可。以下是傳入到 WebKit 的物件相關的 Python 程式碼:
class PythonJS(QObject):
    __pyqtSignals__ = ("contentChanged(const QString &)")
    @pyqtSignature("QString")
    def alert(self, msg):
        self.emit(SIGNAL('contentChanged(const QString &)'), msg)

    @pyqtSignature("", result="QString")
    def message(self):
        return "Click!"
這個名為 PythonJS 的 class,繼承自 class QObject。透過 PyQt,我們宣告一個自訂的 signal: "contentChanged(const QString &)",這不需要額外的 moc compiler 即可有對應的 metadata 關聯。剛剛在 class BrowserScreen 的 HTML 程式列表中,所提及的 "python.alert" method 就定義於此,筆者依據 Qt 的 Signals-Slot 機制,去 emit 出 "contentChanged(const QString &)" 這個 signal,並將 alert() method 後方的字串 (const QString & 型態) 一併傳出,也可見到 PyQt 中宣告 msg 參數型態為 "QString"。同樣,PythonJS::message method 也是如此,依據上方的執行順序來看,會先呼叫 PythonJS::message() 在將傳回的 QString 字串透過 QtWebKit 內部的轉換,變成 JavaScript 的字串並作物件的合成動作 (即 "'[python] ' + python.message()" 陳述),並將得到的 JavaScript 字串傳遞給 QtWebKit 外部的 Python 物件,呼叫 PythonJS::alert() method,當然,這時候要將 JavaScript 字串轉變成 Pthon 可處理的 QString 字串。

撰寫了以上兩個 class,程式幾乎完成了,只要將兩者整合起來即可,為了增加視覺上的比較效果,筆者透過 Qt 4.4 提供的 System Tray (在 X11/FreeDesktop 的術語為 "Notification Area") 來作訊息顯示的動作。所以修改了 class BrowserScreen,追加兩個 method,程式碼如下:
class BrowserScreen(QWebView):
    def __init__(self):
        QWebView.__init__(self)

        self.createTrayIcon()
        ...
        self.trayIcon.show()

    def createTrayIcon(self):
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon("images/trash.svg"))

    def showMessage(self, msg):
        self.trayIcon.showMessage("This is Python", msg,
            QSystemTrayIcon.MessageIcon(0), 15 * 1000)
BrowserScreen::createTrayIcon() method 透過 class QSystemTrayIcon 要求系統配置 system tray,筆者設定了 SVG 圖檔 (向量繪圖,所以不需要考慮顯示端的空間尺寸),而 BrowserScreen::showMessage() method 看似不相關,僅是顯示訊息的動作,稍後,我們可透過 Qt 的 Signals-Slots,將 QtWebKit 中的 DOM/JavaScript 事件與此 method 給予 "connect" 起來。

最後一個部份,就是畫龍點睛了,以下是 main 程式列表:
if __name__=='__main__':
    import sys

    app = QApplication(sys.argv)

    browser = BrowserScreen()
    pjs = PythonJS()
    browser.page().mainFrame().addToJavaScriptWindowObject("python", pjs)

    QObject.connect(pjs, SIGNAL("contentChanged(const QString &)"),
                    browser.showMessage)

    sys.exit(app.exec_())
重點當然是從前面兩個 class 建立 Python 物件,也就是 "browser" 與 "pjs",整個程式最巧妙之處,就在於以下這行:
    browser.page().mainFrame().addToJavaScriptWindowObject(
        "python", pjs)
這也是 QtWebKit 技術的「火力展示」,原來前面的 HTML 列表的 JavaScript 程式的 "python" 物件,就是甫建立的 "pjs" 物件,而下一行也充滿著驚喜:
    QObject.connect(pjs, SIGNAL("contentChanged(const QString &)"),
                    browser.showMessage)
class PythonJS 所生的物件 "pjs" 裡頭的 signal "contentChanged" 被連結 (connect) 到 class BrowserScreen 所生的物件 "browser" 裡頭的 slot "showMessage",原本只是平淡無奇的 PyQt 敘述,但因為 "pjs" 物件被傳入 QtWebKit,WebKit 所描繪的網頁 (範例即 Google 首頁) 有完整的 DOM/JavaScript,依據之前的 HTML 程式列表,已建立這兩者的關聯,如今,再將 PyQt 的事件一舉打通。是此,Python/PyQt - QtWebKit - DOM/JavaScript 的關聯就建立了,咱們來體驗看看,以下是操作時的圖例:

在 system tray 多了一個綠色、類似資源回收桶的圖樣,而主畫面就如預期,就是 QtWebKit。是的,寫 UI 就是這麼簡單,只要 HTML tag 加上 iframe,頓時有聲有色,為了要證明這不是紙老虎,咱們按一下左方的 "Click Javascript!" input button,會得到下方螢幕輸出:

這就是 Javascript 裡頭呼叫 alert() method,展示了 QtWebKit 的基本能力,最後我們看看剛剛張羅許久的展示,當按下 "Click Python!" input button 後...

注意到 system tray 下方彈跳出黃色對話訊息,別小看這個,這可是歷經 Python/PyQt - QtWebKit - DOM/JavaScript 等部份,顯示於我們眼前的。我們也可發現,QtWebKit 要與桌面整合並產生有效的互動,只需要上述這一些程式碼即可,透過 Python 體驗 QtWebKit 快速開發,看來很棒呢。

取得上述程式碼加上 SVG 圖檔: [pyqtwebkit-sample.tar.bz2],因為 Qt framework 與 PyQt 都以 GNU GPL 授權發行,本範例也是如此。
由 jserv 發表於 May 21, 2008 10:01 PM
迴響
發表迴響









記住我的資訊?