视图、命令、资源文件及布局的使用

<UIQ3开发白皮书1>应用程序框架的使用

UIQ3开发白皮书系列文档翻译自UIQ3官方开发文档;
本文档英文名称:UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.pdf
翻译者:yzhv@IOICN
欢迎转载,请注明出处作者.

视图、命令、资源文件及布局的使用
   

一、简介
    在前一个白皮书中(参看 [1])讨论了:如何使用应用程序框架,最后生成了一个具有空视图的应用程序。 在本文中,继续在程序中添加功能。但是首先我们先介绍一些基本的概念,通过阅读本文,你可以:
• 更好地理解UI配置,
• 更好地理解 Build块的概念。
• 更好地理解命令处理框架Command Processing Framework (CPF)的概念。
• 掌握如何从资源文件构造视图内容。
• 掌握如何在视图中处理命令,
• 掌握如何在不同的 UI配置中调整视图.


  在前一白皮书中提到的:
• 切换模型的工作方式
• 如何在不同的程序之间切换
   将放在以后的白皮书中介绍。

二、UIQ 3 中的一些基本概念
2.1 简介
     UIQ 3 引入了一些新的概念,本文档重点介绍其中的三个,这些背景知识将在下一章中使用。

1. 第一个概念在前一白皮书中已经提到过 (参看 [1]):UI 配置.本文将更详细地进行讨论.
2. 第二个概念:命令处理框架 Command Processing Framework (CPF),该框架取代了以前的菜单系统,允许我们以比菜单系统更抽象的方式处理命令。
3. 第三个概念是管理布局的一种,构建程序的一种方式。在 UIQ 2.x 中,因为所有内容必须手工创建,因此程序的布局非常难以处理,现在,我们有三种方式来布局我们的程序:
(a) Listbox: 列表框是布局相似信息的一种简单方式。在 UIQ 2.x中 也可以使用列表框,但是使用起来非常困难,几乎不可能创建自己的布局。
(b) Layout manager:布局管理器在其它的平台上非常流行(参看 [6]),它允许在一个区域放置不同的控件,而且大多数情况下,取代了控件的手工布局 。UIQ布局管理器包括:
• 行布局管理器
• 流布局管理器
• 网格布局管理器
•列布局管理器
(c) Building block: Build块是布局控件的一种新方式, Build块结合了布局管理器的强大功能,同时提供了列表框控件。

     本文档不讨论listbox和 layout manager,它们将在以后的白皮书中讨论 (或者参看[2]). 我们只讨论 building block。
4. 第4个概念我们不再详细介绍了,因为我们整个白皮书都是基于这种概念的:资源文件驱动的布局, Resource-File-Driven
Layout.

2.2 UI 配置

2.2.1 简介
    UI 配置的概念已经介绍过了(参看[1]). 当时,介绍了模拟器的在2种不同的配置情况:笔风格和软键风格的 UI配置。在上一个白皮书中最后完成的程序在这2种配置下均可工作。在笔风格UI配置下,可以看到“后退”按钮,如果运行在软键风格UI配置下,该按钮自动消失。对我们的程序来说,这种变化是透明的,在设计程序的时候就应该考虑这个问题,以便找到一个差异最小的设计方案。通常为设备添加一个触摸屏问题并不大,因此:推荐先针对软键模式开发,然后在触摸屏模式下进行优化,这也是本文要做的主要内容,因此,先配置模拟器运行在 软键UI配置下,在命令行中输入:

QUOTE:

cmd> uiqenv -ui softkey
那么不同UI 配置的区别是什么?在不同UI配置下运行UIQ 最终意味着什么?接下来我们将要给出一个概括性的概念,你将看到它对程序员的影响。需要注意的是 只有特定的配置特性通过UI 配置处理,我将还会介绍一些不能被UI 配置处理的自定义区域.

2.2.2 一般性介绍
    UIQ 软件平台功能非常强大,可以通过很多高层的配置参数进行配置。这些参数目前包括:
Screen mode :屏幕模式是定义屏幕分辨率的方式.目前包括 4种已定义的屏幕模式(分辨率如下):
• 纵式(QVGA, 240 x 320 pixels)
• 横式(QVGA, 320 x 240 pixels)
• 小型纵式(240 x 256 pixels)
• 小型横式(256 x 240 pixels).

上述的分辨率仅仅是粗略的大小,实际屏幕分辨率取决于具体设备,可能有所变化。如果需要知道精确的分辨率,需要查询显示屏设备的厂商。
屏幕朝向(orientation):表示是否使用正常或反向模式(屏幕旋转180 度)。通常会以正常模式来开发应用,因为屏幕的朝向对您的开发不会有多大影响。
触摸屏:显示该应用程序能否被优化来充分利用数字转换器。并非所有的应用程序都可受益于此类优化(例如动作类游戏),这取决于硬件是否支持。下文会解释,应用程序将要采用的主要交互风格是决定是否为触摸屏进行优化的更佳依据。
交互风格:目前有2中不同的交互风格
• 笔风格(pen-style),为手写笔进行优化(笔风格用户界面并不意味着交互必须使用手写笔进行);
• 软键风格(softkey-style),为单手操作进行优化。
这些UI 参数结合起来被称为UI 配置,并可通过唯一地ID辨认。头文件Qikon.hrh 含有一些预设的UI 配置,例如:KQikSoftkeyStylePortrait。将来还可能添加其他配置参数。该文件还说明了这些ID如何使用。

2.2.3 UI 配置可以提供的内容
      上面介绍的UI 配置参数仅仅是目前我们用到的,其他的配置用做将来的扩展.UIQ 3 手机不一定要在单一的UI 配置中运行,例如,
Sony Ericsson P990 可运行在下述六种屏幕模式里:
• KQikSoftkeyStyleLandscape
• KQikSoftkeyStylePortrait
• KQikSoftkeyStyleLandscape180
• KQikSoftkeyStyleSmallLandscape
• KQikSoftkeyStyleSmallPortrait
• KQikSoftkeyStyleSmallLandscape180.

未命名.JPG
然而,在同一时间,只能够有一个配置可被激活。例如不可同时运行纵向与横向模式。


2.2.4 如何开发
      前面已经提到:在任何时候UI配置只能同时运行一种.不同UI 配置是可以切换的。该切换可由硬件事件启动(例如:P990 手机上的“翻盖打开”或“翻盖关闭”),也可通过应用程序控制(例如:浏览器从纵向模式到横向模式切换)。通常,切换到横向模式意味着应用程序将在全屏模式下运行而不显示标题栏、状态栏等常见的修饰栏,这些内容我们在以后介绍。尽管可以根据特定的UI 配置来进行优化,但一般而言,应用程序的界面排版、指令清单和具体行为与UI配置是独立的。例如,“翻盖打开”时的纵向模式所显示的选项可能会比“翻盖关闭”时的纵向模式的选项要多;而横向模式里组件的布局可以和纵向模式里的不同等等。是否需要优化,主要取决于应用程序所提供的功能。

     注意,您不需要为所有的UI 配置编写代码。您的应用程序可能会正常运行在一个并非专门为其设计的UI 配置的设备上。例如,应用程序运行在一个仅支持笔风格UI 配置的设备上时,要求切换到软键风格的UI 配置,这一转换是不可能的。但如果程序仅仅定义了软键风格配置,在其初始化时,仍会使用软键风格配置的版本,但是将会实际运行在笔风格配置里。这一类初始化,即应用初始化的UI 配置与其运行的设备的不同,被称之为“降级路径”。应用程序框架将为应用初始化寻找最接近的、最恰当的UI 配置。降级路径大大简化了您的任务,因为您不需要为所有的UI 配置定义应用程序的界面排版。这也是前面提到的屏幕朝向对大多数应用程序没什么影响的原因,因为应用程序在正向和反向朝向中运行时,通常都采取相同的外观。
    降级路径亦使程序可以在未来的UI 配置上运行。
    在接下来的内容里,我们将介绍这些行为如何实现.

2.2.5 其它自定义区域
    注意:并非所有的自定义可以通过UI配置来处理,UI 配置只指定高层参数,手机厂家具有风格化UIQ3 的高度自由。请勿做以下假设:
• 屏幕组件(例如状态栏)的具体外观和位置;
• 指令分布——请遵照指南;
• 硬件按钮的映射(例如,假设具有非触摸屏UI 配置的手机应至少提供四个方向的导航及确认按钮);
• 缺省字体大小。
   在某部手机上,状态栏位于屏幕顶端,在另外一部,可能在底部,而在第三部里,可能在屏幕的边上。


2.3 指令处理框架Command Processing Framework (CPF)

2.3.1 简介
      如前所述,UIQ允许自定义扩展,软件在设备上需要能够变化,因此,需要一些适当的支持.例如,不必总是需要菜单栏,软件不能假设拥有这些元素,以及直接处理它们. 在没有触摸屏的设备上,软件的按钮显然是不太有用,因此,需要有一种方法将"按钮"转换为其它的元素. 所以.软件不能真地决定指令在哪里执行,因为这取决于UI配置,是否拥有菜单栏,按钮,软键,或者可能是硬键.我们必须把指令处理和指令显示分离开来,这通过指令处理框架(CPF)来完成 (参看 [10]),系统来决定显示指令的合理位置.(在特定的设备上提供哪些按钮,有一些特定的要求,例如:没有触摸屏 UI配置的设备需要提供至少 4中切换方式,一个确认按钮)
.

      程序只须定义它提供什么指令,这些指令如何显示由 CPF来决定.如果程序运行在软键配置下,这些指令应该总能显示.因此,这些指令必须能够反映正确的状态 (可见,暗化,选中等).所以,当打开菜单面板时,这些指令不可能动态初始化.DynInitMenuBarL 和DynInitMenuPaneL因此不能使用.另外,应该注意新的系统与 UIQ2.x中使用的旧的菜单系统之间的兼容性.这样才能方便移植.重要的特性下面介绍,更多信息参看[2].
     篇幅原因,此处仅提供command processing framework的简要介绍, CPF提供的优点包括:
1. 分布式指令生成
2. 分布式的抽象指令提供
3. 分布式指令处理

2.3.2 设计概要

指令模型:UIQ 通常拥有视图结构,每个视图拥有自己的一系列指令,当进行视图切换时,指令集进行相应的切换.当弹出对话框时,指令集也进行适当的切换,因此,每个视图,对话框,或者弹出对象都拥有自己的指令模型.只有在正确指令模型里面的指令才会被显示.
分布式指令生成及处理: 一般,指令模型由几个控件组成.例如,一个视图可以拥有一个具有文本的编辑框,用户应该提供一些指令处理文本,如:“剪切”, “复制”, 和 “粘贴”.这些指令除了可以被视图管理,也可以由控件自己直接处理.  CPF 允许指令模型由多个控件组成.每个组成构建自己的指令列表.
上述内容形成了指令管理器 command manager (CQikCommandManager), 组成了所有功能的入口点(参看下图).指令管理器 只有一个能够被程序进行全局访问.(控件栈上的任何控件都有自己的指令模型,只有拥有焦点的激活控件能够被使用).这使得
FEP能够在当前视图中添加自己的指令.

未命名.JPG
       需要说明的是CQikCommand 不是一个抽象接口,而是一个具体的类.从资源文件创建的指令总是从这种类型.,也可以重载这个类以提供其他功能.指令甚至可以自己实现MQikCommandHandler接口,然后自己处理.
组成部件: 这里介绍指令模式的参与者,以及相应UIQ接口:

    客户:生成指令并将指令添加到框架的实体.客户负责设置接收者,也就是每个指令的处理器. 在 UIQ 3 框架中客户是一个 CCoeControl对象,并且实现 MQikCommandModelOwner接口.
    接收器:执行与指令相关的动作.接收器实现MQikCommandHandler接口.
    激发器:请求相应的指令发出执行请求.例如,软键栏和程序的标题栏就是激发器.激发器实现 MQikCommandOperator 接口.


   注意:不一定要指定指令处理器,这种处理方法与 UIQ 2.x软件非常相似,所有的指令在 AppUi种处理.
指令操作器:有两种不同的指令操作器:默认的指令操作器由应用程序框架安装, 自定义的操作器可以在特定的情况下安装.后者允许以自定义的方式处理指令.自定义的处理器要求确保它能够在所有的UI 配置(甚至是未知的)上正常工作, 这使得它们的实现更巧妙,因此,推荐:避免使用自定义的操作器.
注意: 指令操作器不必拥有可见的界面.甚至某些硬键也是如此处理,例如 "后退"或"确认"键. 其它的键通过OfferKeyEventL() 函数处理,不使用 CPF 操作器处理.


2.4 Building Block (BB)
     building block是UIQ 3引入的新概念,用于加快在视图中的布局创建.创建视图时,可以选择想要使用的 building block.每个 building block 拥有一些空隙,在每个空隙里,可以放置任何控件.每个 building block有一个设置,指定其属性如: 页边,对齐及大小.简单点说:选择building block,使用控件填充空隙,将 building blocks放进视图,你的布局就完成了.

Building block的示例如下图:

未命名.JPG
Building block 也是普通的控件,不过没有可见的界面及状态信息,因为它们仅仅是其它控件的容器,仅仅负责这些控件的放置.因此,如果你想要替换视图的布局,可以使用新的building block替换当前的 building block.

      在这种布局切换过程中,可能发生下面3种情况:
1. 新布局要求的控件已经是旧布局的一部分:这种情况下,该控件应当重用,并且保持其状态信息.
2. 新布局不需要旧布局中的控件:这种情况下,该控件不再继续使用,我们可以隐藏它或者删除它.

3. 新布局要求使用一个旧布局中没有的控件:这种情况下需要构造控件,如果控件已经构造但是不可见,那么设置它可见.


    使用 building block时,即可以使用系统的building block ,也可以自己创建.推荐尽可能使用系统的,优点如下:
• 所有使用系统building blocks 能够获得一致的外观
• 所有的 building blocks 的行为经过了合理定义,使得可以估计可用的字符串空间 (对于特定的设备),这可以节约转换和本地化的时间,因为可以估计字符串被截断的情况.
    如果系统的 building block不能满足需要,可以创建自定义的 building block.在示例程序将使用系统building blocks. 如何创建自定义 building block 参看[2].
    前面说过 UIQ 3也引入了布局管理其,但是它们很少使用.大多数情况,仅用于布局不同的 building blocks (通常是行方式).大多数系统组件在内部使用布局管理器,但是没有给开发者开放.如果你想要在程序中使用,请参看[7].


三、视图布局的构造

3.1 简介
     本章将重点通过扩展前一白皮书中实现的程序,介绍如何从资源文件构造视图. 上次,我们通过代码直接构造所有的内容,虽然没有包含任何控件,但是工作的非常好.本章目标是:完全从资源文件构造视图(包括所有控件及布局).虽然可以直接从代码中直接创建,但是需要程序员做更多工作,包括:
• 控件构造
• 控件的拥有关系
• 焦点处理
• 布局处理
• 视图 绘制处理
• 屏幕模式切换
    资源结构允许用户极大的自由,因此刚开始会觉得比较复杂,因为它们可以分解成较小的子结构,这可能会导致更复杂,但是分割使得我们更容易理解如何创建视图,参看下图:展现了资源文件中的不同结构,以及它们之间的关系.



未命名.JPG
     跟我们在 UI配置节看到的以至,视图外观根据运行环境的 UI配置的改变而不同.刚开始,我们只为单个的 UI 配置定义一个视图:软键风格.这不代表它不能在其它的 UI配置下运行,仅代表该视图 是针对软键风格优化的.其它的 UI 配置,我们没有精确定义,应用程序框架自动选取合适的风格.第一步,我们将创建一个空的,然后逐步加入更复杂的内容.

3.2 目录结构
       按惯例先说明需要添假和修改的文件的目录结构,大多数修改主要是针对资源文件,另外还要添加一些bitmap和一个 hrh文件,如下图:


UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.jpg

3.3 基本资源文件的修改
3.3.1 简介
     先来做一些准备工作.首先,需要定义一个我们视图所要使用的资源文件结构 (QIK_VIEW_CONFIGURATIONS) ,它定义了视图的布局以及指令,指令在下一章讨论,现在留空.到后面我们会看到: 不在视图布局中定义控件会带来的便利性, 控件定义在 QIK_CONTROL_COLLECTION结构中.现在我们先提供它,尽管还不使用.定义完这2个结构(虽然只是空的)后,从资源文件构造视图. 我们应该能够通过修改资源文件,创建视图布局的剩余部分.

3.3.2 视图结构的定义

     资源文件结构应该至少包括下列结构,QHelloWorld.rss 资源文件的内容:

QUOTE:

// QHelloWorld.rss
#include <Qikon.hrh>
#include <Qikon.rh>
RESOURCE QIK_VIEW_CONFIGURATIONS r_qhelloworld_view_config
{
      configurations =
     {
          QIK_VIEW_CONFIGURATION
         {
                   ui_config_mode = KQikSoftkeyStylePortrait;
                   view = 0;
                   command_list = 0;
         }
     };
}


     前面说过,我们将在软键风格的 UI配置下继续演示示例,因此只为KQikSoftkeyStylePortrait定义了一个视图.  然后定义一个控件集合的结构,如下:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_CONTROL_COLLECTION r_qhelloworld_control_collection
{
      items =
     {
     };
}

      显然,大部分结构还是空的,但是 已经足够我们修改视图的构造代码来使用它们.

3.3.3 第一次从资源文件构造

      视图的构造代码原来是:

QUOTE:

// QHelloWorldView.cpp
void CQHelloWorldView::ViewConstructL()
{
        CQikCommandManager& cmdManager = CQikCommandManager::Static();
        cmdManager.CreateCommandListL(*this);
}
现在修改为:

QUOTE:

// QHelloWorldView.cpp
#include <QHelloWorld.rsg>
void CQHelloWorldView::ViewConstructL()
{
         ViewConstructFromResourceL(R_QHELLOWORLD_VIEW_CONFIG,
         R_QHELLOWORLD_CONTROL_COLLECTION);
}

     当然,需要在自动创建的资源文件中包含rsg文件,以便能够找到我们的资源结构,当从资源文件创建视图时,视图保证为我们创建合适的指令列表,所以我们不再需要 CreateCommandListL()函数了.执行如下命令重新编译程序:

QUOTE:

cmd> abld build winscw udeb
因为还没有定义布局,因此现在还看不到界面上的任何差异,接下来我们将介绍这些内容:创建布局.


3.4 创建布局
3.4.1 简介
      现在可以决定我们的视图里可以包含什么了.首先我们要添加一个 building block,其中包含一个控件.在创始它们之前,需要先定义视图结构.

3.4.2 定义视图布局结构
        视图结构非常简单,因为我们将使用大部分默认值 QIK_VIEW 结构定义如下:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_VIEW r_qhelloworld_view
{
     pages = r_qhelloworld_viewpages;
}
此处联接到 QIK_VIEW_PAGES 结构:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_VIEW_PAGES r_qhelloworld_viewpages
{
     pages =
    {
            QIK_VIEW_PAGE
            {
                  page_content = 0;
            }
    };
}


     下一节我们将定义 page_content此处留空(定义为0).再重新编译之前,需要更新 QIK_VIEW_CONFIGURATION 结构,联接到 QIK_VIEW ,结果如下:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_VIEW_CONFIGURATIONS r_qhelloworld_view_config
{
     configurations =
      {
            QIK_VIEW_CONFIGURATION
              {
                     ui_config_mode = KQikSoftkeyStylePortrait;
                     view = r_qhelloworld_view;
                     command_list = 0;
             }
       };
}

       为避免重新编译不必要的内容,我们需要将新的结构 添加到资源文件的末尾,如QIK_VIEW_CONFIGURATIONS 和 QIK_CONTROL_COLLECTION 结构在文件中的位置不变.
       然后重新编译程序,执行:

QUOTE:

cmd> abld resource winscw udeb
为确保修改是正确的,可以在模拟器中运行程序测试,不过不要期望看到可见的变化.


3.4.3 创建 building block


       现在可以给程序添加可见的修改了. 从系统 building blocks (参看[28])里选择使用 IconCaptionedOnelineBuildingBlock, 这个 building block 带有一个小图标,一个标题,一个任意控件. 这个 building block通过枚举值 EQikCtIconCaptionedOnelineBuildingBlock标识,定义在 QikStockControls.hrh文件中.
      UIQ提供的大多数控件又叫做 stock 控件,通过工厂函数创建.在 UIQ 2.x 中,这个原则适用于从资源文件创建对话框.在此,该原则用于构造视图,包括 building blocks.
      首先定义一个  QIK_XXX_CONTAINER_SETTINGS 结构.有两种选择:
QIK_CONTAINER_SETTINGS 是默认的容器,它不添加任何滚动栏. 如果在整个页面里只想包含一个控件,它就非常方便.任何滚动条可以由容器内的控件提供,这类容器的典型的例子就是列表框.如果控件比较多,可能会被放到可见区域之外,导致不可访问.
QIK_SCROLLABLE_CONTAINER_SETTINGS 适合于不止一个控件的情况,如果控件拥有自己的滚动条,那么就不应当使用该容器,因为你可能会面临2个滚动条, (一个是容器的,另一个是控件自己的l).

       起初,我们只有一个控件,但是后面可能会使用更多的控件,因此我们选用 QIK_SCROLLABLE_CONTAINER_SETTINGS 结构 (而不是使用缺省值 ). 在此结构里面,需要分别定义容器的元素.有多个QIK_CONTAINER_ITEM_XXX 资源结构可以使用,允许直接使用结构或者 间接联接. 可用的结构包括:
• QIK_CONTAINER_ITEM
• QIK_CONTAINER_ITEM_CI_LI
• QIK_CONTAINER_ITEM_CD_LI
• QIK_CONTAINER_ITEM_LD
• QIK_CONTAINER_ITEM_CI_LD
• QIK_CONTAINER_ITEM_CD_LD
• QIK_CONTAINER_ITEM_NESTED_CONTAINER
• QIK_CONTAINER_ITEM_NESTED_CONTAINER_CI_LI
     这些结构的区别请参看SDK文档,或者参看 Qikon.hrh 文件中的结构定义.在示例中,我们直接内含定义我们的控件,但是不想自定义布局的数据,因此我们选择 QIK_CONTAINER_ITEM_CD_LI 结构,而留空布局的数据. 第一步,我们不定义任何控件,除了 building block.因为  building block 不包含任何可见内容,因此做了修改之后仍不能看到变化:

QUOTE:

// QHelloWorld.rss
#include <QikStockControls.hrh>
RESOURCE QIK_SCROLLABLE_CONTAINER_SETTINGS r_qhelloworld_page_content
{
     controls =
    {
          QIK_CONTAINER_ITEM_CD_LI
         {
              type = EQikCtIconCaptionedOnelineBuildingBlock;
              control =
              {
                      QIK_SYSTEM_BUILDING_BLOCK
                      {
                            content =
                            {
                            };
                      }
               };
         }
    };
}

      不要忘记更新 QIK_VIEW_PAGE 结构,指定我们使用一个滚动容器EQikCtScrollableContainer),联接到其定义(QIK_SCROLLABLE_CONTAINER),它定义了滚动容器的行为,并且指定了使用的页面内容:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_VIEW_PAGES r_qhelloworld_viewpages
{
     pages =
    {
          QIK_VIEW_PAGE
         {
               container_type = EQikCtScrollableContainer;
               container = r_qhelloworld_container;
               page_content = r_qhelloworld_page_content;
         }
    };
}


// we are happy with the default behavīor of the scrollable container
RESOURCE QIK_SCROLLABLE_CONTAINER r_qhelloworld_container
{
}



3.4.4 创建标题
      现在可以创建控件,创建后就可以看到程序的变化
. 我们的 building block 可以容纳三个不同的控件,首先定义一个标题,需要填充 building block的合适的间隙; 因此创建 QIK_SLOT_CONTENT_XXX. 定义的时候,有几个变化需要注意,因为只定义标题,所以最合适的就是
QIK_SLOT_CONTENT 版本:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_SCROLLABLE_CONTAINER_SETTINGS r_qhelloworld_page_content
{
        controls =
        {
                  QIK_CONTAINER_ITEM_CD_LI
                  {
                        type = EQikCtIconCaptionedOnelineBuildingBlock;
                        control =
                        {
                                   QIK_SYSTEM_BUILDING_BLOCK
                                  {
                                           content =
                                          {
                                                  QIK_SLOT_CONTENT
                                                  {
                                                         slot_id = EQikItemSlot1;
                                                          caption = "Hello World";
                                                   }
                                           };
                                  }
                         };
                   }
         };
}


3.4.5 创建位图
      现在添加图标. 首先要创建一个mbm文件, 与第一个白皮书中介绍的方法一样.首先在mmp文件中定义mbm文件:

QUOTE:

// QHelloWorld.mmp
START BITMAP qhelloworld.mbm
TARGETPATH /private/E1000001
HEADER
SOURCEPATH ../data/image
SOURCE c16 GoldStar.bmp
SOURCE 8 GoldStar_mask.bmp
END


      bitmap文件存储在私有的数据缓冲目录里.这可以避免任何名字冲突,可以避免别人误用.我们创建自己的mbm文件,但是不添加到已有的mbm文件中,尽管那样做是可行的.首先使用更新的mmp文件创建新的mbm文件,确保编译正确,然后创建资源,执行下列命令:

QUOTE:

cmd> abld makefile winscw
cmd> abld resource winscw udeb
现在可以使用图标了,首先将图标的 mbg文件包含到资源文件,通过创建 CEikImage 控件,并放入building block的EQikIconSlot1 间隙中 . 因为要内含创建控件,因此使用 QIK_SLOT_CONTENT_DIRECT,现在 building block的定义如下:

QUOTE:

// QHelloWorld.rss
#include <QHelloWorld.mbg>
QIK_SYSTEM_BUILDING_BLOCK
{
       content =
      {
            QIK_SLOT_CONTENT
            {...},
            QIK_SLOT_CONTENT_DIRECT
            {
                 slot_id = EQikIconSlot1;
                 type = EEikCtImage;
                 control = QIK_IMAGE
                 {
                       content = QIK_CONTENT_MBM
                       {
                               bmpfile = //private//E1000001//QHelloWorld.mbm;
                               bmpid = EMbmQhelloworldGoldstar;
                               bmpmask = EMbmQhelloworldGoldstar_mask;
                       };
                  };
            }
      };
}
       重新编译资源文件,启动模拟器,现在只能看到布局的第一行,第二行的控件还没有添加(在下一节中介绍).如下图:


UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.jpg
       因为绘制矩形的代码没有删除,因此截图中仍然可以看到,通过对比,也可以看到UIQ 3 和UIQ 2.x的基本差别.
       添加控件的方法非常简单,复制已有的图标或标题,作为第2个控件,不过不能交互,因为用户不会和标题进行交互.因此,下面我们要介绍允许交互的控件的添加,例如编辑窗口.

3.4.6 创建控件
      下面将要为 building block添加一个edwin (editor window), 仅仅需要添加一个QIK_SLOT_CONTENT_DIRECT, 而且是一个edwin.

QUOTE:

// QHelloWorld.rss
QIK_SYSTEM_BUILDING_BLOCK
{
      content =
      {
            QIK_SLOT_CONTENT
           {
                slot_id = EQikItemSlot1;
                caption = "Hello World";
           },
          QIK_SLOT_CONTENT_DIRECT
          {
               slot_id = EQikIconSlot1;

               type = EEikCtImage;
               control = QIK_IMAGE
               {
                     content = QIK_CONTENT_MBM
                     {
                         bmpfile = //private//E1000001//QHelloWorld.mbm;
                         bmpid = EMbmQhelloworldGoldstar;
                         bmpmask = EMbmQhelloworldGoldstar_mask;
                     };
               };
          },
         QIK_SLOT_CONTENT_DIRECT
         {
              slot_id = EQikItemSlot2;
              type = EEikCtEdwin;
              control = EDWIN
             {
             };
         }
    };
}

    重新编译后执行,就可以看到结果:
1. building block被高亮显示 (如下图a),高亮部分包含了2行,这也是为什么不使用两个简单的 IconOnelineBuildingBlocks 的原因,虽然外观是一样的,但是行为不同,因为那样高亮部分只包含一行. 高亮行为是自动添加的,现在可以与 building block交互,或者与控件交互.

2. 有一行虚线,看上去象是编辑窗口,但是 它不是,因为那里没有显示允许用户输入的光标.(a).
3. 即使没有光标,文本输入也是可能的,因为右侧有一个三角,说明系统可以识别手写(a).
4. 自动拥有一个软键 — 唯一的一个,因为我们并没有添加(a).
5. 可以通过下列方式,激活 building block:
• 选择适当的软键 (确认键)
• 输入一些文本,与输入系统独立 (例如:键盘,手写识别,预测文本输入, ...)
• 点击 building block
     可以点击building block可能会比较令人意外, 即使我们正在运行的 UI配置没有包含触摸屏.就象前面提到的(参看 2.2节) , UI配置中的“touch”参数 并不代表没有数字转换器. 显然模拟器上有一个 活动的数字转换器,因此可以直接与屏幕直接交互(b).
6.一旦激活 building block 就可以与它交互了.现在我们真正看到了 edwin. 可以输入文本,并且可以移动光标,也可以关闭它(c).

7. 可以看到一个自动弹出的building block标题 (c).
8. 如果 edwin里没有文本,软键就非常简单,唯一能做的就是就是关闭 edwin.也可以通过点击浮动栏 的外面来关闭 (c).
9. 输入文本后,自动产生了更多的指令(d).
10. 关闭浮动栏后,文本出现在building block中,替换了原来的虚线(e).
11. 如果输入过多的文本,末尾将会被截断.现在应该明白为什么这个 building block 叫做 OnelineBuildingBlock. 因为它只能显示一行文本. TwolineBuildingBlock 允许换行一次,然后才截断. (f).



未命名.JPG

3.4.7 其它的布局
      其它控件的创建方法类似.建议使用不同的 building blocks体会一下.例如:

• 复制已有的building block
• 从复制结果中删除图标
• 将building block 类型从 EQikCtIconCaptionedOnelineBuildingBlock 改为 EQikCtCaptionedHalflineBuildingBlock
• 为building block 添加一个标志以便拥有一个分割条.
修改后的内容如下:

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_SCROLLABLE_CONTAINER_SETTINGS r_qhelloworld_page_content
{
        controls =
        {
             QIK_CONTAINER_ITEM_CD_LI
             {...},
             QIK_CONTAINER_ITEM_CD_LI
             {
                  type = EQikCtCaptionedHalflineBuildingBlock;
                  control =
                  {
                         QIK_SYSTEM_BUILDING_BLOCK
                         {
                                  flags = EQikBuildingBlockDividerBelow;
                                  content =
                                 {
                                        QIK_SLOT_CONTENT
                                       {
                                             slot_id = EQikItemSlot1;
                                             caption = "Hello World!";
                                       },
                                       QIK_SLOT_CONTENT_DIRECT
                                       {
                                             slot_id = EQikItemSlot2;
                                             type = EEikCtEdwin;
                                             control = EDWIN
                                            {
                                            };
                                        }
                                 };
                           }
                    };
             }
     };
}


      结果如下图,我们不需要担心焦点处理,它已经自动实现了. 第二个 building block与第一个有明显的区别.示例说明: 在一个视图中使用混合building blocks时,应当保持视图清晰,并容易使用.在以后的白皮书中,我们将会详细介绍什么时候应该/不应该使用混合的 building blocks.

UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.jpg

     现在第二个 building block 已经显示了,并且仍然可以看到红色的矩形.显然,控件只重绘了它需要绘制的部分.并且高亮是半透明的,因此我们可以看见矩形.
     在UIQ 2.x 中控件绘制时,通常用背景色清除矩形.不应该再继续这样实现,因为背景可能不是单色.另外,这使得不同的控件很好地混合,就象示例那样.一般,控件仅需要删除 ClearRect() 函数 .应用程序框架保证重绘正确处理,并且更新背景.

3.5 使用新的布局
     除了上节已经介绍的控件创建,交互之外,控件指令的添加也是自动处理的.但是,我们怎么在程序中访问从资源文件中创建的控件呢?我们需要考虑两种情况:
1. 获取控件以便设置或获取一些值
2. 控件发生改变时获取通知消息(以便能够检查发生了什么改变)

3.5.1 访问控件
      前面没有讨论控件的标识,如果我们有多个同类型的控件,该怎么办? 因此,需要给每个控件一个识别符.在源代码中,使用相同的标识符.为了保证正确使用, 可以创建一个通用的头文件,即
hrh文件.在示例中创建文件 QHelloWorld.hrh,里面包含了一些用于标识控件的枚举值.我们并对所有的控件定义标识,只标识我们需要的;我们将只使用如下unique_handle 来标识两个 edwinith.

QUOTE:

// QHelloWorld.hrh
enum TQHelloWorldControls
{
EFirstEdwin = 1,
ESecondEdwin
};
然后就可以使用这些枚举值来标识控件:

QUOTE:

// QHelloWorld.rss
#include "QHelloWorld.hrh"
QIK_SLOT_CONTENT_DIRECT
{
      unique_handle = EFirstEdwin;
      slot_id = EQikItemSlot2;
      type = EEikCtEdwin;
      control = EDWIN
      {
      };
}
[...]
QIK_SLOT_CONTENT_DIRECT
{
        unique_handle = ESecondEdwin;
        slot_id = EQikItemSlot2;
        type = EEikCtEdwin;
       control = EDWIN
       {
       };
}


      通常在激活视图的时候显示视图内容,因此 ViewActivatedL()函数是最适合查找控件的位置.对 LocateControlByUniqueHandle()的调用将遍历视图的控件,然后找到我们需要的控件:

QUOTE:

// QHelloWorldView.cpp
#include <EikEdwin.h>
#include "QHelloWorld.hrh"
void CQHelloWorldView::ViewActivatedL(const TVwsViewId&, TUid, const TDesC8&)
{
        _LIT(KFirstEdwinText, "First");
        _LIT(KSecondEdwinText, "Second");
        CEikEdwin* firstEdwin = LocateControlByUniqueHandle<CEikEdwin>(EFirstEdwin);
        firstEdwin->SetTextL(&KFirstEdwinText);
       CEikEdwin* secondEdwin = LocateControlByUniqueHandle<CEikEdwin>(ESecondEdwin);
       secondEdwin->SetTextL(&KSecondEdwinText);
}


      上述代码查找两个edwin控件,然后为它们设置文本.然而代码中有2个问题需要小心.因为,我们不能保证控件确实存在.可能程序员想修改布局,不想再继续使用第二个edwin. 因此上述代码需要重写,以检查 LocateControlByUniqueHandle() 的返回值,以便只处理确实存在的控件.另一个问题是每次激活视图时都为它设置文本,因此,我们所做的修改可能会丢失,例如,离开程序,然后又切换回来.
      另一种情况是,当需要保存控件内容时,怎么访问控件,适合的位置是 SaveL() 函数,这个函数每次在需要保存视图内容十,由应用程序框架调用.例如:

QUOTE:

// QHelloWorldView.cpp
void CQHelloWorldView::SaveL()
{
      CEikEdwin* firstEdwin = LocateControlByUniqueHandle<CEikEdwin>(EFirstEdwin);
      HBufC* buffer = firstEdwin->GetTextInHBufL();
      User::InfoPrint(*buffer);
      delete buffer;
}


3.5.2 获得修改通知消息

      每次控件内容发生改变时,控件向它的 MCoeControlObserver发送事件消息.该事件然后被传送到视图,因此需要做的就是重载视图的 HandleControlEventL() 函数.通常唯一比较感兴趣的是 EEventStateChanged,通常使用视图的默认行为就足够了,因此确保给它传递所有的事件.

QUOTE:

// QHelloWorldView.cpp
void CQHelloWorldView::HandleControlEventL(CCoeControl* aControl, TCoeEvent aEventType)
{
     _LIT(KInfo,"Control changed");
     if (aEventType == EEventStateChanged)
    {
         iEikonEnv->InfoMsg(KInfo());
     }
      CQikViewBase::HandleControlEventL(aControl, aEventType);
}
      大多数情况,控件发生变化后,不需要立刻知道.因此推荐使用上面介绍的 SaveL()函数.在 UIQ 2.x 中,常常实现HandleControlEventL()函数,因为焦点处理是手工处理的.现在,UIQ3的焦点处理是自动的,焦点策略由布局管理器决定.如果没有安装布局管理器,焦点移动到给定方向的最近一个控件上. (参看[9] )
在这个函数里,可以使用定义的唯一识别符 ,判断是哪个控件发生了变化.

QUOTE:

switch(aControl->UniqueHandle())
{
    case EFirstEdwin:
    // Do something with the first edwin
    break;
    case ESecondEdwin:
    // Do something with the second edwin
    break;
   default:
   // all other controls (not necessarily identifiable)
   break;
}


四 、视图指令
4.1 简介
      本节为视图添加一些指令.前面已经看到一些自动添加的指令,因此我们只需要关注其它的指令.资源文件结构的图中已经告诉我们应该在哪里添加指令,我们首先来介绍第一个,其它的以后介绍.本章,我们将讨论:
1. 添加指令

2. 初始化指令,
3. 处理指令
4. 改变指令状态


4.2 目录结构


UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.jpg


4.3 创建指令
      QikCommand.rh头文件定义了指令列表和指令的资源结构. 因此我们只需使用它们创建指令列表,并添加到视图结构即可.首先,添加一个关闭指令,用于在测试环境下检查内存泄露
.关闭指令的标准 ID EEikCmdExit定义在uikon.hrh文件中,同时需要设置状态标志为 EQikCmdFlagDebugOnly,以便该指令仅在debug编译版本中可见 .
      为指令定义文本时,注意文本不能太长,因为指令可能出现在软键上,空间非常有限.在示例中,我们使用 “Close (debug)”文本,但是,在软键上,显然太长了,因此另外定义一个短的文本 “Close”,在软键上出现时,将使用短文本.

QUOTE:

// QHelloWorld.rss
#include <QikCommand.rh>
#include <uikon.hrh>
RESOURCE QIK_VIEW_CONFIGURATIONS r_qhelloworld_view_config
{
   configurations =
  {
       QIK_VIEW_CONFIGURATION
      {
             ui_config_mode = KQikSoftkeyStylePortrait;
             view = r_qhelloworld_view;
             command_list = r_qhelloworld_commands;
       }
   };
}


RESOURCE QIK_COMMAND_LIST r_qhelloworld_commands
{
        items =
        {
                QIK_COMMAND
               {
                      id = EEikCmdExit;
                      type = EQikCommandTypeScreen;
                      // Indicate that this command will only be visible in debug
                      stateFlags = EQikCmdFlagDebugOnly;
                       text = "Close (debug)";
                     shortText = "Close";
               }
        };
}


       应用程序框架知道如何处理指令,因此,不需要添加任何处理代码. 因为我们创建的所有的代码归应用程序框架拥有,而且我们不希望出现内存泄露,因此程序应当能够无异常地关闭.如果创建了控件,但是没有删除它,程序就不能无异常地关闭,而是产生内存泄露异常,如下图:

UIQ3_Whitepaper_02_View_Commands_ResourceFilesLayout.jpg

       现在添加其它指令,这些指令将由我们的程序处理,因此需要唯一地标识.指令标识不能与系统已定义的冲突(如前面提到的关闭指令标识),因此我们使用 0x1000 作为我们的第一个 ID.

QUOTE:

// QHelloWorld.hrh
enum TQHelloWorldCommands
{
EQHelloWorldCommand1 = 0x1000,
EQHelloWorldCommand2,
EQHelloWorldCommand3
};

       添加的新指令中,有的用于描述可用的特征 (参看[37, 2]).第二个指令用于介绍 cpfFlags,该标志会影响到指令的位置.

QUOTE:

// QHelloWorld.rss
RESOURCE QIK_COMMAND_LIST r_qhelloworld_commands
{
     items =
    {
           QIK_COMMAND
          {
                 id = EEikCmdExit;
                 type = EQikCommandTypeScreen;
                 // Indicate that this command will only be visible in debug
                 stateFlags = EQikCmdFlagDebugOnly;
                 text = "Close (debug)";
                 shortText = "Close";
          },
          QIK_COMMAND
          {
                 id = EQHelloWorldCommand1;
                 type = EQikCommandTypeScreen;
                 text = "Command1";
                 shortText = "Cmd1";
          }
          QIK_COMMAND
          {
                id = EQHelloWorldCommand2;
                type = EQikCommandTypeScreen;
                text = "Command2";
                stateFlags = EQikCmdFlagInvisible;
                cpfFlags = EQikCpfFlagDuplicateInMenuPane |
                EQikCpfFlagPreferToBePlacedInButtonbar;
                shortText = "Cmd2";
          },
          QIK_COMMAND
          {
                id = EQHelloWorldCommand3;
                type = EQikCommandTypeScreen;

                text = "Command3";
                shortText = "Cmd3";
                stateFlags = EQikCmdFlagCheckBox;
                icon = r_qhelloworld_command_icon;
          }
     };
}

RESOURCE QIK_CONTENT_MBM r_qhelloworld_command_icon
{
        bmpfile = //private//E1000001//QHelloWorld.mbm;
        bmpid = EMbmQhelloworldGoldstar;
   &
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值