本文檔英文名稱:UIQ3_Whitepaper_01_Start_Application_Framework.pdf
翻譯者:yzhv@IOICN
歡迎轉載,請註明出處.
應用程序框架的使用
一、通過閱讀此文檔,開發者可以:
• 理解標準的 UIQ 工程的結構
• 瞭解重要的工程文件的用途
• 掌握程序註冊的方法
• 掌握程序框架的結構
• 掌握程序框架的使用
• 掌握如何創建視圖,以及在視圖中進行繪製的簡單方法.
二、Build 和make 文件
1、簡介:本文檔通過編寫一個非常簡單的程序來介紹UIQ工程文件的結構[12]。
2、目錄結構
所有的UIQ工程的結構都以相同的方式進行組織,理解在將要創建的工程裏需要包含的文件是非常重要的,因此,需要先介紹工程的目錄結構。如下圖:
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
工程中的目錄包括:
data: 包含了軟件所使用的一些數據。如:位圖文件、聲音文件。
doc: 包含了軟件組件的文檔資料。
group:包含創建應用程序所需要的所有配置文件 ,執行編譯指令時使用這些文件。
inc :包含公共的頭文件,這些文件不一定必須導出。
rsc :包含資源文件以及與資源相關的文件。
reg :包含程序註冊文件.
src :包含所有的源代碼文件。
對於不需要的目錄,可以直接刪除,每個目錄下可以按需要自定義目錄。如下圖:
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
3、 build 文件(bld.inf)
對於大多數開發者可以先在Windows上的模擬器上開發軟件,最後再爲ARM處理器編譯發行版本,這樣可以加快開發進度。 build 文件 (bld.inf),位於 group 目錄, 指定將要編譯的的目標軟件等.因爲我們僅編譯示例的QHelloWorld 程序,因此指定 :在PRJ_MMPFILES 節指定makefile的相對路徑(相對於bld.inf文件所在目錄).示例中 僅爲程序編譯針對Symbian OS 模擬器的版本,即winscw 目標,因此在 PRJ_PLATFORMS 節中指定.bld.inf 文件的內容如下:
QUOTE:
// QHelloWorld.mmp梢後,我們將介紹如何指定其它的內容:如何導出頭文件(公共接口),如何指定測試組件,如何使用gnumake makefile 編譯等等[16] .
PRJ_MMPFILES
QHelloWorld.mmp
PRJ_PLATFORMS
winscw
4、 makefile (QHelloWorld.mmp)文件
mmp文件類似於makefile 文件. 使用這個文件避免了不同編譯器,目標,IDE所帶來的複雜性。開發者所需要做的事情就是編寫合適的mmp文件 (參看[18]).我們的示例也需要一個mmp文件,文件內容示例如下
QUOTE:
// QHelloWorld.mmpTARGET 指定了我們要創建的二進制文件名稱, TARGETTYPE 指定了這個二進制文件的類型(通常是EXE 或DLL [19]).我們的示例是 EXE.
TARGET QHelloWorld.exe
TARGETTYPE EXE
UID 0x100039CE 0xE1000001
SOURCEPATH ../src
SYSTEMINCLUDE /epoc32/include
SOURCE QHelloWorldApplication.cpp
LIBRARY EUSER.LIB
UID 唯一地標識了我們的程序. 每個 Symbian OS 的二進制程序使用3個 UID (參看 [20])進行標識. 第一個對所有的二進制文件都是一樣的,因此,上面的代碼中沒有指定。在 mmp文件中指定的是 UID2 和UID3. UID2說明可執行文件的類型,通常對於exe文件是0x100039CE(使用另一個UID). 第三個UID唯一標識我們的程序。開發者需要向 Symbian (參看 [21])請求分配UID。在開發階段可以使用保留的開發專用 UID,範圍是: 從0xE1000000 到0xEFFFFFFF. 當發佈程序的時候需要使用從symbian申請到的UID。 UID的申請以及安裝包的製作將在其他的白皮書中介紹。
SOURCEPATH 指定源代碼的位置,SYSTEMINCLUDE 指明系統頭文件的位置,SOURCE指定要編譯的源代碼文件,LIBRARY 指明鏈接時需要使用的庫文件, EUSER(與stdlib相似), 是最基本的庫文件,總是需要引用.
5 、最簡單的示例程序
爲了編譯成功,我們需要添加一些額外的代碼。至少需要添加一個入口點函數,在symbian中是 E32Main2.代碼如下:
QUOTE:
// QHelloWorldApplication.cpp該程序所做的事情僅僅是直接返回。這已經足夠了。
#include <e32std.h>
TInt E32Main()
{
return KErrNone;
}
6、 第一次編譯
現在我們已經爲編譯作好了準備.編譯過程包含兩個階段。 第一步,生成必要的編譯配置文件. 這個步驟基本上不需要程序員完成,我們需要做的就是修改我們的bld.inf 文件. 第一步創建abld.bat文件, 一個在第二步用到的批處理文件。這些步驟的執行方法,啓動一個 command控制檯, 將當前工作目錄切換到group 目錄,確保這個目錄裏包含你創建的 bld.inf 文件,輸入下列命令
QUOTE:
cmd> bldmake bldfilesabld 與 其他平臺上的make 工具非常相似。該工具的工作過程參看[17]. 它的第二個參數前面已經討論過了,表明我們編譯時所針對的目標平臺,再這裏可以指定 bld.inf 文件裏添加的任一目標平臺. 因爲我們只指定了winscw ,因此,我們現在只能使用這個參數。最後一個參數指定編譯的版本是 debug 還是release版本.通常爲模擬器編譯debug (udeb) 版本,爲實際設備生成release (urel) 版本(其中的u,表示默認支持unicode)。
cmd> abld build winscw udeb
現在我們還不能運行我們的程序,在運行之前,我們還需要在系統中註冊我們的軟件。
三、應用程序註冊文件Application Registration Files (AIF)
1、簡介
應用程序註冊文件是一個資源文件,用於在系統中進行軟件註冊。只有在註冊之後,軟件才能被用戶使用. 註冊的結果是:程序加載器能夠列出新的程序及其圖標。完整的註冊過程不僅僅需要註冊文件,還需要下列文件:
1)一個註冊文件 (_reg)
2)至少一個本地化文件 (_loc), 每種語言對應一個。
3.)至少一個 multi-bitmap 文件 (mbm) ,其中包含多個位圖文件
所有這些信息在UIQ 2.x 中被整合到一個文件中,即 Application Information File (AIF). 因此我們在UIQ3中仍然稱它們爲AIF, 不過意思是 Application Information Files. 下圖爲應用程序加載器( Application Launcher):
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
因爲在Symbian OS v9.0中 引入了PlatSec, AIF文件被分成了幾部分,一個註冊文件(_reg) ,一個本地化文件(_loc) 和一個位圖文件.這些文件的結構與內容跟原來的 AIF文件非常相似。
_ref文件是根文件, 其它的文件直接或間接與根文件建立關係,以後我們會更詳細地說明如何建立這些關係。這些文件的關係如下圖:說明不同AIF文件之間的關係:
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
在進行真正的編程之前,必須首先創建 AIF, 因爲沒有AIF,我們的程序什麼都不能做。
2、目錄結構
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
bld.inf 文件不需要做任何修改,因此上圖中沒有列出。
3、註冊文件 (_reg)
註冊文件是一個普通的資源文件 (rss) (參看[10]),具有如下類似結構:
QUOTE:
// QHelloWorld_reg.rssAppInfo.rh 文件中包含了這種資源文件的結構定義,因此需要包含它。UID2應該是KUidAppRegistrationResourceFile, UID3 必須對應於我們的程序的 UID, 從Symbian申請到的值. APP_REGISTRATION_INFO 是一個資源結構,定義了我們的程序的外觀和行爲,以及描述它提供的服務。現在,需要做的就是在註冊文件和可執行文件之間建立關聯。因此,我們指定了app_file結構,其它的值使用AppInfo.rh中定義的默認值.
#include <AppInfo.rh>
UID2 KUidAppRegistrationResourceFile
UID3 0xE1000001
RESOURCE APP_REGISTRATION_INFO
{
// filename of application binary (minus extension)
app_file = "QHelloWorld";
}
4 、編譯_reg-file
所有的資源都必須被編譯成更緊湊的格式,註冊文件也不例外。爲了編譯資源文件,在 mmp文件加入下列內容:
QUOTE:
// QHelloWorld.mmpSOURCEPATH 指定資源文件的路徑, TARGETPATH 指定編譯後的資源文件的路徑,對於程序註冊,必須指定爲/private/10003a3f/apps 目錄。 這是由 PlatSec 和 Data Caging (參看[6])要求的。
SOURCEPATH ../reg
START RESOURCE QHelloWorld_reg.rss
TARGETPATH /private/10003a3f/apps
END
在以前的版本中AIF文件總是和應用程序在同一目錄。但是現在,因爲 PlatSec的原因, _reg文件必須放在apparcserver的私有目錄裏面,由apparcserver負責程序的註冊。現在,我們需要重新編譯程序,因爲作了一些對mmp文件的修改。仍然使用如下指令:
QUOTE:
cmd> abld build winscw udeb5、第一次運行程序
現在我們的程序已經註冊了,可以開始運行了。首先在Pen Style UI 模式下啓動模擬器(參看“UI configuration” i) ,啓動方法:
QUOTE:
cmd> epoc模擬器啓動後,可以看到Application Launcher ,在裏面可以找到我們的程序,可以看到程序名字以及默認的圖標。當我們選擇我們的程序的時候,發現什麼都沒有發生,毫不奇怪,因爲我們的程序代碼裏什麼都沒有做。因此,爲了看到程序的響應,我們來對代碼做些修改,編輯 E32Main() 函數如下:
QUOTE:
// QHelloWorldApplication.cpp在實際的應用程序中應該避免User::InfoPrint() 的調用,相應地可以使用CEikonEnv::InfoMsg() 函數代替,因爲它的效率更高。重新編譯後,重新啓動模擬器,從 Application Launcher 中選擇我們的程序時,可以看到我們的程序確實執行了(運行結果如下圖
TInt E32Main()
{
// define a non-modifiable compile time allocated
// descrīptor (Symbian OS string)
_LIT(KQHelloWorldString, "Hello World");
// show an indication
User::InfoPrint(KQHelloWorldString);
return KErrNone;
}
UIQ3_Whitepaper_01_Start_Application_Framework.jpg),不過沒有什麼值得興奮的,因爲,現在程序的外觀還不符合UI style[1]中定義的標準。程序中還沒有標題欄、軟鍵欄,還不能在屏幕上繪製。只有當我們啓動應用程序框架之後,它才能成爲一個真正的程序,這也是接下來我們要做的,不過在此之前,先給程序指定一個真正的名稱和一個我們自己的圖標。
在編譯時 要保證模擬器沒有運行,因爲它有可能會妨礙一些文件的正常寫入。如果在編譯過程中出現錯誤,應當確保刪除已經編譯的結果,然後重新編譯。步驟如下:
QUOTE:
cmd> abld reallyclean winscw udeb6 、註冊文件的本地化部分 (_loc)
cmd> abld build winscw udeb
註冊文件中僅包含非本地化的信息,本地化的信息被放進獨立的文件,稱作: _loc文件。 該文件在系統中跟其它資源文件一樣可以被進行本地化。如何進行本地化不是本文檔的內容。 _loc文件定義了一個LOCALISABLE_APP_INFO結構,該結構在 AppInfo.rh 文件中被重新定義,它包含了標題和圖標信息。需要說明的是: short_caption 還沒有被 UIQ使用, 將來可能使用,因此應當設置爲有意義的值。 _loc 文件僅包含了 bitmap 文件的位置信息。在 UIQ 2.x 中 mbm文件被封裝進 AIF 文件,但是現在,我們只需要指明 bitmap文件的位置。我們的 QHelloWorld_loc.rss 文件的內容如下:
QUOTE:
// QHelloWorld_loc.rss我們仍然使用默認圖標,只不過現在我們明確指定使用該圖標。number_of_icons specifies 指定使用的圖標的數目,通常應該是3個,也可以包含更多或更少。 (參看7).
#include <AppInfo.rh>
RESOURCE LOCALISABLE_APP_INFO
{
short_caption = "Hello!";
caption_and_icon =
{
CAPTION_AND_ICON_INFO
{
caption = "Hello World!";
number_of_icons = 3;
icon_file = "//Resource//Apps//default_app_icon.mbm";
}
};
}
現在,我們只需要確保添加新的 _loc文件到 mmp文件,以便它能夠編譯。添加的方法與 _reg文件的添加一樣,不過, 需要注意的是這個文件的路徑是 /resource/apps目錄.因爲 PlatSec (data caging 參看[6]) ,該目錄必須不同於_reg文件的位置. _reg文件的位置前面已經討論過,_loc文件可以位於任一可讀的全局目錄 (因此不能是私有目錄). 推薦使用/resource/apps目錄, 因爲它提供全局可讀的訪問權限,以及寫保護。
QUOTE:
// QHelloWorld.mmpmmp文件的可以隱含地包含語言定義。例如,如果要編譯爲 語言號碼爲 10的語言,可以指定 LANG 10 。每種語言在 e32const.h中有一個預定義的號碼 ,在示例中我們編譯資源爲 US English. 另一件需要做的事情就是:添加新的 _loc文件到我們的 _reg文件。 我們需要在_reg中爲APP_REGISTRATION_INFO結構 新添加一行,指明新創建的_loc文件:
SOURCEPATH ../reg
START RESOURCE QHelloWorld_loc.rss
TARGETPATH /resource/apps
END
QUOTE:
// QHelloWorld_reg.rss7、創建自定義的位圖
localisable_resource_file = "//Resource//Apps//QHelloWorld_loc";
按照 UIQ Style 指導書,應該提供3種不同大小的位圖:
• 小圖標: 18x18 pixels
• 大圖標: 40x40 pixels
• 特大圖標: 64x64 pixels
因此,我們首先需要上述大小的 3 個位圖。除此之外,我們還需要每個位圖對應的 mask位圖,它指定了圖標中的那些部分可見 (白色),哪些部分透明(黑色).也可以通過指定 gray 值來定義半透明的部分.然後我們需要將這些獨立的位圖編譯成一個 mbm文件。 示例中的 mbm文件包含 6個獨立的 bitmap (3對位圖/mask ). 添加下列內容到 mmp文件:
QUOTE:
// QHelloWorld.mmp這些說明創建一個 mbm文件 (qhelloworld_default_icon.mbm),其中包含了所有的 bitmap.創建過程中包含了正確的顏色深度的轉換 (由c16和 8指定).另外,創建了一個頭文件( HEADER關鍵字),使得我們可以在我們的代碼中包含 mbm 文件中的任意位圖文件。該文件將放入 /epoc32/include 目錄,名字爲 qhelloworld_default_icon.mbg.
START BITMAP qhelloworld_default_icon.mbm
TARGETPATH /resource/apps
HEADER
SOURCEPATH ../data/appicon
SOURCE c16 QHelloWorld_small.bmp
SOURCE 8 QHelloWorld_small_mask.bmp
SOURCE c16 QHelloWorld_large.bmp
SOURCE 8 QHelloWorld_large_mask.bmp
SOURCE c16 QHelloWorld_xLarge.bmp
SOURCE 8 QHelloWorld_xLarge_mask.bmp
END
在上面的例子中,應用程序框架將包含不同位圖,頭文件並不重要,因此,我們只需確保指定的“bitmap/mask對”在一起。Mask應該定義爲 1位色 (黑白)或 8 位色(灰度值)。 例子中, masks 被定義爲 8bit (gray scale) ,圖標是16 位色bit (color).
這就是創建mbm文件所需做的全部工作了。現在,可以用它來引用我們的 _loc文件。現在可以使用我們的圖標替換默認的圖標。圖標的數目不變,更新後的內容如下:
QUOTE:
// QHelloWorld_loc.rss重新編譯後,在Application Launcher 中就可以看到我們的程序變化。 小圖標用於列表視圖,大圖標和超大圖標用於網格視圖 (非高亮和高亮),效果如下:
icon_file = "//resource//apps//qhelloworld_default_icon.mbm";
未命名.JPG
前面我們介紹了 如何正確地註冊程序,但是我們仍然沒有實現一個漂亮的圖形界面,現在,我們就來創建更誘人的界面,第一步就是產生應用程序框架 application framework.
四、應用程序框架 Application Framework
1 、簡介
前面介紹了大量準備工作,現在將要介紹真正的代碼編寫。完全從0開始編寫圖形界面的程序非常困難,所有的工作可以由應用程序框架來完成,包括::
• 創建與文件服務器的連接
• 創建與窗口服務器的連接
• 創建與內存管理器的連接
• 註冊工作
• 處理錯誤和 OOM
• 初始化其它的應用服務 (字體提供服務, FEPs等等)
• 創建默認的屏幕設備
• ...
• 最後,啓動活動調度器 (事件循環)
因此,我們首先介紹非常重要的 application framework.接下來我們將看到這對我們的程序意味着什麼,以及我們需要做些什麼。
2、目錄結構如下:
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
3、設計概述
不需要知道 application FW (Application Framework)看起來象什麼, 但是,理解FW 的基本結構非常重要,大多數應用程序至少包含下面4個基本基類:
• Application (CQikApplication)
• Document (CQikDocument)
• AppUi (CQikAppUi)
• View (CQikViewBase)
“Application” 是應用程序工作在Application FW中的基礎. 這個類主要負責創建一個 “Document”. “Document”負責處理應用程序數據,它也負責創建到引擎、文件、數據庫的連接等等,另外,負責創建“AppUi”。“AppUi” 本身是一個非圖形化的類,但是這個類擁有大部分的圖形組件,包括屏幕設備,例如:軟鍵欄,程序標題欄等等,因此,這個類負責創建不同的視圖。 在我們的例子中,只包含一個視圖,這個視圖是我們的程序的第一個圖形單元。也是軟件用戶將要真正看到的界面,其他的內容主要在後臺運行。在UIQ2.x中,應用程序不必擁有視圖,即使有視圖,也僅僅是一個抽象接口 (MCoeView). 在 UIQ3中,這條原則仍然有效,大多數應用程序使用具體可見的視圖,因此從CCoeControl 和 MCoeView繼承; 視圖的新基類 (CQikViewBase) 保證正確地完成。這個轉換很容易完成,僅僅把從CCoeControl 和MCoeView的繼承改爲從 CQikViewBase繼承。這也使得 CQikAppUi 有可能擁有視圖。關於視圖,更多的描述將在其它的白皮書中介紹。
我們的任務就是:爲我們的軟件定義這4個類,這不需要花費太多工作,因爲大多數的功能在基類中已經實現了,我們只需繼承就可以了。繼承的結構和構造的順序如下圖:
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
application framework 沿着紅色箭頭按從左到右的順序構造應用程序。按照什麼順序進行構造,是由低層應用程序框架來處理,但是能夠處理什麼是由高層(我們的軟件)來決定。
4 、啓動應用程序框架
第一步就是啓動 application framework. 僅僅通過調用 EikStart::RunApplication()就可以了, 該函數定義在eikstart.h. 修改我們程序的入口點函數如下:
QUOTE:
// QHelloWorldApplication.cpp該函數使用一個指向一個non-leaving函數的指針,該函數返回一個指向我們程序的指針,如指向工廠函數的指針。我們將工廠函數放在CQHelloWorldApplication類中.在 UIQ2.x 中, RunApplication的調用是自動完成的,不需顯式調用,但是因爲我們的程序現在是
#include <eikstart.h>
TInt E32Main()
{
return EikStart::RunApplication( CQHelloWorldApplication::NewApplication);
}
EXE,所以必須顯式調用,除此之外,其他的啓動過程是一樣的。
4.1 CQHelloWorldApplication 類
我們的CQHelloWorldApplication 類非常簡單,從 CQikApplication繼承,並且只需要實現前面提到的工廠函數,以及 AppDllUid 和
CreateDocumentL函數. QHelloWorldApplication.h 如下所示:
QUOTE:
// QHelloWorldApplication.h現在來看一下這幾個函數的實現,需要強調的是:工廠函數是一個 non-leaving 函數,在函數中,要麼成功創建 CQHelloWorldApplication 對象,要麼失敗返回 NULL . 因此,不能使用 new(ELeave) 操作。
#include <QikApplication.h>
class CQHelloWorldApplication : public CQikApplication
{
public:
static CApaApplication* NewApplication();
private:
TUid AppDllUid() const;
CApaDocument* CreateDocumentL();
};
QUOTE:
// QHelloWorldApplication.cpp其它兩個函數同樣簡單, AppDllUid 函數返回應用程序的UID,因爲應用程序 UID比任何環境都重要,所以,爲它單獨創建了一個頭文件 QHelloWorldExternalInterface.h,文件內容如下:
#include "QHelloWorldApplication.h"
CApaApplication* CQHelloWorldApplication::NewApplication()
{
return new CQHelloWorldApplication();
}
QUOTE:
// QHelloWorldExternalInterface.hCreateDocumentL 函數也是一個工廠函數,創建一個文檔實例,稍後介紹:
const TUid KUidQHelloWorldApp = {0xE1000001};
QUOTE:
// QHelloWorldApplication.cpp4.2 CQHelloWorldDocument 類
#include "QHelloWorldExternalInterface.h"
#include "QHelloWorldDocument.h"
TUid CQHelloWorldApplication::AppDllUid() const
{
return KUidQHelloWorldApp;
}
CApaDocument* CQHelloWorldApplication::CreateDocumentL()
{
return CQHelloWorldDocument::NewL(*this);
}
CQHelloWorldDocument 類也非常簡單,大多數功能已經被基類實現了,或者在我們的小程序中不需要實現。唯一需要實現的就是CreateAppUiL() 函數.因爲這個類將擁有到我們程序數據、引擎、服務器的連接,所以爲它提供所有合適的構造函數。代碼如下:
QUOTE:
// QHelloWorldDocument.h需要注意的是 CreateAppUiL()函數,同樣是一個一個工廠函數:
#include <QikDocument.h>
class CQikApplication;
class CQikAppUi;
class CQHelloWorldDocument : public CQikDocument
{
public:
static CQHelloWorldDocument* NewL(CQikApplication& aApp);
~CQHelloWorldDocument();
private:
CEikAppUi* CreateAppUiL();
private:
CQHelloWorldDocument(CQikApplication& aApp);
void ConstructL();
};
QUOTE:
// QHelloWorldDocument.cpp4.3 CQHelloWorldAppUi 類
#include <QikApplication.h>
#include "QHelloWorldDocument.h"
#include "QHelloWorldAppUi.h"
CEikAppUi* CQHelloWorldDocument::CreateAppUiL()
{
return new (ELeave) CQHelloWorldAppUi();
}
CQHelloWorldDocument* CQHelloWorldDocument::NewL(CQikApplication& aApp)
{
CQHelloWorldDocument* self = new(ELeave) CQHelloWorldDocument(aApp);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
CQHelloWorldDocument::~CQHelloWorldDocument()
{
}
CQHelloWorldDocument::CQHelloWorldDocument(CQikApplication& aApp)
: CQikDocument(aApp)
{
}
void CQHelloWorldDocument::ConstructL()
{
}
現在到了實現 Application framework的最後一步了. AppUi 是實際需要的最後一步。AppUi 可能含有一個空的實現,但是我們需要在這裏做一些工作,因此,給它提供了一個標準的構造函數。
QUOTE:
// QHelloWorldAppUi.h除了 ConstructL,其它函數都是空函數,ConstructL 調用它的基類的實現版本CQikAppUi::ConstructL(). 這個構造函數總是首先被調用,不管你要實現什麼。
#include <QikAppUi.h>
class CQHelloWorldAppUi : public CQikAppUi
{
public:
CQHelloWorldAppUi();
void ConstructL();
~CQHelloWorldAppUi();
};
QUOTE:
// QHelloWorldAppUi.cpp5、創建應用程序資源文件
#include "QHelloWorldAppUi.h"
CQHelloWorldAppUi::CQHelloWorldAppUi() : CQikAppUi()
{
}
CQHelloWorldAppUi::~CQHelloWorldAppUi()
{
}
void CQHelloWorldAppUi::ConstructL()
{
CQikAppUi::ConstructL();
}
使用application framework的代碼已經足夠了,但是我們還漏掉了一件事情:應用程序資源文件。每個程序都有一個同名的資源文件。示例中,這個文件(QHelloWorld.rss)非常簡單,因爲,我們還什麼都沒有定義。我們現在僅僅擁有 application framework所要求的這些元素,但是到後面,我們將頻繁使用這個文件:
QUOTE:
// QHelloWorld.rss上面的所有結構都必須被提供,而且必須按這個順序。NAME爲資源文件分配了一個名字,以便區分加載到這個程序中的不同的資源文件,任何4字母的名字都可以,但是通常使用應用程序名字的縮寫,唯一需要注意的就是:保證這個名字是唯一的。 RSS_SIGNATURE, TBUF, 以及EIK_APP_INFO 可以留空。
#include <uikon.rh>
NAME HELW
RESOURCE RSS_SIGNATURE { }
RESOURCE TBUF { buf = ""; }
RESOURCE EIK_APP_INFO { }
6 、編譯新的程序
現在所有的文件都已經準備好了,但是還需要更新我們的mmp文件,以便能編譯所有的內容。首先,按前面的方法編譯應用程序資源文件,在mmp文件中添加:
QUOTE:
// QHelloWorld.mmp然後編譯源文件, 同樣需要添加 USERINCLUDE 以便編譯器能夠找到我們的頭文件,另外需要添加一些庫文件:
SOURCEPATH ../rsc
START RESOURCE QHelloWorld.rss
HEADER
TARGETPATH /resource/apps
END
QUOTE:
// QHelloWorld.mmp最後2個庫(QikAllocDll and QikAlloc), 實際上並不需要,但是爲任何應用程序(EXE)添加這兩個庫是一個比較好的習慣,因爲它可以確保你的程序能夠更好地處理 OOM situations,它們可以減少OOM situations 的發生。確保顯式加入這2個庫。
USERINCLUDE ../inc
SOURCE QHelloWorldApplication.cpp
SOURCE QHelloWorldDocument.cpp
SOURCE QHelloWorldAppUi.cpp
LIBRARY EUSER.LIB
LIBRARY APPARC.LIB
LIBRARY CONE.LIB
LIBRARY EIKCORE.LIB
LIBRARY QIKCORE.LIB
LIBRARY QIKALLOCDLL.LIB
STATICLIBRARY QIKALLOC.LIB
重新編譯之後,就可以啓動程序了,程序已經使用了應用程序框架,因爲我們可以看見一個標題欄,和一個空的按鈕欄,不過看起來不是非常美觀,因爲在應用程序中間出現了一個“空洞” ,原因是:我們僅僅實現了框架,但是還沒有構造視圖。要想關閉程序,你可以關閉模擬器,或者使用組合鍵:Shift-Ctrl-Alt-K,這個組合可以在 debug版本下殺死程序。如果你使用Application Launcher鍵(在 FW
keypad,如下圖
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
) 或 Application Launcher 按鈕 (在狀態欄 ),將僅僅將應用程序放入後臺運行,但是它仍然在運行。
7、創建視圖
創建視圖最簡單的方法是直接從CQikViewBase繼承,然後實現必要的函數.首先需要實現的就是構造函數和析構函數 .構造函數使用兩階段構造,使得程序能夠更快地啓動.視圖的完整構造只有在視圖需要的時候纔會發生,第一階段,只完成一些儘可能少的基本工作,唯一需要完成的就是在此處向視圖服務器註冊視圖;在工廠函數 NewLC中封裝這部分功能.向視圖服務器註冊可以使得用戶能夠從其它程序跳轉到我們的視圖,更詳細的信息在下一個白皮書中介紹. 第二階段, ViewConstructL, 進一步構造視圖,並且在需要的時候被應用程序框架調用.此時,視圖中的所有元素應當可用.
在我們的示例中,在第二階段裏需要初始化視圖,在每次視圖顯示的時候,調用ViewActivatedL,這樣這樣就有機會在視圖顯示的時候,使用用戶數據顯示.
在 UIQ2.x中可以將構造函數從 ViewConstructL移到視圖的正常構造函數中.但是,在UIQ 3.0中,不太可能,因爲這需要在啓動的時候完成完整的構造,這不是UIQ3的推薦做法. 因此,要求總是首先調用 PreemptViewConstructionL. 另一個與UIQ2.x 不同的是:視圖以異步的方式 激活或 deactivated.另一個需要做的是:實現ViewId 函數,唯一標識視圖,該函數被應用程序框架調用,在介紹函數的實現的時候,將會介紹一些程序員需要遵守的一些規則.現在看一下我們的視圖類:
QUOTE:
// QHelloWorldView.h上列函數的實現非常簡單. NewLC 函數遵循2階段構造(參看 [8]),該構造函數使用合適的參數調用CQikViewBase的構造函數.第2個參數對於切換模型非常重要,將在以後的白皮書中介紹,現在我們僅僅傳入KNullViewId. ConstructL 需要調用 BaseConstructL(),其它的我們留空.正常情況,我們還需要爲ViewConstructL添加一些代碼來放置一些控件.在示例中.暫時不放任何控件,所以在ViewConstructL()中執行以下調用(在新版本的SDK中,甚至可以不調用):
#include <QikViewBase.h>
class CQHelloWorldView : public CQikViewBase
{
public:
static CQHelloWorldView* NewLC(CQikAppUi& aAppUi);
~CQHelloWorldView();
TVwsViewId ViewId()const;
protected:
void ViewConstructL();
void ViewActivatedL(const TVwsViewId &aPrevViewId,
TUid aCustomMessageId,
const TDesC8 &aCustomMessage);
private:
CQHelloWorldView(CQikAppUi& aAppUi);
void ConstructL();
};
QUOTE:
CQikCommandManager& cmdManager = CQikCommandManager::Static(*iEikonEnv);UIQ 3.0 引入了Static()函數,通常有2個不同的版本,其中一個帶 CEikonEnv 參數,另一個不帶. 推薦使用帶參數的版本,因爲它的執行更高效. 每個控件都保持一個 CEikonEnv/CCoeEnv指針: iEikonEnv/iCoeEnv (在程序內iEikonEnv 和iCoeEnv 是一樣的).如果你的SDK裏面沒有這些函數,說明你的SDK是老版本的,你可以:
cmdManager.CreateCommandListL(*this);
1).升級到新版本或
2)不使用帶參數的版本,忍受糟糕的性能吧 (如果此調用出現的不錯,性能差別也不會太大).
另一個需要實現的函數是ViewId(),前面說過:需要返回一個視圖標識符.該標識符包含2部分:應用程序 UID和視圖UID.視圖UID只需要在程序裏面唯一就足夠了.視圖可以是 從0x00000001 到 0x0FFFFFFF之間的值 (推薦)或者從symbian申請 (與應用程序 UID的申請一樣).在示例中,我們使用 0x00000001,將這個 UID添加到 QHelloWorldExternalInterface.h 文件:
QUOTE:
// QHelloWorldExternalInterface.h現在已經有了視圖,但是需要更新 CQHelloWorldAppUi 類去創建這個視圖. 視圖的擁有者是 CQikAppUi,它確保視圖已經正確地在視圖服務器中註冊. ConstructL的代碼如下:
const TUid KUidQHelloWorldView = {0x00000001};
// QHelloWorldView.cpp
#include <QikAppUi.h>
#include <QikCommand.h>
#include "QHelloWorldView.h"
#include "QHelloWorldExternalInterface.h"
CQHelloWorldView* CQHelloWorldView::NewLC(CQikAppUi& aAppUi)
{
CQHelloWorldView* self = new(ELeave) CQHelloworldView(aAppUi);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}
CQHelloWorldView::CQHelloWorldView(CQikAppUi& aAppUi)
: CQikViewBase(aAppUi, KNullViewId)
{
}
void CQHelloWorldView::ConstructL()
{
CQikViewBase::BaseConstructL();
}
CQHelloWorldView::~CQHelloWorldView()
{
}
void CQHelloWorldView::ViewConstructL()
{
CQikCommandManager& cmdManager = CQikCommandManager::Static(*iEikonEnv);
cmdManager.CreateCommandListL(*this);
}
void CQHelloWorldView::ViewActivatedL(const TVwsViewId &aPrevViewId,TUid aCustomMessageId,const TDesC8 &aCustomMessage)
{
}
TVwsViewId CQHelloWorldView::ViewId() const
{
return TVwsViewId(KUidQHelloWorldApp, KUidQHelloWorldView);
}
QUOTE:
// QHelloWorldAppUi.cpp接下來更新mmp文件如下:
#include "QHelloWorldView.h"
void CQHelloWorldAppUi::ConstructL()
{
CQikAppUi::ConstructL();
CQHelloWorldView* view = CQHelloWorldView::NewLC(*this);
AddViewL(*view);
CleanupStack::Pop();
}
QUOTE:
// QHelloWorld.mmp重新編譯並運行程序,可以發現:
SOURCE QHelloWorldView.cpp
LIBRARY eikcoctl.lib
1) 程序界面中的"空洞"已經被默認背景圖片填充了.自動更新的區域取決於用戶使用的主題.
2) 界面按鈕欄上出現了一個"後退"按鈕.
3) 按下"後退",可以返回到 Application Launcher, 但是它不關閉我們的程序,程序的關閉由系統自動完成(在需要的時候),到後面,我們會介紹:調試階段爲了測試,許關閉程序 是一個非常好的選擇.
8、 後續工作
在結束之前, 爲視圖進行一些簡單的繪製。所有的繪製在 圖形上下文(CWindowGC, 參看[28])中完成,該上下文在所有從 CCoeControl 中繼承的類中均可用。窗口服務器使得繪製非常簡單,需要做的就是實現虛函數 Draw (virtual void CCoeControl::Draw(const TRect& aRect) const) (參看[29]).通常在繪製之前需要做一些初始化工作,不過這些可以由應用程序框架完成。這意味着:並不是任何時候都可以隨心所欲地進行繪製。這也是 Draw() 函數被聲明爲私有函數的原因。但是在從CCoeControl 繼承的類中調用重繪 總是允許的。(按你喜歡的順序):
1)調用Window().Invalidate(const TRect& aRect)函數使需要重繪的區域無效。
2)調用 DrawDeferred()函數對控件調度重繪。
3)調用DrawNow()立即繪製。
因此,第一步需要獲得準備繪製的圖形上下文,一般總是系統圖形上下文 System Graphical Context (SystemGc),然後開始繪製。示例中,設置筆顏色爲紅色,然後繪製一個矩形,位置是從屏幕頂部 50 pixels 和左側 50 pixels處,大小是 100×100 pixels.首先爲視圖添加 Draw() 函數:
QUOTE:
// QHelloWorldView.h座標相對於控件,直接從標題欄下面開始繪製。
class CQHelloWorldView : public CQikViewBase
{
[...]
void Draw(const TRect& aRect) const;
}
// QHelloWorldView.cpp
void CQHelloWorldView::Draw(const TRect& /*aRect*/) const
{
CWindowGc& gc=SystemGc();
gc.SetPenColor(TRgb(0xff, 0x00, 0x00));
gc.DrawRect(TRect(TPoint(50,50), TSize(100,100)));
}
Draw 函數裏面的矩形包含了需要繪製的區域,如果需要進行復雜的繪製,就要進行優化,繪製什麼內容就取決於程序員了:可以繪製任何內容。
UIQ3_Whitepaper_01_Start_Application_Framework.jpg
五、結束語
本文檔重點介紹瞭如何使用應用程序框架,完整的源代碼在此。
下一個白皮書將會在此示例上添加更復雜的內容,主要包括:
• 如何從資源文件構造視圖內容?
• 如何創建命令?
• 如何爲不同的UI 配置調整視圖?
• 切換模型如何工作?
• 如何在不同的程序之間切換?