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架構的理解而已,寫下來只是希望比我還新的新人在碰到這種架構問題時,看到我的文章,能夠有所啓發。限於本人水平有限,可能文中有理解錯誤之處,還請高手不吝賜教。
轉載請註明出處,謝謝。