2015-5-2 藍(lán)藍(lán)設(shè)計(jì)的小編
一:源起
1.何為CB/S的應(yīng)用程序
C/S結(jié)構(gòu)的應(yīng)用程序,是客戶端/服務(wù)端形式的應(yīng)用程序,這種應(yīng)用程序要在客戶電腦上安裝一個(gè)程序,客戶使用這個(gè)程序與服務(wù)端通信,完成一定的操作。
B/S結(jié)構(gòu)的應(yīng)用程序,是瀏覽器/服務(wù)端形式的應(yīng)用程序,這種應(yīng)用程序不用在客戶端部署任何東西,客戶只需要通過(guò)瀏覽器與服務(wù)端通信,來(lái)完成一定的操作。
兩種類型的程序優(yōu)缺點(diǎn)對(duì)比:
由上可知,兩種形式的應(yīng)用程序各有利弊。架構(gòu)師在做技術(shù)選型的時(shí)候,往往會(huì)根據(jù)項(xiàng)目需要,對(duì)比這兩種技術(shù)形式的優(yōu)缺點(diǎn),做出正確的選擇。
然而,國(guó)內(nèi)大多數(shù)企業(yè)應(yīng)用程序,需要頻繁、及時(shí)的更新升級(jí)、需要更高的客戶端控制權(quán)限、需要更高的數(shù)據(jù)實(shí)時(shí)性和更高的通信效率,但卻不在意部署上的問(wèn)題。
這時(shí),架構(gòu)師就考慮把C/S結(jié)構(gòu)的應(yīng)用程序和B/S結(jié)構(gòu)的應(yīng)用程序結(jié)合起來(lái),讓客戶端嵌套一個(gè)瀏覽器以與服務(wù)器通信,完成一定的操作。這樣的程序就是CB/S結(jié)構(gòu)的應(yīng)用程序。
這樣做的好處是一般的業(yè)務(wù)邏輯只要在服務(wù)端更新升級(jí),即可體現(xiàn)在客戶端。對(duì)于客戶端系統(tǒng)權(quán)限、基于Socket的通信等瀏覽器核心無(wú)法完成的操作,可以由客戶端來(lái)完成??蛻舳丝梢灾苯优c服務(wù)端通信,也可以通過(guò)瀏覽器核心與服務(wù)端通信。
下圖為CB/S結(jié)構(gòu)應(yīng)用程序的基本示意圖:
目前還有一種介于C/S和B/S結(jié)構(gòu)的應(yīng)用程序之間的應(yīng)用程序:RIA富互聯(lián)網(wǎng)應(yīng)用程序,這種結(jié)構(gòu)的應(yīng)用程序一般都是基于瀏覽器插件來(lái)運(yùn)行的,它有較高的客戶端控制權(quán)限(比B/S程序高,但比C/S程序低),通信方式也有較多的選擇(不只是基于HTTP協(xié)議),目前較常見(jiàn)的RIA技術(shù)有:Adobe的flex技術(shù)、微軟的Silverlight技術(shù)、Oracle的WebStart技術(shù)。架構(gòu)師在做技術(shù)選型的時(shí)候,也可以綜合權(quán)衡采用這些技術(shù)。
2.為何選擇QT的WebKit與Extjs開發(fā)企業(yè)應(yīng)用
ExtJs是一個(gè)用于創(chuàng)建Web用戶界面的JS框架,提供了豐富的界面部件及布局方式,對(duì)于web開發(fā)者來(lái)說(shuō),實(shí)現(xiàn)企業(yè)應(yīng)用所需的各種畫面只要掌握J(rèn)S語(yǔ)言即可。不必再引入flash或silverlight技術(shù),而且能很容易的創(chuàng)建風(fēng)格統(tǒng)一的企業(yè)應(yīng)用程序。
雖然ExtJs支持各種流行的瀏覽器,甚至包括IE6,但是它在IE系瀏覽器下運(yùn)行、渲染的效率不高。在谷歌瀏覽器下表現(xiàn)最好,F(xiàn)ireFox瀏覽器次之(這得益于谷歌瀏覽器的JS腳本引擎)。
然而谷歌瀏覽器和FireFox瀏覽器的核心都是WebKit(蘋果公司開源的瀏覽器核心,負(fù)責(zé)解析HTML文本,并呈現(xiàn)到界面上),所以,要想讓我們的CB/S+ExtJs結(jié)構(gòu)的應(yīng)用程序能有更好的表現(xiàn),我們必須采用WebKit核心的瀏覽器。
雖然我們能很方便的獲得WebKit的源碼,然而編譯它卻十分耗時(shí)費(fèi)力,不但要選對(duì)編譯工具,還要安裝一系列的SDK,編譯時(shí)間更是長(zhǎng)的驚人(這幾乎是大型C++項(xiàng)目的通病)。編譯出來(lái)的DLL使用起來(lái)也不是很方便(要翻閱大量的WebKit的API)。
幸運(yùn)的是QT界面庫(kù)為我們做了這些工作,QT庫(kù)中包含webkit的瀏覽器控件,并且這個(gè)C++庫(kù)是跨平臺(tái)的,也就是說(shuō)基于這幾項(xiàng)技術(shù)開發(fā)的CB/S企業(yè)應(yīng)用可以部署在Linux系統(tǒng)內(nèi)。
除了使用QT界面庫(kù),還可以選擇gtk+和wxWidgets兩個(gè)界面庫(kù),而且這兩個(gè)界面庫(kù)都對(duì)WebKit做過(guò)包裝,但是從開發(fā)方式,生產(chǎn)效率,運(yùn)行速度等多方面考慮,還是QT最為合適。
QT界面庫(kù)也分為兩個(gè)版本,一個(gè)是收費(fèi)的digia提供的QT,另一個(gè)是免費(fèi)的qt-project提供的QT(GPL V3 LGPL V2),這里我們選擇免費(fèi)版的QT,本文第三節(jié)會(huì)介紹如何搭建開發(fā)環(huán)境。
架構(gòu)師除了選擇QT的WebKit做瀏覽器核心之外,還可以選擇CEF(Chromium Embedded Framework,項(xiàng)目地址:https://code.google.com/p/chromiumembedded/)這個(gè)項(xiàng)目是對(duì)谷歌瀏覽器的重新編譯、封裝,分為兩個(gè)版本線,CEF1和CEF3,我曾對(duì)此項(xiàng)目做過(guò)一些研究,研究的相關(guān)資料參見(jiàn):http://www.cnblogs.com/liulun/archive/2013/03/18/2874276.html;另外,還有一個(gè)node webkit的項(xiàng)目(地址:https://github.com/rogerwang/node-webkit)也是對(duì)谷歌瀏覽器的重新編譯和封裝,但它引入了NodeJs,使用簡(jiǎn)單的HTML JS CSS就可以編寫出絢麗的客戶端界面。node webkit目前處于V0.7.X版本。
二:思路
1.目標(biāo)
搭建一個(gè)CB/S結(jié)構(gòu)的企業(yè)應(yīng)用程序
盡量保證系統(tǒng)的執(zhí)行效率
盡量保證系統(tǒng)升級(jí)更新的便利性
盡量保證系統(tǒng)的可擴(kuò)展性
2.方案
ExtJs框架是一個(gè)比較龐大的框架,一般B/S結(jié)構(gòu)的程序使用ExtJS框架,都是把ExtJs的框架放在服務(wù)端,這樣用戶每次請(qǐng)求頁(yè)面的時(shí)候,都會(huì)去訪問(wèn)ExtJS框架的JS文件,從而產(chǎn)生大量的磁盤IO和網(wǎng)絡(luò)消耗,這也是ExtJS框架看起來(lái)渲染很慢的一個(gè)因素。B/S結(jié)構(gòu)的應(yīng)用程序無(wú)法解決這個(gè)問(wèn)題,主要是因?yàn)闊o(wú)法控制客戶端的瀏覽器,CB/S結(jié)構(gòu)的程序就能輕松解決這個(gè)問(wèn)題。可以把ExtJs框架打包進(jìn)客戶端程序中,隨客戶端程序分發(fā)給使用者,使用者請(qǐng)求頁(yè)面時(shí),使用的是本地的ExtJS框架的JS文件,業(yè)務(wù)邏輯程序則仍舊使用服務(wù)端的。這樣做減少了磁盤IO和網(wǎng)絡(luò)消耗,保證了系統(tǒng)的執(zhí)行效率;服務(wù)端對(duì)業(yè)務(wù)邏輯程序依舊保持著很好的控制權(quán),保證了系統(tǒng)升級(jí)更新的便利性
關(guān)于系統(tǒng)的可擴(kuò)展性,ExtJs就能很好的處理,在下一節(jié)中會(huì)有詳細(xì)描述。
3.難點(diǎn)
CB/S結(jié)構(gòu)的應(yīng)用程序其實(shí)就是一個(gè)高度定制的瀏覽器。為了讓這個(gè)瀏覽器完成指定的功能(比如:包含ExtJs框架的js文件,做成cookie,發(fā)起請(qǐng)求等)難免會(huì)有很多客戶端和瀏覽器核心的交互。這些交互涉及到C++,Js,HTML,CSS等的互操作,是系統(tǒng)在技術(shù)上的難點(diǎn)。
三:客戶端瀏覽器實(shí)現(xiàn)
1.搭建開發(fā)環(huán)境
我們下載基于MinGW 4.8, OpenGL創(chuàng)建的QT 5.1,地址為:http://qt-project.org/downloads。不選擇基于VS編譯器的QT是因?yàn)橛肰S編譯器編譯出的DLL依賴VS運(yùn)行時(shí),分發(fā)程序時(shí)較困難。下載并安裝后,你會(huì)看到這并不是一個(gè)簡(jiǎn)單的界面庫(kù),它還包含了一個(gè)IDE,Qt Creator。
安裝完成后,就可以使用Qt Creator來(lái)創(chuàng)建你自己的基于Qt的桌面程序,你可以在Qt Creator的歡迎界面看到入門程序、示例程序和幫助文檔。Qt的開發(fā)方式并不是本文所講述的重點(diǎn),建議讀者到官網(wǎng)學(xué)習(xí)。
雖然我們可以成功在Qt Creator內(nèi)編譯并成功執(zhí)行程序,但到windows目錄下通過(guò)雙擊執(zhí)行編譯出的exe程序,就不能正常運(yùn)行,這是因?yàn)榭蓤?zhí)行程序所需的動(dòng)態(tài)鏈接庫(kù)并沒(méi)有與可執(zhí)行程序在同一個(gè)目錄內(nèi),至于可執(zhí)行程序依賴哪些動(dòng)態(tài)鏈接庫(kù),我們將在本文第四節(jié)詳細(xì)描述。
2.邊框和標(biāo)題欄
目前大部分windows桌面程序都使用自定義的邊框和標(biāo)題欄,比如QQ,360安全衛(wèi)士等,使用MFC或Windows API自定義窗口的標(biāo)題欄和邊框并不是一件容易的事情,使用Qt來(lái)開發(fā)Windows桌面程序也有一樣的困難。
由于我們開發(fā)的是企業(yè)應(yīng)用系統(tǒng),這類系統(tǒng)一般情況下都出于最大化狀態(tài),所以我們?cè)诳紤]自定義標(biāo)題欄和邊框的時(shí)候就可以不用考慮還原按鈕、拖拽改變窗口大小和位置的功能。但是,我們需要為標(biāo)題欄增加一個(gè)下拉菜單按鈕,以使用戶完成系統(tǒng)設(shè)置、打開調(diào)試器等相關(guān)功能。
另外,為了使標(biāo)題欄和業(yè)務(wù)界面中ExtJs的風(fēng)格一致,我們索性去掉了主窗口的標(biāo)題欄和邊框,直接使用ExtJs來(lái)生成。
在Qt中去掉標(biāo)題欄和邊框是很容易的事,創(chuàng)建窗口的時(shí)候設(shè)置一個(gè)WindowFlags即可,見(jiàn)如下代碼:
w.setWindowFlags(Qt::FramelessWindowHint);
但設(shè)置此WindowFlags之后隨之帶來(lái)的問(wèn)題是,窗口將撐滿整個(gè)屏幕,把系統(tǒng)的任務(wù)欄也遮住了,這顯然不是我們想要的,解決此問(wèn)題需要重寫Qt窗口類的changeEvent槽,見(jiàn)如下代碼:
if(event->WindowStateChange)
{
switch(this->windowState())
{
case Qt::WindowMinimized:
this->hide();
event->ignore();
break;
case Qt::WindowMaximized:
QDesktopWidget desktopWidget =QApplication::desktop();
QRect deskRect =desktopWidget->availableGeometry();
this->resize(deskRect.width(), deskRect.height());
break;
}
}
這樣創(chuàng)建的Qt窗口將不具有標(biāo)題欄和邊框,至于如何用ExtJs來(lái)渲染標(biāo)題欄,以及如何實(shí)現(xiàn)標(biāo)題欄的最小化及關(guān)閉等功能,將在后續(xù)小節(jié)講述。
3.打開新窗口
使用Qt的WebKit非常簡(jiǎn)單,直接把QWebView控件拖放到界面中去即可,但是默認(rèn)的QWebView在實(shí)現(xiàn)上有些缺憾,比如無(wú)法打開新窗口,無(wú)法下載文件,無(wú)法打印等。然而這些功能是一個(gè)瀏覽器所必備的功能,我們的CB/S企業(yè)應(yīng)用系統(tǒng)也需要這些功能。要想讓瀏覽器支撐這些功能,只能通過(guò)重寫QWebView來(lái)完成。
要想讓自制的瀏覽器打開新窗口,需要重寫QWebView的createWindow方法,見(jiàn)如下代碼:(UtmpWebView即為QWebView的子類)
UtmpWebView webView = new UtmpWebView;
QWebPage newWeb = new QWebPage;
if(type == QWebPage::WebModalDialog)
{
webView->setWindowModality(Qt::ApplicationModal);
}
webView->setAttribute(Qt::WA_DeleteOnClose,true);
webView->setPage(newWeb);
webView->show();
return webView;
然而,這只能應(yīng)對(duì)a標(biāo)簽的target屬性為_blank的新窗口鏈接,無(wú)法應(yīng)對(duì)使用javascript通過(guò)window.open的方式打開新窗口的場(chǎng)景。要想滿足這一點(diǎn),必須在QWebView的構(gòu)造函數(shù)里,更改一下瀏覽器的配置參數(shù),代碼如下:
QWebSettings default_settings = QWebSettings::globalSettings();
default_settings->setAttribute(QWebSettings::JavascriptEnabled,true);
default_settings->setAttribute(QWebSettings::JavascriptCanOpenWindows,true);
4.打印
我們經(jīng)常在網(wǎng)頁(yè)中通過(guò)javascript使用window.print的方式來(lái)調(diào)用打印機(jī)打印HTML頁(yè)面,常見(jiàn)的瀏覽器都會(huì)支持這個(gè)功能,然而QWebView默認(rèn)并不支持此功能,要想讓我們定制的瀏覽器支持此功能必須為其做一個(gè)事件鏈接,代碼如下:
connect(this->page(), SIGNAL(printRequested(QWebFrame)),this,SLOT(customPrintRequested(QWebFrame)));
this->page()->setForwardUnsupportedContent(true);
customPrintRequested槽的實(shí)現(xiàn)如下:
QPrinter* p = new QPrinter(QPrinter::HighResolution);
QPrintDialog printDialog(p, this);
printDialog.setWindowTitle("UTMP打印");
if(printDialog.exec() != QDialog::Accepted)
{
return;
}
frame->print(p);
5.下載
同樣QWebView默認(rèn)也不支持下載文件。所有的瀏覽器把請(qǐng)求的響應(yīng)分為兩類,一類是瀏覽器可以解析的(Html文本),另一類是瀏覽器無(wú)法解析的(文件),常見(jiàn)的瀏覽器遇到無(wú)法解析的文件,往往會(huì)下載到本地給用戶使用,要想讓QWebView支持下載,就必須截獲瀏覽器的unsupportedContent信號(hào),該信號(hào)所對(duì)應(yīng)的槽的代碼實(shí)現(xiàn)如下
ShellExecuteA(NULL, "open", reply->url().toString().toStdString().c_str(), "", "", SW_SHOW);
注意,要想讓上面的代碼正確執(zhí)行,必須在頭文件中引入windows.h(這也體現(xiàn)出QT框架與NativeAPI能沒(méi)有任何限制的輕松交互)。上面的代碼是調(diào)用了系統(tǒng)默認(rèn)的瀏覽器來(lái)完成下載。當(dāng)然讀者也可以考慮自己實(shí)現(xiàn)下載線程并提示下載進(jìn)度、保存地址等。
6.與頁(yè)面腳本交互
我們既然選擇自己開發(fā)瀏覽器,那么瀏覽器一定能自如的讓頁(yè)面執(zhí)行一些特殊腳本,頁(yè)面也可以通過(guò)腳本讓瀏覽器完成一些腳本無(wú)法完成的操作。此功能一般的瀏覽器都無(wú)法支撐,只有我們自定義的QWebView可以輕松實(shí)現(xiàn)。
我們知道javascript在頁(yè)面中執(zhí)行都會(huì)用到window對(duì)象,比如,我們調(diào)用alert()方法時(shí),其實(shí)是調(diào)用window.alert()方法,使用document對(duì)象時(shí),其實(shí)是使用window.document對(duì)象,要想讓瀏覽器能與頁(yè)面腳本交互,我們必須讓瀏覽器給頁(yè)面的window對(duì)象注冊(cè)一個(gè)子對(duì)象(window對(duì)象的屬性)。
遇到的第一個(gè)問(wèn)題并不是如何注冊(cè)此對(duì)象,而是在何時(shí)注冊(cè)。由于在頁(yè)面加載之初,window對(duì)象就已經(jīng)初始化完成了,此時(shí)為其注冊(cè)子對(duì)象已為時(shí)已晚,必須在其初始化之前為其注冊(cè),為此QWebView專門提供了javaScriptWindowObjectCleared信號(hào),在刷新網(wǎng)頁(yè)、打開新網(wǎng)頁(yè)和加載嵌套的iframe頁(yè)面時(shí)(window對(duì)象初始化時(shí)),此信號(hào)都會(huì)被觸發(fā)。與此信號(hào)關(guān)聯(lián)的槽,代碼如下:
this->page()->mainFrame()->addToJavaScriptWindowObject("QtWinFrame", this);
如你所見(jiàn),我們?yōu)閣indow對(duì)象注冊(cè)了一個(gè)名為QtWinFrame的對(duì)象。這就像瀏覽器為window對(duì)象注冊(cè)document子對(duì)象一樣,要想讓頁(yè)面腳本能調(diào)用瀏覽器核心的方法,必須為讓瀏覽器核心提供相應(yīng)的方法才行,由于我們?cè)诘诙」?jié)已經(jīng)把窗口默認(rèn)的標(biāo)題欄和邊框去掉了,所以必須通過(guò)頁(yè)面javascript來(lái)關(guān)閉瀏覽器和最小化瀏覽器,假設(shè)我們?cè)跒g覽器核心中實(shí)現(xiàn)的方法代碼如下:
void UtmpWebView::SetFrameWindow(int flag)
{
switch(flag)
{
case 0:
this->close();
break;
case 1:
this->showMinimized();
break;
}
}
在瀏覽器頁(yè)面內(nèi),只要通過(guò)如下javascript代碼,即可讓瀏覽器核心執(zhí)行相應(yīng)的操作:
QtWinFrame.SetFrameWindow(1);QtWinFrame.SetFrameWindow(0);
相對(duì)于“腳本讓瀏覽器執(zhí)行工作”來(lái)說(shuō),“瀏覽器讓腳本執(zhí)行工作”就簡(jiǎn)單很多,只需要在瀏覽器中調(diào)用evaluateJavaScript方法即可,見(jiàn)如下代碼:
this->page()->mainFrame()->evaluateJavaScript("testFun();");
注意:這有些類似于javascirpt中的eval()方法,如果前端框架中引入了ExtJs,最好不要直接使用此方法來(lái)調(diào)用ExtJs提供的函數(shù),執(zhí)行效率非常慢??梢韵仍陧?yè)面上用普通的js函數(shù)包裝一下ExtJs提供的函數(shù),再來(lái)調(diào)用。
7.打開腳本調(diào)試器
調(diào)試javascript代碼一直以來(lái)都是開發(fā)人員面臨的老大難的問(wèn)題,自從有了FireBug和谷歌瀏覽器自帶的javascript調(diào)試器之后,這個(gè)問(wèn)題得到了很大程度的解決,所以有個(gè)好的javascript調(diào)試器十分關(guān)鍵。QWebView也提供了相應(yīng)的調(diào)試工具(我認(rèn)為就是谷歌瀏覽器的javascript調(diào)試器,但未經(jīng)驗(yàn)證。)。使瀏覽器核心打開調(diào)試器的代碼如下:
QDialog d = new QDialog(this,(Qt::WindowMinimizeButtonHint|Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint));
d->setAttribute(Qt::WA_DeleteOnClose, true);
QWebInspector wi = new QWebInspector(d);
wi->setPage(this->page());
d->setLayout(new QVBoxLayout());
d->layout()->setMargin(0);
d->layout()->addWidget(wi);
d->show();
d->resize(600,350);
由于我們?cè)谙到y(tǒng)啟動(dòng)的時(shí)候,使用Qt::FramelessWindowHint屬性禁用掉了窗口的標(biāo)題欄和邊框,所以在打開調(diào)試器子窗口的時(shí)候,要恢復(fù)該子窗口的標(biāo)題欄和邊框,為此我們多做了一些工作,讀者也可以自己實(shí)現(xiàn)QDialog類型的父類,以應(yīng)對(duì)更多子窗口業(yè)務(wù)。
8.截獲瀏覽器請(qǐng)求
既然我們對(duì)瀏覽器有最大的控制權(quán),那么我們就希望當(dāng)瀏覽器完成指定工作時(shí)通知我們,好讓我們做一些前期或后期的處理。最常見(jiàn)的工作莫過(guò)于瀏覽器發(fā)起請(qǐng)求了。我們知道瀏覽器解析一個(gè)網(wǎng)頁(yè)的過(guò)程中,可能會(huì)發(fā)起多次請(qǐng)求,比如圖片標(biāo)簽的src路徑,iframe標(biāo)簽的src路徑,js/css資源的路徑等等。要想知道這些請(qǐng)求何時(shí)發(fā)起,何時(shí)終結(jié)需要重寫QNetworkAccessManager,然后通過(guò)如下方式,讓瀏覽器加載自定義的QNetworkAccessManager
QNetworkAccessManager oldManager = webview->page->networkAccessManager();
MyNetworkAccessManager newManager = new MyNetworkAccessManager(oldManager, this);
webview->page->setNetworkAccessManager(newManager);
然后,我們可以在自定義的MyNetworkAccessManager類中重寫createRequest(QNetworkAccessManager::Operation operation,const QNetworkRequest &request, QIODevice *device)方法,其中request參數(shù),包含了原始請(qǐng)求的URL信息,此方法需要返回一個(gè)QNetworkReply對(duì)象,假設(shè)我們想改變?cè)颊?qǐng)求的路徑,可以按如下操作方式來(lái)完成
return QNetworkAccessManager::createRequest(operation, myrequest, device);
如你所見(jiàn),我們用QNetworkAccessManager新建了一個(gè)請(qǐng)求(createRequest的返回值為QNetworkReply類型),該請(qǐng)求中myrequest實(shí)參的類型為QNetworkRequest,其他兩個(gè)實(shí)參從原始方法中獲得。
9.本地化ExtJs庫(kù)
一般我們使用ExtJs(官方地址:http://localhost:8080/UTMP/app.js"></script>
在QT中只需要通過(guò)本地路徑加載這個(gè)靜態(tài)頁(yè)面即可,代碼如下:
UtmpWebView w;
QDir dir(QDir::currentPath());
QUrl url = url.fromLocalFile(dir.path()+"/debug/index.html");
w.load(url);
由此可見(jiàn),保存在客戶端的資源基本都是業(yè)務(wù)無(wú)關(guān)的、比較穩(wěn)定的、不易變更的資源。保存在服務(wù)端的內(nèi)容,都是與業(yè)務(wù)有關(guān)的,比較容易變更的內(nèi)容,這種機(jī)制主要意圖是保證了業(yè)務(wù)的可升級(jí)性。
四:服務(wù)端業(yè)務(wù)腳本
1.OPOA模式
使用Extjs的企業(yè)應(yīng)用系統(tǒng)大多都是OPOA模式(One Page One Application),OPOA模式的WEB系統(tǒng)只有一個(gè)頁(yè)面,在這個(gè)頁(yè)面中會(huì)引入extjs的資源并通過(guò)js來(lái)渲染一個(gè)框架頁(yè)面,然后根據(jù)用戶的操作載入更多的js代碼,來(lái)完成不同的業(yè)務(wù)。對(duì)于我們的系統(tǒng)來(lái)說(shuō)這個(gè)頁(yè)面就是放在客戶端本地debug目錄下的靜態(tài)頁(yè)面。這個(gè)頁(yè)面引入了一個(gè)服務(wù)器端的js文件(http://localhost:8080/UTMP/app.js),通過(guò)此文件以及由此文件加載的其他js文件,我們渲染出了一個(gè)框架頁(yè)面,見(jiàn)如下代碼:
Ext.application({
name:'UTMP',
appFolder:'
controllers:["sys.index"],
views:["sys.menuTree","sys.titleBar","sys.contentTabPanel"],
launch:function(){
Ext.create('Ext.Viewport',{
layout:'border',
items:[
{xtype: 'menuTree'},
{xtype: 'titleBar'},
{xtype: 'contentTabPanel'}
]
});
}
});
如你所見(jiàn),這是一個(gè)Extjs系統(tǒng)的開始(Ext.application),而且我們使用了Extjs的MVC模式(關(guān)于ExtJs的MVC模式的相關(guān)資料請(qǐng)參閱:http://docs.sencha.com/extjs/4.2.1/#!/guide/application_architecture),系統(tǒng)界面中包含三個(gè)視圖:menuTree、titleBar和contentTabPanel。由于我們?cè)O(shè)計(jì)的瀏覽器沒(méi)有標(biāo)題欄,所以視圖titleBar就是系統(tǒng)的標(biāo)題欄,它包含了關(guān)閉、最小化按鈕。
2.定制模塊加載基址
Extjs有一套獨(dú)特的模塊加載機(jī)制,它可以通過(guò)js類的名稱空間來(lái)加載相應(yīng)的js代碼文件,比如視圖文件的名稱空間是UTMP.sys.menuTree,ExtJs框架會(huì)從appFolder指定的路徑下找sys目錄下的menuTree.js文件。在普通的ExtJs項(xiàng)目中,appFolder屬性并不用設(shè)定為絕對(duì)路徑,只需要使用相對(duì)路徑即可,但由于我們的項(xiàng)目的主頁(yè)(靜態(tài)頁(yè)面)是放在客戶端本地的,如果使用相對(duì)路徑的話,ExtJs框架就會(huì)在客戶端本地尋找相應(yīng)的資源,然而我們的業(yè)務(wù)JS文件都是放在服務(wù)端的,所以勢(shì)必會(huì)無(wú)法加載這些資源。
3.定制AJAX請(qǐng)求基址
模塊加載機(jī)制可以通過(guò)設(shè)置appFolder基路徑來(lái)解決,但是對(duì)于業(yè)務(wù)JS代碼隨處可見(jiàn)的AJAX請(qǐng)求該如何處理呢?確實(shí),AJAX請(qǐng)求也會(huì)面臨這種問(wèn)題,而且更為突出。因?yàn)樵贓xtJs中對(duì)AJAX請(qǐng)求做了很多封裝:proxy、store、request、load等,隨處可見(jiàn)ajax的身影。幸而ExtJs是一個(gè)對(duì)象化程度較高的js類庫(kù),使得這個(gè)問(wèn)題能很容易的解決。
在ExtJs中所有Ajax請(qǐng)求都離不開Ext.data.Connection類的支撐,我們可以使用ExtJs提供的觀察者模式來(lái)注冊(cè)Ext.data.Connection類的beforerequest事件(這是一種侵入性較強(qiáng)的做法),這樣所有的AJAX請(qǐng)求,不管是proxy發(fā)起的,還是request發(fā)起的,都逃不出我們的手心,具體實(shí)現(xiàn)代碼如下:
Ext.util.Observable.observe(Ext.data.Connection,{
beforerequest: function(conn, options, eOpts){
options.url = ";
}
});
五:分發(fā)
1.依賴的動(dòng)態(tài)鏈接庫(kù)
在使用QTCreator開發(fā)基于QT的應(yīng)用程序時(shí),不管是debug編譯還是release編譯,都無(wú)法到編譯目錄下,通過(guò)雙擊exe程序來(lái)執(zhí)行應(yīng)用(會(huì)提示“無(wú)法啟動(dòng)此程序,因?yàn)橛?jì)算機(jī)中丟失xxxx.dll....”的錯(cuò)誤信息),之所以在IDE內(nèi)能順利執(zhí)行,是因?yàn)镮DE已經(jīng)為程序執(zhí)行創(chuàng)建好了環(huán)境,但倘若不解決此問(wèn)題,就無(wú)法把應(yīng)用程序分發(fā)給直接用戶。
要解決此問(wèn)題只要把Qt類庫(kù)提供的dll文件放在可執(zhí)行程序的目錄下或其所在目錄的子目錄下即可,在C:\Qt\Qt5.1.1\5.1.1\mingw48_32\bin目錄下有Qt類庫(kù)提供的大多數(shù)dll,這些dll名稱以字母d結(jié)尾的是debug編譯的應(yīng)用程序所依賴的類庫(kù),不以字母d結(jié)尾的則是release編譯的應(yīng)用程序所需要的類庫(kù),除了此目錄內(nèi)的dll外,在C:\Qt\Qt5.1.1\5.1.1\mingw48_32\plugins目錄下還有一些應(yīng)用程序需要的dll類庫(kù)。
如此數(shù)量眾多的dll,都需要打包到我們最終的安裝程序中去嗎?當(dāng)然不用這么做。通過(guò)IDE執(zhí)行我們的應(yīng)用程序時(shí),我們只需要通過(guò)processExplorer工具來(lái)查看應(yīng)用程序進(jìn)程所依賴的dll,即可判定哪些dll是需要打包到安裝包中去的(大多數(shù)情況下可以這么做,如果是開發(fā)人員通過(guò)代碼動(dòng)態(tài)加載的類庫(kù),那么我相信你也會(huì)自行甄別的)。
2.打包
可能有的讀者會(huì)問(wèn):“我可不可以把類庫(kù)靜態(tài)編譯到exe中去呢?”當(dāng)然可以,但是非常麻煩,你需要自己靜態(tài)編譯整個(gè)QT工程,還需要對(duì)IDE做出相應(yīng)的調(diào)整(要編譯QT的Webkit還需要做更多的工作),這是一項(xiàng)耗時(shí)、耗力還不一定能成功的工作,我不建議這么做。
當(dāng)我們找到應(yīng)用程序依賴的所有dll后,我們就可以使用打包工具來(lái)制作應(yīng)用程序的安裝包,當(dāng)然也可以自己開發(fā)安裝包工具(可以參見(jiàn)我的博客:http://www.cnblogs.com/liulun/archive/2011/12/12/2284360.html)。
國(guó)內(nèi)外有很多不錯(cuò)的打包工具,我推薦使用inno setup(http://www.jrsoftware.org/),它支持編寫腳本來(lái)控制安裝過(guò)程,使用LZMA壓縮算法來(lái)打包程序(壓縮效率非常高,是7-zip使用的壓縮算法),但它并不支持中文安裝界面,目前社區(qū)有開發(fā)者提供了針對(duì)inno setup的中文語(yǔ)言包,使用起來(lái)也非常方便。
藍(lán)藍(lán)設(shè)計(jì)的小編 http://yvirxh.cn