應用程序框架的使用

UIQ3開發白皮書系列文檔翻譯自UIQ3官方開發文檔;
本文檔英文名稱: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
PRJ_MMPFILES
QHelloWorld.mmp
PRJ_PLATFORMS
winscw
梢後,我們將介紹如何指定其它的內容:如何導出頭文件(公共接口),如何指定測試組件,如何使用gnumake makefile 編譯等等[16] .
4、 makefile (QHelloWorld.mmp)文件
    mmp文件類似於makefile 文件. 使用這個文件避免了不同編譯器,目標,IDE所帶來的複雜性。開發者所需要做的事情就是編寫合適的mmp文件 (參看[18]).我們的示例也需要一個mmp文件,文件內容示例如下

QUOTE:

// QHelloWorld.mmp
TARGET QHelloWorld.exe
TARGETTYPE EXE
UID 0x100039CE 0xE1000001
SOURCEPATH ../src
SYSTEMINCLUDE /epoc32/include
SOURCE QHelloWorldApplication.cpp
LIBRARY EUSER.LIB
TARGET 指定了我們要創建的二進制文件名稱, TARGETTYPE 指定了這個二進制文件的類型(通常是EXE 或DLL [19]).我們的示例是 EXE.
    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 bldfiles
cmd> abld build winscw udeb
abld 與 其他平臺上的make 工具非常相似。該工具的工作過程參看[17]. 它的第二個參數前面已經討論過了,表明我們編譯時所針對的目標平臺,再這裏可以指定  bld.inf 文件裏添加的任一目標平臺. 因爲我們只指定了winscw ,因此,我們現在只能使用這個參數。最後一個參數指定編譯的版本是 debug 還是release版本.通常爲模擬器編譯debug (udeb) 版本,爲實際設備生成release (urel) 版本(其中的u,表示默認支持unicode)。
     現在我們還不能運行我們的程序,在運行之前,我們還需要在系統中註冊我們的軟件。

三、應用程序註冊文件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.rss
#include <AppInfo.rh>
UID2 KUidAppRegistrationResourceFile
UID3 0xE1000001
RESOURCE APP_REGISTRATION_INFO
{
// filename of application binary (minus extension)
app_file = "QHelloWorld";
}
AppInfo.rh 文件中包含了這種資源文件的結構定義,因此需要包含它。UID2應該是KUidAppRegistrationResourceFile, UID3 必須對應於我們的程序的 UID, 從Symbian申請到的值. APP_REGISTRATION_INFO 是一個資源結構,定義了我們的程序的外觀和行爲,以及描述它提供的服務。現在,需要做的就是在註冊文件和可執行文件之間建立關聯。因此,我們指定了app_file結構,其它的值使用AppInfo.rh中定義的默認值.

4 、編譯_reg-file
     所有的資源都必須被編譯成更緊湊的格式,註冊文件也不例外。爲了編譯資源文件,在 mmp文件加入下列內容:

QUOTE:

// QHelloWorld.mmp
SOURCEPATH ../reg
START RESOURCE QHelloWorld_reg.rss
TARGETPATH /private/10003a3f/apps
END
SOURCEPATH 指定資源文件的路徑, TARGETPATH 指定編譯後的資源文件的路徑,對於程序註冊,必須指定爲/private/10003a3f/apps 目錄。 這是由 PlatSec 和 Data Caging (參看[6])要求的。
     在以前的版本中AIF文件總是和應用程序在同一目錄。但是現在,因爲 PlatSec的原因, _reg文件必須放在apparcserver的私有目錄裏面,由apparcserver負責程序的註冊。現在,我們需要重新編譯程序,因爲作了一些對mmp文件的修改。仍然使用如下指令:

QUOTE:

cmd> abld build winscw udeb
5、第一次運行程序
     現在我們的程序已經註冊了,可以開始運行了。首先在Pen Style UI 模式下啓動模擬器(參看“UI configuration” i) ,啓動方法:

QUOTE:

cmd> epoc
模擬器啓動後,可以看到Application Launcher ,在裏面可以找到我們的程序,可以看到程序名字以及默認的圖標。當我們選擇我們的程序的時候,發現什麼都沒有發生,毫不奇怪,因爲我們的程序代碼裏什麼都沒有做。因此,爲了看到程序的響應,我們來對代碼做些修改,編輯 E32Main() 函數如下:

QUOTE:

// QHelloWorldApplication.cpp
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;
}
在實際的應用程序中應該避免User::InfoPrint() 的調用,相應地可以使用CEikonEnv::InfoMsg() 函數代替,因爲它的效率更高。重新編譯後,重新啓動模擬器,從 Application Launcher 中選擇我們的程序時,可以看到我們的程序確實執行了(運行結果如下圖

UIQ3_Whitepaper_01_Start_Application_Framework.jpg),不過沒有什麼值得興奮的,因爲,現在程序的外觀還不符合UI style[1]中定義的標準。程序中還沒有標題欄、軟鍵欄,還不能在屏幕上繪製。只有當我們啓動應用程序框架之後,它才能成爲一個真正的程序,這也是接下來我們要做的,不過在此之前,先給程序指定一個真正的名稱和一個我們自己的圖標。
     在編譯時 要保證模擬器沒有運行,因爲它有可能會妨礙一些文件的正常寫入。如果在編譯過程中出現錯誤,應當確保刪除已經編譯的結果,然後重新編譯。步驟如下:

QUOTE:

cmd> abld reallyclean winscw udeb
cmd> abld build winscw udeb
6 、註冊文件的本地化部分 (_loc)
     註冊文件中僅包含非本地化的信息,本地化的信息被放進獨立的文件,稱作: _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
#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";
          }
    };
}
我們仍然使用默認圖標,只不過現在我們明確指定使用該圖標。number_of_icons specifies 指定使用的圖標的數目,通常應該是3個,也可以包含更多或更少。 (參看7).

      現在,我們只需要確保添加新的 _loc文件到 mmp文件,以便它能夠編譯。添加的方法與 _reg文件的添加一樣,不過, 需要注意的是這個文件的路徑是 /resource/apps目錄.因爲 PlatSec (data caging 參看[6]) ,該目錄必須不同於_reg文件的位置. _reg文件的位置前面已經討論過,_loc文件可以位於任一可讀的全局目錄 (因此不能是私有目錄). 推薦使用/resource/apps目錄, 因爲它提供全局可讀的訪問權限,以及寫保護。

QUOTE:

// QHelloWorld.mmp
SOURCEPATH ../reg
START RESOURCE QHelloWorld_loc.rss
TARGETPATH /resource/apps
END
mmp文件的可以隱含地包含語言定義。例如,如果要編譯爲 語言號碼爲 10的語言,可以指定 LANG 10 。每種語言在 e32const.h中有一個預定義的號碼 ,在示例中我們編譯資源爲 US English. 另一件需要做的事情就是:添加新的 _loc文件到我們的 _reg文件。 我們需要在_reg中爲APP_REGISTRATION_INFO結構 新添加一行,指明新創建的_loc文件:

QUOTE:

// QHelloWorld_reg.rss
localisable_resource_file = "//Resource//Apps//QHelloWorld_loc";
7、創建自定義的位圖
     按照 UIQ Style 指導書,應該提供3種不同大小的位圖:
• 小圖標: 18x18 pixels
• 大圖標: 40x40 pixels
• 特大圖標: 64x64 pixels
    因此,我們首先需要上述大小的 3 個位圖。除此之外,我們還需要每個位圖對應的 mask位圖,它指定了圖標中的那些部分可見 (白色),哪些部分透明(黑色).也可以通過指定 gray 值來定義半透明的部分.然後我們需要將這些獨立的位圖編譯成一個 mbm文件。 示例中的 mbm文件包含 6個獨立的 bitmap (3對位圖/mask ). 添加下列內容到 mmp文件:

QUOTE:

// QHelloWorld.mmp
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
這些說明創建一個 mbm文件 (qhelloworld_default_icon.mbm),其中包含了所有的 bitmap.創建過程中包含了正確的顏色深度的轉換  (由c16和 8指定).另外,創建了一個頭文件( HEADER關鍵字),使得我們可以在我們的代碼中包含 mbm 文件中的任意位圖文件。該文件將放入 /epoc32/include 目錄,名字爲 qhelloworld_default_icon.mbg.
     在上面的例子中,應用程序框架將包含不同位圖,頭文件並不重要,因此,我們只需確保指定的“bitmap/mask對”在一起。Mask應該定義爲 1位色 (黑白)或 8 位色(灰度值)。 例子中, masks 被定義爲 8bit (gray scale) ,圖標是16 位色bit (color).
     這就是創建mbm文件所需做的全部工作了。現在,可以用它來引用我們的 _loc文件。現在可以使用我們的圖標替換默認的圖標。圖標的數目不變,更新後的內容如下:

QUOTE:

// QHelloWorld_loc.rss
icon_file = "//resource//apps//qhelloworld_default_icon.mbm";
重新編譯後,在Application Launcher 中就可以看到我們的程序變化。 小圖標用於列表視圖,大圖標和超大圖標用於網格視圖 (非高亮和高亮),效果如下:


未命名.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
#include <eikstart.h>
TInt E32Main()
{
     return EikStart::RunApplication( CQHelloWorldApplication::NewApplication);
}
該函數使用一個指向一個non-leaving函數的指針,該函數返回一個指向我們程序的指針,如指向工廠函數的指針。我們將工廠函數放在CQHelloWorldApplication類中.在 UIQ2.x 中, RunApplication的調用是自動完成的,不需顯式調用,但是因爲我們的程序現在是
EXE,所以必須顯式調用,除此之外,其他的啓動過程是一樣的。

4.1  CQHelloWorldApplication 類
      我們的CQHelloWorldApplication 類非常簡單,從 CQikApplication繼承,並且只需要實現前面提到的工廠函數,以及 AppDllUid 和
CreateDocumentL函數.  QHelloWorldApplication.h 如下所示:

QUOTE:

// QHelloWorldApplication.h
#include <QikApplication.h>
class CQHelloWorldApplication : public CQikApplication
{
public:
static CApaApplication* NewApplication();
private:
TUid AppDllUid() const;
CApaDocument* CreateDocumentL();
};
現在來看一下這幾個函數的實現,需要強調的是:工廠函數是一個 non-leaving 函數,在函數中,要麼成功創建 CQHelloWorldApplication 對象,要麼失敗返回 NULL . 因此,不能使用 new(ELeave) 操作。

QUOTE:

// QHelloWorldApplication.cpp
#include "QHelloWorldApplication.h"
CApaApplication* CQHelloWorldApplication::NewApplication()
{
return new CQHelloWorldApplication();
}
其它兩個函數同樣簡單, AppDllUid 函數返回應用程序的UID,因爲應用程序 UID比任何環境都重要,所以,爲它單獨創建了一個頭文件 QHelloWorldExternalInterface.h,文件內容如下:

QUOTE:

// QHelloWorldExternalInterface.h
const TUid KUidQHelloWorldApp = {0xE1000001};
CreateDocumentL 函數也是一個工廠函數,創建一個文檔實例,稍後介紹:

QUOTE:

// QHelloWorldApplication.cpp
#include "QHelloWorldExternalInterface.h"
#include "QHelloWorldDocument.h"
TUid CQHelloWorldApplication::AppDllUid() const
{
    return KUidQHelloWorldApp;
}

CApaDocument* CQHelloWorldApplication::CreateDocumentL()
{
     return CQHelloWorldDocument::NewL(*this);
}
4.2 CQHelloWorldDocument 類
     CQHelloWorldDocument 類也非常簡單,大多數功能已經被基類實現了,或者在我們的小程序中不需要實現。唯一需要實現的就是CreateAppUiL() 函數.因爲這個類將擁有到我們程序數據、引擎、服務器的連接,所以爲它提供所有合適的構造函數。代碼如下:

QUOTE:

// QHelloWorldDocument.h
#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();
};
需要注意的是 CreateAppUiL()函數,同樣是一個一個工廠函數:

QUOTE:

// QHelloWorldDocument.cpp
#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()
{
}
4.3 CQHelloWorldAppUi 類

      現在到了實現 Application framework的最後一步了. AppUi 是實際需要的最後一步。AppUi 可能含有一個空的實現,但是我們需要在這裏做一些工作,因此,給它提供了一個標準的構造函數。

QUOTE:

// QHelloWorldAppUi.h
#include <QikAppUi.h>
class CQHelloWorldAppUi : public CQikAppUi
{
public:
CQHelloWorldAppUi();
void ConstructL();
~CQHelloWorldAppUi();
};
除了 ConstructL,其它函數都是空函數,ConstructL 調用它的基類的實現版本CQikAppUi::ConstructL(). 這個構造函數總是首先被調用,不管你要實現什麼。

QUOTE:

// QHelloWorldAppUi.cpp
#include "QHelloWorldAppUi.h"
CQHelloWorldAppUi::CQHelloWorldAppUi() : CQikAppUi()
{
}
CQHelloWorldAppUi::~CQHelloWorldAppUi()
{
}
void CQHelloWorldAppUi::ConstructL()
{
     CQikAppUi::ConstructL();
}
5、創建應用程序資源文件
     使用application framework的代碼已經足夠了,但是我們還漏掉了一件事情:應用程序資源文件。每個程序都有一個同名的資源文件。示例中,這個文件(QHelloWorld.rss)非常簡單,因爲,我們還什麼都沒有定義。我們現在僅僅擁有 application framework所要求的這些元素,但是到後面,我們將頻繁使用這個文件:

QUOTE:

// QHelloWorld.rss
#include <uikon.rh>
NAME HELW
RESOURCE RSS_SIGNATURE { }
RESOURCE TBUF { buf = ""; }
RESOURCE EIK_APP_INFO { }
上面的所有結構都必須被提供,而且必須按這個順序。NAME爲資源文件分配了一個名字,以便區分加載到這個程序中的不同的資源文件,任何4字母的名字都可以,但是通常使用應用程序名字的縮寫,唯一需要注意的就是:保證這個名字是唯一的。 RSS_SIGNATURE, TBUF, 以及EIK_APP_INFO 可以留空。

6 、編譯新的程序
      現在所有的文件都已經準備好了,但是還需要更新我們的mmp文件,以便能編譯所有的內容。首先,按前面的方法編譯應用程序資源文件,在mmp文件中添加:

QUOTE:

// QHelloWorld.mmp
SOURCEPATH ../rsc
START RESOURCE QHelloWorld.rss
HEADER
TARGETPATH /resource/apps
END
然後編譯源文件, 同樣需要添加 USERINCLUDE 以便編譯器能夠找到我們的頭文件,另外需要添加一些庫文件:

QUOTE:

// QHelloWorld.mmp
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
最後2個庫(QikAllocDll and QikAlloc), 實際上並不需要,但是爲任何應用程序(EXE)添加這兩個庫是一個比較好的習慣,因爲它可以確保你的程序能夠更好地處理 OOM situations,它們可以減少OOM situations 的發生。確保顯式加入這2個庫。

     重新編譯之後,就可以啓動程序了,程序已經使用了應用程序框架,因爲我們可以看見一個標題欄,和一個空的按鈕欄,不過看起來不是非常美觀,因爲在應用程序中間出現了一個“空洞” ,原因是:我們僅僅實現了框架,但是還沒有構造視圖。要想關閉程序,你可以關閉模擬器,或者使用組合鍵: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
#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();
};
上列函數的實現非常簡單. NewLC 函數遵循2階段構造(參看 [8]),該構造函數使用合適的參數調用CQikViewBase的構造函數.第2個參數對於切換模型非常重要,將在以後的白皮書中介紹,現在我們僅僅傳入KNullViewId.  ConstructL 需要調用 BaseConstructL(),其它的我們留空.正常情況,我們還需要爲ViewConstructL添加一些代碼來放置一些控件.在示例中.暫時不放任何控件,所以在ViewConstructL()中執行以下調用(在新版本的SDK中,甚至可以不調用):

QUOTE:

CQikCommandManager& cmdManager = CQikCommandManager::Static(*iEikonEnv);
cmdManager.CreateCommandListL(*this);
UIQ 3.0 引入了Static()函數,通常有2個不同的版本,其中一個帶  CEikonEnv 參數,另一個不帶. 推薦使用帶參數的版本,因爲它的執行更高效. 每個控件都保持一個 CEikonEnv/CCoeEnv指針: iEikonEnv/iCoeEnv (在程序內iEikonEnv 和iCoeEnv 是一樣的).如果你的SDK裏面沒有這些函數,說明你的SDK是老版本的,你可以:
1).升級到新版本或
2)不使用帶參數的版本,忍受糟糕的性能吧 (如果此調用出現的不錯,性能差別也不會太大).

     另一個需要實現的函數是ViewId(),前面說過:需要返回一個視圖標識符.該標識符包含2部分:應用程序 UID和視圖UID.視圖UID只需要在程序裏面唯一就足夠了.視圖可以是 從0x00000001 到 0x0FFFFFFF之間的值 (推薦)或者從symbian申請  (與應用程序 UID的申請一樣).在示例中,我們使用 0x00000001,將這個 UID添加到 QHelloWorldExternalInterface.h 文件:

QUOTE:

// QHelloWorldExternalInterface.h
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);
}
現在已經有了視圖,但是需要更新 CQHelloWorldAppUi 類去創建這個視圖. 視圖的擁有者是 CQikAppUi,它確保視圖已經正確地在視圖服務器中註冊. ConstructL的代碼如下:

QUOTE:

// QHelloWorldAppUi.cpp
#include "QHelloWorldView.h"
void CQHelloWorldAppUi::ConstructL()
{
     CQikAppUi::ConstructL();
     CQHelloWorldView* view = CQHelloWorldView::NewLC(*this);
     AddViewL(*view);
     CleanupStack::Pop();
}
接下來更新mmp文件如下:

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 配置調整視圖?
• 切換模型如何工作?
• 如何在不同的程序之間切換? 
發佈了14 篇原創文章 · 獲贊 0 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章