[OPTIONS MENUS]
Avkon菜单项是从 menu bar和RSS文件中指定的menu pane resources生成的。我们可以通过windows下模拟器按F1来启动或通过options自定义功能键来启动,这是要使用 EAKnSoftkeyOptions Id来实现的。如果应用程序要切换到pre-existing'options应该使用R_AVKON_SOFTKEYS_OPTIONS_BACK这个 CBA资源。
每个菜单的选项都是在MENU_PANE资源结构中定义的,使用在system、application、view和context。
譬如下列菜单资源:
RESOURCE MENU_PANE r_system_menu
{
items =
{
MENU_ITEM {command = ECmdCut; txt = "Cut";},
MENU_ITEM {command = ECmdCopy; txt = "Copy";},
MENU_ITEM {command = ECmdPaste; txt = "Paste";},
};
}
子菜单和有由'cascade'参数指定,下面是一个列子
MENU_ITEM {command = ESystemOptions; txt = "System Options";
cascade = r_system_options_menu;}
顾名思义,这个就指定是了上面r_system_options_menu的子菜单。
COMBINING MENU SECTIONS
菜单sections由MENU_BAR资源来整合。按照从下到上的顺序。
一个典型的列子是
RESOURCE MENU_BAR r_menuapp_menu
{
titles =
{
MENU_TITLE { txt = "System"; menu_pane = r_system_menu;},
MENU_TITLE { txt = "App"; menu_pane = r_app_menu;},
MENU_TITLE { txt = "View"; menu_pane =
r_view1_options_menu;},
MENU_TITLE { txt = "Context"; menu_pane = r_context1_menu;},
};
}
这样就把所有的应用程序菜单列出来了。
注意txt选项只是为了识别用的,根本不显示。但还是要寸在资源文件中。
缺省的菜单栏使用EIK_APP_INFO资源,当启动时载入。如果使用的是view体系结构,那可以在view资源结构中设置菜单。
CHANGING MENU SECTIONS
在程序运行过程中我们可以随意改变菜单项目,只需要调用:
iEikonEnv->AppUiFactory()->MenuBar()->SetMenuTitleResourceId(MENU_BAR_RESOURCE_ID)
每次需要改变内容时就这么做,所以你要事先把所有可能用到的菜单资源都准备好:
)
注意,如果使用的是view系统结构,那就用使用view自己的菜单系统,应如下操作
iMyView->MenuBar()->SetMenuTitleResourceId(MENU_BAR_RESOURCE_ID)
CHANGING MENU ITEMS
还可以改变个别菜单项目,这使得我们可以随时随地的添加或者是删除菜单项。
应用程序UI应该重载下列虚函数:
void DynInitMenuPaneL(TInt aResourceId, CEikMenuPane* aMenuPane)
这个函数应该在什么情况下调用哪?当所有secitons已被加入到menu,menu pane object已经被建立后。
应用程序UI还可以重载下面这个虚函数:
void DynInitMenuBarL(TInt aResourceId, CEikMenuBar* aMenuBar)
Avkon 滚动指示器在softky pane的中间,此外,softkey pane会因为"popped-up" controls的出现或消失而创建或注销掉。在使用滚动的controls和当前可见滚动指示器之间的通信是通过在系统中建立的CAknEnv,被 softky pane和CEikScrollbarFrame使用的系统来管理的。
这个机制是对编程者隐藏的,一般来说,我们不需要担心这个scrollers。
保证正确的scrolling控制
1,Using Listboxes with CAknPopupList
void CSimpleAppUi::CreatePopupSelectionL(TInt aItems, TBool aTile)
{
CEikTextListBox* list = new(ELeave)CEikTextListBox;
CleanupStack::PushL(list);
CAknPopupList* popupList = CAknPopupList::NewL(List,
R_AVKON_SOFTKEYS_OK_BACK); //softkey pane created here
CleanupStack::PushL(popupList);
list->ConstructL(popupList, CEikListBox::ELeftDownInViewRect);
//scroller referennce fetched from (now existing) softkey pane here:
list->CreateScrollBarFrameL(ETrue);
//Set the visibility of the scrollbars (This code may become redundant)
list->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff,
CEikScrollBarFrame::EAuto);
插入代码
popupList->ExecuteLD(); //Popup selection list is put up
插入代码
CleanupStack::Pop(); //popuplist
CleanupStack::PopAndDestroy(); //list
}
其他编写知识:
1.应用程序的退出和EEIKCMDEXIT在一个Avkon程序中,有两个方法可以帮助你退出程序,一是exit菜单选项,一个就是back功能键,这两种方法有略微的差别,下面我们就来讲讲。
任何程序都可以通过调用Exit()以回应EEikCmdExit来结束程序。类似的,一些控件如dialogs,menus,popup lists等可以以此来回复escape 键的响应以便退出。
What to do
应用程序和views通过HandleCommandL()函数来处理菜单和功能键传来的命令。HandleCommandL()必须能够处理命令ID EEikCmdExit,这个是退出当前程序的信号。
对待这种情况,典型的app UI处理应该调用SaveAnyChangesL()和ExitL()。注意,如果你是写的是view体系程序,那views或app UI都可以处理这些命令,但不能同时处理这些,否则会引起问题的。
当按下back功能键时HandleCommandL()函数还会接受到EAknSoftKey这个命令ID,怎么处理这个命令要看上下文环境。但如果确实要退出,我们就要象接受到EEikCmdExit一样对待它。
功能键back可能总是产生EAknSoftkey这个ID,而菜单选项exit总上会产生EAknCmdExit命令ID,注意这个是和EEikCmdExit不同的,它是不直接使用的一般。
How it works
Exit() 函数的调用将会导致当前程序的退出。如果程序是内嵌的,那控制将交到父窗口那里。在Avkon中,我们希望由back功能键来执行,但exit会导致所有窗口包括父窗口的关闭。关闭机制同样要求相关子窗口应用程序的关闭。这个是连锁反应的。如果要这样操作,可以发送给每个相关程序EEikCmdExit命令ID。因此程序在调用exit()时应该回应EEikCmdExit和EAknSoftKeyBack命令。Framework将会在检查到有 EAknCmdExit时触发进程。
这就是你使用EAknCmdExit命令ID在你资源中的用处,但应该在HandlCommandL()中回应EEikCmdExit命令ID。
Dialog Shutter
应用程序的dialogs和popup窗口能够通过使用dialog shutter来关闭。
EMBEDDED APPLICATIONS
离开和后退
正如上面所说的,exit和back有所不同,在出现内嵌程序时就表露出来了。
App UI pointers
当你内嵌一个程序时,所有的程序都共享一个CEikonEnv实例。但注意EikonEnv::EikAppUi这个成员函数总是返回最里面的那个应用程序 UI pointer。因此,如果你在写一个需要访问app UI的组件时,你必须决定你是否想访问最内层的app UI。
如果你一直要访问最内层的app UI,那就使用CEikonEnv::EikAppUi()。
怎么用那,就是在你的对象要被构造时,通过调用这个函数来得到app UI,然后将它存储在数据成员中以后日后使用。
如果你想访问根app UI,你应该通过循环list of container app UIs。
CEikAppUi* root = this;
while (root->ContainerAppUi())
root = root->ContainerAppUi();
INI FILES
缺省情况下Avkon并不处理INI文件。如果你的程序非要这样,那就会得到一个not supported的错误。如果你希望自己的程序支持INI文件,你必须重载OpenIniFileC()函数,
ieCEikApplication::OpenIniFileLC()。
下面是个例子:
CDictionaryStore* CClkApplication::OpenIniFileLC(RFs& aFs) const
{
return CEikApplication::OpenIniFileLC(aFs);
}
DOCUMENTS FILES
Avkon缺省中并不支持文档文件的产生(当CAknDocument做为程序文档的基类时)。如果你需
如果需要文档,那你必须要重载OpenFileL(TBool aDoOpen, const TDesC& aFilename, RFs& aFs)函数。
CFileStore* CTestDocument::OpenFileL(TBool aDoOpen, const TDesC& aFilename, RFs& aFs)
{
return CEikDocument::OpenFileL(aDoOpen, aFilename, aFs);
}
AIF FILES
每个程序都应该有个自己的信息文件(AIF文件),里面包括位图和相关的程序标题。如果我们的程序需要不同的bitmaps和语言,那就由这个文档的多种版本提供。其中就有两位的语言代码。
Avkon可根据需要为每种语言产生不同的标题(利用AIF),产生他们的资源结构定义在apcaptionfile.rh中。
例如:
#include "tstappcaption.loc"
#include
RESOURCE CAPTION_DATA
{
caption = tst_app_caption_string;
shortcaption = tst_app_short_caption_string;
}
标题资源文件应该被命名为_caption.rss,编译后的资源文件应该位于\system\apps\%appname%\appname_caption.rXX 这里XX是2位数字语言代码(在mmp文件中)
-----------------------------------------------------------------------
symbian系统对异常的处理要求很高,因为这个系统是专为小内存的应用模式所设计,因此在实际使用中很可能会产生系统资源分配不足,这就要求我们的程序设计遵守symbian的规则要求。
所有能够引起异常退出的函数在函数名的最后都应当有一个字母L,异常退出通过“异常退出函数”经过调用栈向回传播,直到被异常捕获模块捕获。这个模块一般由控制台类应用程序的主函数E32Main()实现,或者作为应用程序框架的一部分提供给图形用户界面(GUI)程序。
注意,我们并不需要提供什么捕捉模块,一般来说,系统会在恰当的地方提供,我们只需要在可能引起异常的函数后面加上L后缀即可。
类的NewL()和NewLC()实现很有意思,如果该对象是由成员变量指向的,那么可以使用NewL()来生成,如果是由一个自定义变量指向的,那么就要使用NewLC()这里C后缀指明了将杂堆上创建一个类的新实例,并且将其压入清除栈,如果出现内存不足则异常退出。(一般来说,后缀“C”表示该函数在返回前将指向已创建对象的指针压入清除栈。)
我们上面说过了,一般不用自己的异常捕获,如果真要写的话,就要用TRAP和TRAPD 宏,而正常情况下,我们是把异常传给激活调度器(Active Scheduler),但随之而来的有了新的问题,激活调度器是将调用的函数的自变量都删除的,但如果这个时候有个指向堆上分配的对象的指针,该怎么办那,如果贸然的将它删除,会造成内存的泄漏。于是,需要一些机制来保留这些指针,以便于它们所指向的内存在异常退出后可以得到释放。Symbian OS在清除栈中提供了解决这个问题的机制。
清除栈就是这样一种堆栈,它含有那些指向当异常发生时需要被释放的对象的指针。这句话很重要,我们也就理解了为什么那些自定义的指向分配的对象的指针要用到C类处理的道理。(注意:Symbian OS中C类对象总是在堆上进行分配,并且总是将CBase作为它们最基本的基类。)
而我们另个得知的由成员变量指向的对象指针不需要压入清除栈的原因是,在这些函数的析构函数中就已经有了删除对象的处理,如果这个时候要再压入清除栈,就会两次删除对象也就错了。
在 symbian的学习过程中,发现这个嵌入式的操作系统确实与我们普通编程大相径庭,它对异常的处理,对内存的谨慎处理都让人咋舌。我到现在对异常的大致模糊认识是(在还没有看一个完整源代码的基础上),首先对有可能出现异常退出的函数加上L后缀,这个时候留交给系统的异常捕捉去处理,或者你自己写出这个一个Trap harness,而具体的顺序是,发生异常时,并不是返回一个错误代码,而是直接异常退出,这个时候系统将调用User::Leave(),调用了它我们也就得以到该函数的异常捕捉模块中了。一般来说,我们自己没有什么必要来写这个Trap harness,Symbian的Framework会在恰当的位置提供的,我们要做的就是在可能引起异常退出的函数名后面加上后缀L来确保异常的处理。
一般引起异常的也就是分配空间不足时可能发生,比如new操作,我们该咋办那,symbian是这么解决的,用了一个参数来调用new,即new(ELeave),有了它,在分配内存时出错了,咱们就可以异常退出了。
譬如说
CSomeObject* myObject = new CSomeObject;
if (!myObject) User::Leave(KErrNoMemory);
就可以替换为
CSomeObject* myObject = new(ELeave) CSomeObject;
对于但个对象,我们可以这么做,但是对于复合对象那,如果一个对象其中有一个成员指针指向另一个对象,那我们在构造这个对象的时候如何来保证在子对象出错的情况下正确的异常退出,而没有内存泄漏那?
这里牵涉到双向构造的知识点,总的来说是在NewL和NewLC函数中给出的解决的,而这两个又有区别,如果是用一个成员变量来指向改对象,那么应该使用 NewL函数,如果是自定义的变量就应该是用NewLC函数,因为NewLC可将对象压入清除栈,以便出现内存不足的时候正确退出,也就是C后缀总表示将指向已创建对象的指针压入清除栈,而具体实现上,NewLC和NewL的区别也就是LC没有最后的CleanupStack::Pop()函数,而L有,它也就最后出了清除栈的概念。
TRAP主要看看SDK的,很有帮助,
TRAP(_r, _s) {TTrap __t; if (__t.Trap(_r) == 0) {_s;TTrap::UpTrap();}}
我们是在一个异常捕捉里执行_s的,这个是一个c++段.。_r必须是一个TInt类型的值,我看过了这个是singed int的定义。也就是int。32位的。而且是要先声明。在宏外,相反,TRAPD就不用了,它在内部声明了,其他一样。如果有任何的c++段发生异常退出了,那退出的返回值就赋给了_r(事实上,是从User::Leave()的返回值)正如前面讲的,发生异常时,系统调用的是User::Leave,我们看出来了,异常发生时的顺序也就是从语言到User::Leave到Trap) ,否则它就是KErrNone这个值。
这个时候,一般系统为c++段准备一个清除栈,任何在_s中的语言发生异常时,在清除栈中的对象都会被清除掉。
Trap harness虽然很好,但是不应该大量使用它,从可执行代码的规模和执行速度来看,这个东西的成本很高,如果使用不当的话,很可能导致遗漏错误。一般来说,当在方法名的最后部分加上L,从而允许异常退出,不是更好的选择时我们才用到Trap.
在检查内存是否有泄漏时,我们最好用_ASSERT_DUBUG宏来进行测试,它能够防止很多问题,可以不受限制的检测函数中的参数、指针等。不过只是在debug模式下。
CMyClass::Function(CTing* aThing)
{
_ASSERT_DEBUG(aThing, Panic(EMyAppNullPointerInFunction));
}
这个语句的意思就是在aThing为false时,我们调用EMyAppNullPointerInFunction,这个是指针为NULL时应该做的:)
传统的symbian程序是继承自CCoeControl的自定义视图控件编写的,这些自定义的控件都是存放在控件栈中的,根据程序的需要而创建、清除或隐藏。
另外还有种视图结构,这种结构与传统的结构有最大的不同就是,它没有使用系统的视图管理系统,当然这个也可以算是它的优点,因为毕竟View Architecture有很多的限制。
就技术实现来说,视图切换是通过创建、清除和更改主视图控件的可视性来实现的。具体来说我们在控件栈中存放大量的控件,从而将按键事件定位到各个控件上。对话框结构,总的来说就是在主视图上就是一个对话框,起主要作用的视图模型都是一些对话框。这个与传统结构相比的好处就在于,我们不需要重新编译c++代码,而只需要修改资源文件就可以改变布局了。
但是要注意如果说不小心使用,嵌套的对话框就会占用相当多的栈空间。
注意一个新的特点就是,在Avkon具有内置于多页面对话框中的自动状态窗格处理功能,这个与其他的两种结构不同,在它们那里,是要靠导航窗格的标签进行导向操作的。
如果要使用对话框结构,那最好将其设计成无模式对话框形式运行。
设计主对话框的使用通常需要用到整个客户区,还有无模式的指定等,在一个典型的主视图对话框中,设计的资源文件如下:
RESOURCE DIALOG r_dlgapp_main_dialog
{
flags = EEikDialogFlagNoDrag | EEikDialogFlagNoTitleBar |
EEikDialogFlagFillAppClientRect | EEikDialogFlagCbaButtons |
EEikDialogFlagModeless;
buttons = r_dlgapp_softkeys_options_home;
pages = r_dlgapp_main_pages;
}
构造时,先来看:
void CDlgappAppUi::ConstructL()
{
BaseConstructL();
iAppView = new (ELeave) CDlgAppMainView;
iAppView->ExecuteLD(R_DLGAPP_MAIN_DIALOG);
AddToStackL(iAppView);
}
我们必须自己来做将控件加到控件栈的工作,因为非模式对话框自身不会这么做。
再来看视图结构,每个运行中的应用程序都有个当前活动的视图,视图结构适用于那些不发布供外部应用使用的视图或适用于可以处理外部应用中断的那些应用程序。使用视图结构的程序总是有个视图处于激活状态,其他的处于非活动状态,我们在切换视图时就造成一个视图和另一个视图的激活和非激活行为,注意了如果是从一个应用程序切换出来,而又返回到该程序,那么是不会产生视图的激活和非激活的。
应用程序用户界面创建并注册每一个视图,并由它们来决定是激活或去处激活,做为对来自视图服务器的事件的响应。而且注意当任何一个视图去处激活时,其相应的内存可能被清除掉。
这些当前的激活视图都是能够接受各种事件的,这些事件告诉应用什么时候出现到前台,什么时候隐藏到后台。相应的视图将接受前台和后台事件。
各种应用的视图都是处于同一层,尽管视图间的导航可以按层次结构来安排。每个视图就象一个小型的应用程序用户界面,它其中要实现众多的函数以供程序使用,如必须提供一个Id()函数,让系统得以识别。再者重载DoActivateL()、DoDeactivate()、 HandleForegroundEventL()和HandleCommandL()以及HandleStatusPaneSizeChange()等函数。
处理DoActivateL()函数时是当客户端要求激活一个视图时进行的,必须考虑的一点就是当视图已经是活动时,我们如何处理这个函数,这时通常需要一些特定的参数。
DoDeactivate()是当程序退出,或切换视图时才被调用,注意了没有,这个函数没有异常退出情况,不错哎:)
HandleForegroundEventL() 是在视图处于活动状态时调用的,视图在前台,它就会收到HandleForegroundEventL(ETrue)事件,在后台就会收到 HandleForegroundEventL(EFalse)事件。视图处于活动状态期间,该函数可能被调用好多次,这是因为视图所属的程序反复在后台和前台切换。该函数可以用于设置焦点,或控制屏幕更新。
HandleCommandL()当视图菜单生成一条命令时调用这个程序。
HandleStatusPaneSizeChange()当客户端区域尺寸由于状态窗格发生变化时调用这个函数。这个函数比较特殊。
视图在其活动期间收到的事件的典型顺序如下:
DoActivateL()
HandleForegroundEventL(ETrue)
HandleForegroundEventL(EFalse)
DoDeactivate()
视图也是要有资源文件的,如果你需要自己的菜单或CBA时,就要创建这么一个AVKON_VIEW资源文件,譬如:
RESOURCE AVKON_VIEW r_viewapp_view1
{
hotkeys = r_viewapp_hotkeys;
menubar = r_viewapp_view1_memubar;
cba = R_AVKON_SOFTKEYS_OPTIONS_BACK;
}
注意视图本身是没有绘图能力的,也没有交互性。它们需要拥有继承自MCoeControlObserver和CCoeControl的容器类,才会有这样的能力,因此在CAkvView的类中(一般的自己的视图都是派生自这个类,如CMyViewArchAppVIew1)都会存在有容器的一个实例。
class CMyViewArchAppView1: public CAknView
{
//...
private:
CMyViewArchAppView1Container* iView;
}
这里的CMyViewArchAppView1Container是
Class CMyViewArchAppView1Container: public CCoeControl,
MCoeControlObserver
本地视图的切换,这个是很常用的,主要用iAvkonViewAppUi->ActivateLocalViewL(TUid::Uid(2));
主要指定被切换的视图的UID
注意了每个视图都可能有自己的菜单,如果要切换视图,首先更新那视图相应菜单的内容:
iEikonEnv->AppUiFactory()->MenuBar()->SetMenuTitleResourceId(R_MY_VIEW_ARCH_APP_VIEW2_MENU);
//经过这步才开始进行视图的切换。
如果DoActivateL()函数发生异常退出时,系统会有一套自己的恢复机制,也就是调用DoDeactivateL(),使之可以恢复该应用程序的早先视图。这就使得我们不必在DoActivateL()中再做什么处理机制了。
如果该应用程序拥有在顶层处理不同种类数据的多个视图或多种模式,那我们应该屏弃使用对话框结构,另外两种结构比较擅长与顶层通信,这个顶层也就是核心引擎。
如果该程序要提供给外部程序不同的视图,那你最好用视图结构,有种不在此例的情况,那就是使用的该程序的一个显示页,看上去就象是在外部进程中运行的一样,这种情况该显示页面应该在一个可以被该外部文件连接的DLL文件中实现。
如果用视图结构,那一定要能够处理因外部程序应用而导致的意外去激活,否则就不要用,改用其他两种。
如果外部程序要与该程序进行复杂的数据交互,那注意了,要使用一个客户/服务器系统,这个系统可以与三种结构中任意一种一起工作。
现在我们来谈谈symbian的主要的框架类:
它有四个基本框架,麻雀虽小和MFC框架自然不能比拟,但也确实有相近之处。
Application、Document、AppUi、View
Application属于应用程序的启动对象,就象是MFC的CWinApp类,它定义了应用程序的属性,这个类也创建文档。本应用类的基类就是CAknApplication
Document是做为程序用来存储数据用的,一个应用程序必须有一个Document文档类的实例,可能是用来被加载AppUi的唯一要求,这个类的基类是CAknDocument,它就好似是MFC的CDoument,在MFC中这就是用来处理永久存储的:)
AppUi,负责处理与应用有关的事件,比如说是options菜单选项、文件的打开和关闭等。注意它将图形绘制和基于屏幕的交互操作委派给自己所拥有的Views,也负责这些views之间的切换。AppUi的基类是CAknAppUi或者CAknViewAppUi,我认为这个类有点类似于MFC中的 CFrameWnd。
比较复杂点的是View,它主要是负责显示屏幕上那些可以与用户交互的数据,并且把用户的操作反馈给AppUi,这个正如上面所说的,是处理与应用有关的事件。
view可以继承自CCoeControl或CAknDialog或者是CAknView,看出来没有,三种基本结构view都是唱主角的。这个很重要,反正显示的任务就交给它了,甭管是传统、对话框还是视图结构。
Symbian我们之前讲过,有界面和引擎之分,前面讲的都是界面相关的,那引擎哪,通常是在自己的类库中实现,它主要是体现应用程序的功能,处理其算法等,在这里也有个术语叫Model/Engine。
提纲契领的说一下,是Framework创建Application,由Application创建文档,在由文档创建出AppUi,然后AppUi负责拥有Model/Engine和View。