在我看来,理解了传统的
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即可。
阅读(504) | 评论(0) | 转发(0) |