Avkon視圖切換架構之調試小結

 Avkon視圖切換架構之調試小結

在我看來,理解了傳統的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即可。
 
聲明:這只是我作爲一個Symbian新人對View架構的理解而已,寫下來只是希望比我還新的新人在碰到這種架構問題時,看到我的文章,能夠有所啓發。限於本人水平有限,可能文中有理解錯誤之處,還請高手不吝賜教。
轉載請註明出處,謝謝。
發佈了14 篇原創文章 · 獲贊 0 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章