Symbian開發學習筆記

 
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<get name>資源結構中設置菜單。

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 <apcaptionfile.rh>

RESOURCE CAPTION_DATA

      caption = tst_app_caption_string;
      shortcaption = tst_app_short_caption_string;


標題資源文件應該被命名爲<NAME OF APP>_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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章