Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3507103
  • 博文数量: 1450
  • 博客积分: 11163
  • 博客等级: 上将
  • 技术积分: 11101
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-25 14:40
文章分类

全部博文(1450)

文章存档

2017年(5)

2014年(2)

2013年(3)

2012年(35)

2011年(39)

2010年(88)

2009年(395)

2008年(382)

2007年(241)

2006年(246)

2005年(14)

分类: C/C++

2008-11-08 15:29:38

在我看来,理解了传统的 Symbian OS程序架构可以轻松的帮助我们理解Avkon视图切换架构,因为我们可以事先理解一些看起来比较抽象的概念,比如容器,窗口,复合控件等,了解一下最基 本的Symbian程序框架。实际上,Avkon视图切换架构无非就是在传统的Symbian程序架构上做的一种扩展而已。最大的区别就是多了一个视图 类,即继承自CAvkonView类的自定义View。
    在SDK里,有一个Avkon视图切换的例子MultiViews,我看了一下,这个例子比较非常简单,框架非常清晰,很适合初学者,不过我感觉仅仅研究 这个例子的实际意义并不大,因此本文并不针对这个例子,而是我们通过向导创建S60程序时,向导为我们提供的框架即S60 View based application。这个框架提供的架构要比例子MultiViews稍微复杂一点,因为多了一个新的概念,即将面板应用到了程序中,我将这个演示程 序取名为MultiViewsTest,因为仅为了演示学习用,所以我基本上没有对框架提供的代码做修改。
    通过这个例子MultiViewsTest学习Avkon视图切换架构,重点无非放在MultiViewsTestContainer.cpp、 MultiViewsTestView.cpp、MultiViewsTestAppUi.cpp这三个源程序文件,以及它们中定义的类的成员函数上,当 然因为有了面板(StatusPane),我们也会在这里对面板的相关知识作一小记。
    在做这个例子的时候,我采取的办法是,先删除上面三个源程序文件的内容,然后自己根据它们对应的头文件,自己编写函数代码,自己确定需要引入的头文件,因 为我认为光看代码不去亲自调试对新手来说,是不会有太多提高的。在我写代码的过程中,因为不仔细、记错了或不理解等各种原因,出现了不少错误,因此,我也 会在这里将这些错误记录下来,并做一下错误分析,希望各位同仁不再犯我所经历过的错误。
一、
    在这三个文件里,MultiViewsTestContainer.cpp和传统的Symbian程序架构中的Container文件可以说完全相同,这个容器中也是只有两个Label子控件,因此我们就从这个最简单的开始。
    在文件MultiViewsTestContainer.cpp中我们定义了它所对应的头文件MultiViewsTestContainer.h中定义 的容器类CmultiViewsTestContainer的成员函数的具体实现。在编代码的时候,出了几个小错误,摘抄如下:
void CMultiViewsTestContainer::ConstructL(const TRect& aRect)
{
     CreateWindowL();//忘了"L",嘿嘿

     iLabel = new(ELeave)CEikLabel;
     iLabel->SetContainerWindowL(*this);
     iLabel->SetTextL(_L("Container_Label_1"));

     iToDoLabel = new(ELeave)CEikLabel;
     iToDoLabel->SetContainerWindowL(*this);
     iToDoLabel->SetTextL(_L("Container_Label_2"));

     SetRect(aRect);//设置窗口范围
     ActivateL();//忘记了加"L",嘿嘿
}
――――――――――――――――――――――――――――――――――――――――――――――
//设置标签的显示位置和大小
void CMultiViewsTestContainer::SizeChanged()
{
     iLabel->SetExtent(TPoint(10,10),iLabel->MinimumSize());//这里的SetExtent()方法,没有L
     iToDoLabel->SetExtent(TPoint(10,100),iToDoLabel->MinimumSize());
}
――――――――――――――――――――――――――――――――――――――――――――――
void CMultiViewsTestContainer::Draw(const TRect& aRect) const
{
     CWindowGc& gc=SystemGc();//SymtemGc()是类CCoeControl的方法,获取图形上下文
     gc.SetBrushColor(KRgbRed);
     gc.SetBrushStyle( CGraphicsContext::ESolidBrush );
     gc.DrawRect( aRect );
}
――――――――――――――――――――――――――――――――――――――――――――――

在编这个文件的时候,没有碰到大的障碍,因为这里的这个容器类和传统架构中的容器类是一摸一样的,呵呵,但是还是出了几个小错误。
1、 Symbian中编码和其他C++编码不太一样,对于可能产生异常的函数,名字都会以“L”来结尾,因此往往会搞混的就是,这些常用函数中,哪个有L,哪个没有L,在上面我就搞混了,因此对于这些常用的函数,我们应该熟记。
再写一遍:
CreateWindowL(); ActivateL(); SetExtent();
2、 忘了绘图函数Draw()中,定义图形上下文的类,及产生图形上下文实例的方法:
CwindowGc& = SystemGc();
当然还不能忘了该函数的最后一步,就是绘制区域(注意绘制的不一定非要是整个窗口,通过参数aRect具体确定):gc.DrawRect(aRect);

二、自定义的继承自CAknView类的视图类CmultiViewsTestView
这个类我们在MultiViewsTestView.cpp中定义具体实现,这个视图类实际上起到在Container和AppUi之间的一个桥梁作用, AppUi不在操作Container,而是通过View间接操作。当然这个Container要作为View类的一个private成员,即:
private:
    CmultiViewsTestContainer* iContainer;

当然,关于iContainer所指向的容器实例的创建和释放也要在这个View类中来完成。关键就是在View类的哪个函数中编写创建和释放的具体代码呢?这就是在
void CMultiViewsTestView::DoActivateL(const TVwsViewId& aPrevViewId,TUid aCustomMessageId,
const TDesC8& aCustomMessage)编写创建代码,在
void CMultiViewsTestView::DoDeactivate()中编写释放代码。

一定要注意,不要在View类的ConstructL()方法中创建iContainer,但可以在View类的析构函数中释放iContainer(只 不过这和DoDeactivate()方法中的代码重复了)。那么可能有人会问,那么构造函数和析构函数中定义什么呢?它们中的代码定义如下:
#include < MultiViewsTest.rsg >
void CMultiViewsTestView::ConstructL()
{
     BaseConstructL(R_MULTIVIEWSTEST_VIEW1);//将资源文件中定义的视图资源传入
}
――――――――――――――――――――――――――――――――――――――――――――――
CMultiViewsTestView::~CMultiViewsTestView()
{
     if(iContainer!=NULL)
     {
     //不是RemoveFromStackL(iContainer),而是AppUi()->RemoveFromViewStack( *this, iContainer )
     //IMPORT_C CAknViewAppUi* CAknView::AppUi() const [protected]
          AppUi()->RemoveFromViewStack( *this, iContainer );
         delete iContainer;
          iContainer=NULL;
     }
}
分析:
1、     ConstructL()方法中仅仅创建了一个View的基本框架,通过方法BaseConstructL (R_MULTIVIEWSTEST_VIEW1),其中参数R_MULTIVIEWSTEST_VIEW1是在.rss资源文件中定义的一个View资 源名。注意:为此我们需要将资源文件#include,但我们引入的不能是MultiViewsTest.rss,而是 MultiViewsTest.rsg,在我们编译程序的时候,编译器会将.rss编译出一个资源索引文件.rsg,并把该.rsg文件放在系统的头文件 include目录中,也可能放在项目的Group目录中。
2、     可以看到析构函数中,我们释放了iContainer,实际上这是和DoDeactivate()函数中的定义重复的。这句代码AppUi()- >RemoveFromViewStack( *this, iContainer );其中AppUi()是继承自类CAknView中的成员函数,可以获得这个View所对应的ViewAppUi的指针(即程序中的AppUi类的对象 指针),RemoveFromViewStack( *this, iContainer );从View的Stack中将这个iContainer移除。

再看DoActivateL()和DoDeActivate()的代码:
DoActivateL():构造容器,并绘制窗口

void CMultiViewsTestView::DoActivateL(const TVwsViewId& aPrevViewId,TUid aCustomMessageId,
            const TDesC8& aCustomMessage)
{
     iContainer = new(ELeave)CMultiViewsTestContainer;
     iContainer->SetMopParent(this);
     iContainer->ConstructL(ClientRect());//创建并显示容器内容
     AppUi()->AddToStackL(*this,iContainer);//将容器推入栈顶
}
―――――――――――――――――――――――――――――――――――――――――――――
DoDeActivate():销毁容器对象,跟View类的析构函数功能类似。

void CMultiViewsTestView::DoDeactivate()
{
     if(iContainer!=NULL)
     {
          //RemoveFromStackL(iContainer); //AppUi()->RemoveFromViewStack( *this, iContainer );
          AppUi()->RemoveFromViewStack( *this, iContainer );
         delete iContainer;
          iContainer=NULL;
     }
}
分析:
1、 在DoActivate()方法中,主要是创建容器iContainer和将iContainer推入栈顶,以便接收用户事件。创建之所以比较复杂,是因为我没有在Container类中定义NewL()和NewLC()方法。所以显得稍微复杂。
2、 可以看到DoDeactivate()方法中的代码和View类的析构函数代码是一样的。

问题:有点疑问就是容器入栈的时候用的是AddToStackL()方法,而出栈的时候却是RemoveFromViewStack()方法,为什么不匹配呢?
据xiaobai网友验证,改为AppUi()->RemoveFromStack()后,程序也没有错误,我的理解是:View的Stack和程序的Stack是相通的,只是个人的猜想,不正确的话,还请高手指教,谢谢。
除了上面的四个方法,我们继续看View类所特有的其他方法:

TUid CMultiViewsTestView::Id() const
{
     return KViewId;
}
在这里KviewId是我们事先定义的这个View的UID,即const TUid KViewId = {1};格式是大括号“{ }”包含的1,而不是直接用1初始化。
View类必须包含一个Id()函数,从而系统可以标志这个类。

―――――――――――――――――――――――――――――――――――――――
视图View可以有自己的菜单资源,当然这也需要在.rss文件中进行定义,格式如下:

RESOURCE AVKON_VIEW r_multiviewstest_view1
    {
    hotkeys = r_multiviewstest_hotkeys;
    menubar = r_multiviewstest_menubar_view1;
    cba     = R_AVKON_SOFTKEYS_SELECTION_LIST;   
    }

RESOURCE MENU_BAR r_multiviewstest_menubar_view1
    {
    titles =
        {
        MENU_TITLE { menu_pane = r_multiviewstest_app_menu; txt = "App"; },
        MENU_TITLE { menu_pane = r_multiviewstest_view1_menu; txt = "View"; }
        };
    }

RESOURCE MENU_PANE r_multiviewstest_view1_menu
    {
    items =
        {
        MENU_ITEM { command = EMultiViewsTestCmdAppTest; txt = qtn_view1_option_item; }
        };
}

RESOURCE MENU_PANE r_multiviewstest_app_menu
    {
    items =
        {
        MENU_ITEM { command = EMultiViewsTestCmdAppTest; txt = qtn_appl_option_item; },
        MENU_ITEM { command = EAknCmdExit; txt = qtn_appl_exit; }
        };
}
――――――――――――――――――――――――――――――――――――――――――――――
下面是View中的HandCommandL()方法和AppUi中的HandCommandL()方法:
void CMultiViewsTestAppUi::HandleCommandL(TInt aCommand)
{
     switch(aCommand)
     {
         case EEikCmdExit:
              Exit();
              break;
         case EMultiViewsTestCmdAppTest:
              iEikonEnv->InfoMsg(_L("test"));
            break;
          default:
              break;
     }
}

void CMultiViewsTestView::HandleCommandL(TInt aCommand)
{
     switch ( aCommand )
    {
        case EAknSoftkeyOk:
            {
            iEikonEnv->InfoMsg( _L("view1 ok") );
            break;
            }
        case EAknSoftkeyBack:
            {
            AppUi()->HandleCommandL(EEikCmdExit);
            break;
            }
        default:
            {
            AppUi()->HandleCommandL( aCommand );
            break;
            }
     }
}
在上面的代码中,出现了4个菜单命令事件EEikCmdExit、EMultiViewsTestCmdAppTest、EAknSoftkeyOk、EAknSoftkeyBack。
那么其中哪些是自定义的,哪些又是系统定义的呢?
这里只有命令EmultiViewsTestCmdAppTest是自己在.hrh文件中定义的。如下:
enum TMultiViewsTestCommandIds
{
    EMultiViewsTestCmdAppTest = 1
};

通过查看查看View的HandCommandL()方法会发现,它只处理自己的一个菜单命令EaknSoftkeyOk,而其他不属于自己视图所有的菜 单的命令去调用AppUi里的HandCommandL()方法。这样做的好处是,实现了代码的公用,也就是说,如果有多个视图,并且多个视图都有相同的 命令的话,这时候,我们就可以将菜单命令分为两类:一类是各个View所特有的菜单命令,另一类是每个View都公有的。View特有的命令在自己类的 HandCommandL()中定义执行操作,而公有的菜单命令,则可以放到AppUi的HandCommandL()里面去定义执行操作。
―――――――――――――――――――――――――――――――――――――――
在View类中还有一些其他的成员函数:如:
//用户区大小改变时响应
void CMultiViewsTestView::HandleClientRectChange()
{
     if(iContainer) //即iContainer!=NULL
     {
          iContainer->SetRect(ClientRect());
     }
}
这个函数应该是一个回调函数,用在用户区域大小改变时,不过,我没有在SDK中找到这个函数,希望知道的朋友告知这个函数是在哪里定义的。
当然向这样类似功能的函数肯定还有不少,根据我们的程序不同,所采用的肯定不同,需要我们日后多多积累。在这里只是想介绍简单的View程序架构,所以向这样功能的函数,也就不作过多的解释了。
―――――――――――――――――――――――――――――――――――――――

三、View架构的AppUi
好了,现在来看稍微有点麻烦的AppUi类,即CMultiViewsTestAppUi。实际上,如果本例中不采用面板StatusPane的话,这个 AppUi还是比较简单的,因此,这里所谓的麻烦,主要还是关于StatusPane的,通过这个小例子,在理解View程序架构的同时,我们还可以对 StatusPane有个简单的认识,呵呵。

先需要注意的一点不同就是,程序中用到的View类,不需要作为AppUi类的私有成员,这个和传统架构有点不同,Container需要作为AppUi 的私有成员,在这里是Container作为了View的私有成员,需要注意,我们只需在AppUi的ConstructL()中,创建View,并把它 们添加到View服务器中,然后设置一个默认显示的视图即可:
void CMultiViewsTestAppUi::ConstructL()
{
     BaseConstructL();
     
     //创建一个状态面板指针,一个创建另一个
     CEikStatusPane* sp = StatusPane(); //CEikStatusPane类名忘了e,嘿嘿

     iNaviPane = (CAknNavigationControlContainer*)sp->ControlL(
        TUid::Uid(EEikStatusPaneUidNavi));

     iDecoratedTabGroup = iNaviPane->ResourceDecorator();

     if (iDecoratedTabGroup)
    {
          iTabGroup = (CAknTabGroup*) iDecoratedTabGroup->DecoratedControl();
          iTabGroup->SetObserver( this );
     }
     
//将CMultiViewsTestView对象的二阶段构造代码放在UI里面了,最好在CMultiViewsTestView类里。
     CMultiViewsTestView* view1 = new (ELeave) CMultiViewsTestView;
     CleanupStack::PushL(view1);
     view1->ConstructL();
     AddViewL(view1);
     CleanupStack::Pop(view1);//这个Pop()方法没有"L",但上面的PushL()方法有"L"

     CMultiViewsTestView2* view2 = new(ELeave) CMultiViewsTestView2;
     CleanupStack::PushL(view2);
     view2->ConstructL();
     AddViewL(view2);
     CleanupStack::Pop(view2);
     
     SetDefaultViewL(*view1);//SetDefaultViewL()方法忘了"L",嘿嘿
}
上面的红色部分,即为所需代码,剩余的是关于面板StatusPane的了。
―――――――――――――――――――――――――――――――――――――――
下面来看有关面板StatusPane的代码:
为了在程序中使用面板,我们需要在.rss文件中定义面板资源,相关定义如下:
RESOURCE STATUS_PANE_APP_MODEL r_multiviewstest_status_pane
    {
     panes =
         {
          SPANE_PANE
              {
              id = EEikStatusPaneUidNavi;
              type = EAknCtNaviPane;
              resource = r_multiviewstest_navi_decorator;
              }
         };
    }

//    r_multiviewstest_navi_decorator
RESOURCE NAVI_DECORATOR r_multiviewstest_navi_decorator
    {
    type = ENaviDecoratorControlTabGroup;
    control = TAB_GROUP
         {
          tab_width = EAknTabWidthWithTwoTabs; // two tabs
         active = 0;
         tabs = {
              TAB
              {
                id = EMultiViewsTestView1Tab; // from application hrh
                txt = qtn_view1_tab;
              },
              TAB
              {
                id = EMultiViewsTestView2Tab;
                txt = qtn_view2_tab;
              }
              };
         };
     }
这是面板资源的定义格式,其中两个面板的id,即EMultiViewsTestView1Tab和EMultiViewsTestView2Tab需要在.hrh文件中定义,即如下:
enum TMultiViewsTestTabViewId
{
    EMultiViewsTestView1Tab= 1,
    EMultiViewsTestView2Tab
};
――――――――――――――――――――――――――――――――――――――――――――――
有了上面的前提之后,我们就来看具体的AppUi中有关StatusPane的源程序文件:
先需要给AppUi类定义三个private成员,如下:
private:
    CAknNavigationControlContainer* iNaviPane;
    CAknTabGroup*                   iTabGroup;
CAknNavigationDecorator*        iDecoratedTabGroup;

为了在程序中使用StatusPane,我们需要定义上面三个对象指针,其中:
1、   CAknNavigationControlContainer:
2、   CAknTabGroup:面板的函数集
3、   CAknNavigationDecorator:
下面是AppUi类的ConstructL()中初始化这些指针的代码(这里只截取了部分ConstructL()和StatusPane有关的代码):
void CMultiViewsTestAppUi::ConstructL()
{
     CEikStatusPane* sp = StatusPane(); //CEikStatusPane类名忘了e,嘿嘿
     
     iNaviPane = (CAknNavigationControlContainer*)sp->ControlL(
        TUid::Uid(EEikStatusPaneUidNavi));

     iDecoratedTabGroup = iNaviPane->ResourceDecorator();

     if (iDecoratedTabGroup)
    {
          iTabGroup = (CAknTabGroup*) iDecoratedTabGroup->DecoratedControl();
          iTabGroup->SetObserver( this );
     }
}
从上面的代码,可以看出,为了使用状态面板,我们必须要用用到除面板类CEikStatusPane之外的一些其他类,(而不是简单的只定义一个 CEikStatusPane就可以了,需要注意,这里的CEikStatusPane并没有作为AppUi类的成员存在。),并且这几个指针的创建都是 顺序的,即先创建了sp之后,再依次创建其他指针,最终是为了创建iTabGroup。
―――――――――――――――――――――――――――――――――――――――
看一下为了使用StatusPane需要用到的AppUi的成员函数:
TKeyResponse CMultiViewsTestAppUi::HandleKeyEventL(
            const TKeyEvent& aKeyEvent,TEventCode aType)
{
     if(iTabGroup == NULL)
     {
         return EKeyWasNotConsumed;
     }
     //如果按下的是左方向键或右方向键
     if(aKeyEvent.iCode==EKeyLeftArrow || aKeyEvent.iCode == EKeyRightArrow)
     {
         return iTabGroup->OfferKeyEventL(aKeyEvent, aType);
     }
     else
     {
         return EKeyWasNotConsumed;
     }
}

//这是一个回调函数,定义在接口MAknTabObserver中,需要继承
void CMultiViewsTestAppUi::TabChangedL(TInt aIndex)
{
     ActivateLocalViewL(TUid::Uid(iTabGroup->TabIdFromIndex(aIndex)));//激活视图
}
―――――――――――――――――――――――――――――――――――――――
最后就是AppUi类的析构函数,因为程序中的View,并不是该类的成员,所以析构函数中,无需销毁视图类,只需销毁该类中为类成员分配了堆内存的内存即可。所以代码如下:
CMultiViewsTestAppUi::~CMultiViewsTestAppUi()
{
     delete iDecoratedTabGroup;
}
查看析构函数,非常简单,仅仅释放了iDecoratedTabGroup。那么视图对象在什么地方销毁的呢?如果不通过我们自己销毁的话,应该就是由视 图服务器在程序退出的时候自动销毁的。还有就是StatusPane等对象,为什么也没有在析构函数中删除,我的理解是这些对象都是由框架进行管理的,所 以不需要我们自己显示销毁,在程序关闭时,会由程序框架将它们自动销毁。

―――――――――――――――――――――――――――――――――――――――
小结:程序可能存在着多个View,但是一次只能显示一个,因此它们之间切换显示的方法比较重要,也是经常使用的,采用的函数就是CAknViewAppUi中定义的
IMPORT_C void CAknViewAppUi::ActivateLocalViewL  (  TUid  aViewId  )   

通过传递View的UID实现切换。

备注:这个例子程序中由于使用了StatusPane,所以略微显得累赘,如果仅想了解View切换架构,还是建议看SDK自带的MultiViews这个例子。
应该说StatusPane也是程序中经常用到的,它位于屏幕的上层,因为应用程序窗口的标准面板由状态面板、主面板和软键面板组成,而主面板就是客户矩 形显示的位置,也就是除去状态面板和软键面板后剩余的区域。状态面板StatusPane又分为信号面板、上下文面板、标题面板、导航面板、电池面板等子 面板,因此,要想操作这些子面板需要事先通过StatusPane获得这些子面板的指针,通过sp->ControlL()方法即可。
本例的程序代码,朋友可以在根据S60SDK2.1的向导自动生成,程序名取MultiViewsTest即可。
阅读(495) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~