应用程序框架的使用

时间:2022-12-26 23:19:08
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 配置调整视图?
• 切换模型如何工作?
• 如何在不同的程序之间切换?