基于控件的传统symbian OS架构之调试小结
之前写过一个关于symbian常用程序架构的帖子,不过当时是在简单的看了看书本的条件下写的,仅仅是当时做的一个笔记而已,并没有多少自己的感想体会在里面。当然现在也不能就对传统的symbian os架构很了解了,因为毕竟没有做太多深入的研究试验,仅仅是根据自己的想法做了几个小程序而已。
从四个小程序上来分析:
一、在屏幕上显示两个Label
这是最最基本的了。
关键的就是创建一个容器Container(实际上就是一个拥有窗口的复合控件而已)和一个UI其中Container继承自控件基类CCoeControl,而UI继承自CAknAppUi,这都是必须的。另外,如果Container里的控件,比如编辑框想接收用户事件的话,那么Container同时还要继承?????????因为我的容器里面只有两个Label,所以也就不需要继承这个类了。
(1)先看容器类Container的内容:
因为容器Container里包含两个Label,一定要注意,将两个Label 的指针设为Container类的私有成员变量即:
private
:
CEikLabel* iLabel;
CEikLabel* iToDoLabel;
当然这是在Container类的头文件里声明的。
然后就是看Container的四个方法,这四个都需要我们重写,否则容器中的两个Label控件不会显示。它们分别是:
void
Draw(const TRect& aRect) const;
virtual
CCoeControl* ComponentControl(TInt aIndex) const;
virtual
TInt CountComponentControls() const;
void
SizeChanged();
在本例中,我是这样重写这四个方法的:
CCoeControl* CHelloWorldBasicAppView::ComponentControl(TInt aIndex) const
{
switch(aIndex)
{
case 0:
return iLabel;
case 1:
return iToDoLabel;
default:
return NULL;
}
}
获取容器中每个控件的指针。
void
CHelloWorldBasicAppView::SizeChanged()
{
//设置标签的位置
iLabel->SetExtent( TPoint(10,10), iLabel->MinimumSize() );
iToDoLabel->SetExtent( TPoint(10,100), iToDoLabel->MinimumSize() );
}
SetExtent()方法设置Label的位置。
开始时,我认为,Label的大小不会改变,所以不需要重写该方法,但事实证明,即使容器中的控件大小不会改变,我们也必须重写这两个方法。如果不,那么这两个Label就不会显示。
TInt CHelloWorldBasicAppView::CountComponentControls() const
{
return 2;
}
返回容器中控件的数目,在这里有两个
Label,所以返回2。
void
CHelloWorldBasicAppView::Draw(const TRect& aRect) const
{
// Get the standard graphics context
CWindowGc& gc = SystemGc();
// Gets the control's extent
TRect rect = Rect();
// Clears the screen
gc.Clear(rect);
//设置笔刷
gc.SetPenStyle( CGraphicsContext::ENullPen );
gc.SetBrushColor( KRgbRed ); //红色
gc.SetBrushStyle( CGraphicsContext::ESolidBrush );
gc.DrawRect( aRect );
}
将整个窗口的aRect范围,涂成红色。通过一个TRect对象可以设置Draw的区域即:gc.DrawRect( aRect );也就是说,并不一定要绘制整个窗口。
下面看
Container的ConstructL方法:
void
CHelloWorldBasicAppView::ConstructL(const TRect& aRect)
{
// Create a window for this application view
CreateWindowL();//复合控件创建窗口
iLabel=new(ELeave) CEikLabel;
iLabel->SetContainerWindowL(*this);//将不拥有窗口的控件和窗口关联
iLabel->SetTextL(_L("iLabel"));
iLabel->SetUnderlining(ETrue);
iToDoLabel=new(ELeave) CEikLabel;
iToDoLabel->SetContainerWindowL(*this);
iToDoLabel->SetTextL(_L("iToDoLabel"));
// Set the windows size
SetRect(aRect);
// Activate the window, which makes it ready to be drawn
ActivateL();
}
首先,我们给这个容器创建一个窗口,然后分别创建两个
Label的实例iLabel和iToDoLabel,这其中用到一个方法SetContainerWindowL(*this);,表示将这两个控件显示在那个窗口上,而参数表示的是容器Container,而不是一个Rwindow,这实际上是调用的是CcoeControl中的方法:
virtual void SetContainerWindowL(const CCoeControl& aContainer);
通过看这个函数原型就知道了没有必要非要传递一个Rwindow实例,我们也可以传递一个拥有窗口的容器,而在这里容器Container就是一个拥有窗口的CCoeControl实例,因为我们调用了方法
CreateWindowL();
当然也可以给该方法传递一个
RWindow实例,即调用CCoeControl的重载方法:
void SetContainerWindowL(RWindow& aWindow);
然后设置我们创建的窗口的显示范围SetRect(aRect);,一般是全屏,当然我们也可以设置一个TRect实例,比如:TRect(TPoint(0,0),TSize(100,100)),但是这种情况下,就会看到,我们程序的窗口没有占满整个屏幕,也就是说,仅有TRect(TPoint(0,0),TSize(100,100))的区域来显示我们的程序,而屏幕上剩余的范围还会显示原来的,这样子就比较奇怪,比较难看了。
最后激活窗口ActivateL();也就是说调用容器的Draw()、SizeChanged()等函数,从而开始绘制窗口。
最后就是容器Container类的析构函数:
CHelloWorldBasicAppView::~CHelloWorldBasicAppView()
{
if(iLabel)
{
delete iLabel;
iLabel=NULL;
}
if(iToDoLabel)
{
delete iToDoLabel;
iToDoLabel=NULL;
}
}
这个析构函数的主要作用是销毁我们容器中创建的两个
Label实例iLabel和iToDoLabel。
(2)看UI的内容
其实主要就是UI的ConstructL()方法和析构函数,之所以有析构函数,因为实际上UI也是一个C类。
void
CHelloWorldBasicAppUi::ConstructL()
{
BaseConstructL();
iAppView = CHelloWorldBasicAppView::NewL(ClientRect());
iAppView->SetMopParent( this );
AddToStackL(iAppView);
}
首先创建UI的框架即调用方法BaseConstructL();,然后创建容器Container实例,实际上就是这里的iAppView。
接下来的的
SetMopParent( this );和AddToStackL(iAppView);在本例中并不是必须的:
SetMopParent(
this );是为了设置父控件用的。据网友pan讲:通过这个方法可以设置控件之间的父子关系,然后在子控件中就可以访问父控件或其他子控件,父控件中也可以访问子控件。需要注意的就是要讲这个方法和SetContainerWindowL()区分开。
AddToStackL(iAppView);将容器放到栈顶,从而可以接收用户的事件,如果想让其他容器接收事件的话,就可以通过另一个方法,RemoveFromStatck(iAppView)将当前容器从栈顶移出,然后在将其他容器移入该栈顶即:AddToStackL(iAppView2);
CHelloWorldBasicAppUi::~CHelloWorldBasicAppUi()
{
if (iAppView)
{
iEikonEnv->RemoveFromStack(iAppView);
delete iAppView;//调用容器Container的析构函数,销毁容器对象即iAppView
iAppView = NULL;//注意,一定要将指针置空,否则就会称为悬空指针,也就是野指针。
}
}
在析构函数中销毁创建的容器实例。
需要注意:容器实例iAppView必须是UI类的私有成员,即:
private:
CHelloWorldBasicAppView* iAppView;
小结:这里面出现几个概念,比如屏幕、窗口、容器、复合控件等,比较容易让人混淆。我的理解是这样子的:屏幕就只有一个,也就是手机的显示屏,是一个物理概念;而窗口是逻辑上的概念,一个程序里我们可以创建多个窗口,下面的一个例子我就来说明这个问题;容器和复合控件都是继承自控件基类CCoeControl,并且它们的类定义中都有子控件成员,不同之处就是容器拥有自己的窗口,而复合控件没有,我们可以把复合控件加到其他的复合控件或容器上,我也会在下面的例子里加以实现。
二、屏幕上面切换显示两个窗口
每个窗口都有两个Label。
最开始,我是想这样来实现:让第一个iAppView创建一个窗口,而第二个iAppView2(实际上就是一个复合控件)不再创建新的,而是共用iAppView创建的窗口。试验的时候,在UI的ConstructL()方法里,先创建iAppView,在将拥有窗口的容器iAppView作为参数传递到iAppView2的构造函数中,但却发现并不能实现。
因此,我只能也给iAppView2也创建一个窗口,看这两个容器类的构造函数如下:
void
CHelloWorldBasicAppView::ConstructL(const TRect& aRect)
{
// Create a window for this application view
CreateWindowL();//复合控件创建窗口
iLabel=new(ELeave) CEikLabel;
iLabel->SetContainerWindowL(*this);//将不拥有窗口的控件和窗口关联
iLabel->SetTextL(_L("iLabel"));
iLabel->SetUnderlining(ETrue);
iToDoLabel=new(ELeave) CEikLabel;
iToDoLabel->SetContainerWindowL(*this);
iToDoLabel->SetTextL(_L("iToDoLabel"));
// Set the windows size
SetRect(aRect);
// Activate the window, which makes it ready to be drawn
ActivateL();
}
void
CHelloWorldBasicAppView2::ConstructL(const TRect& aRect,CHelloWorldBasicAppView* iAppView)
{
// Create a window for this application view
CreateWindowL();//复合控件创建窗口
iLabel2=new(ELeave) CEikLabel;
iLabel2->SetContainerWindowL(*iAppView);//将不拥有窗口的控件和窗口关联
iLabel2->SetTextL(_L("iLabel2"));
iLabel2->SetUnderlining(ETrue);
iToDoLabel2=new(ELeave) CEikLabel;
iToDoLabel2->SetContainerWindowL(*iAppView);
iToDoLabel2->SetTextL(_L("iToDoLabel2"));
// Set the windows size
SetRect(aRect);
// Activate the window, which makes it ready to be drawn
ActivateL();
}
通过对照发现,两个构造函数中都创建了窗口,并进行了激活。注意就是它们的窗口范围都是整个屏幕。
现在的问题是:如何轮换显示两个窗口呢?
可以通过方法:
iAppView->MakeVisible( EFalse );或iAppView->MakeVisible( ETrue );来实现窗口的是否显示,来在一个屏幕上切换显示两个范围都是占满个屏幕的窗口。
即看
UI的构造函数:
void CHelloWorldBasicAppUi::ConstructL()
{
BaseConstructL();
iAppView = CHelloWorldBasicAppView::NewL(ClientRect());
iAppView->MakeVisible( EFalse );//暂时不显示容器iAppView
iAppView2 = CHelloWorldBasicAppView2::NewL(ClientRect());
}
三、在一个屏幕上同时显示两个窗口
在设定窗口的范围上,iAppView占屏幕的上半部分,而iAppView2占屏幕的下半部分。其他的和上面一样,当然也就不需要MakeVisible()这个方法了,呵呵。
主要的区别就是UI的构造函数:
void
CHelloWorldBasicAppUi::ConstructL()
{
BaseConstructL();
TRect rect = ClientRect();
iAppView = CHelloWorldBasicAppView::NewL(TRect(rect.iTl.iX,
rect.iTl.iY,
rect.Width(),
rect.Height()/2+rect.iTl.iY));
iAppView2 = CHelloWorldBasicAppView2::NewL(TRect(rect.iTl.iX,
rect.Height()/2+rect.iTl.iY,
rect.Width(),
rect.iBr.iY));
}
这两个容器的窗口分别占屏幕的上下各一半。
四、让
iAppView2仅作为一个复合控件,并称为容器iAppView的一个子控件,和iAppView在一个窗口上显示。
这个地方要比上面复杂一些,我在这里贴出iAppView2的ConstructL()代码:
void
CHelloWorldBasicAppView2::ConstructL(const TRect& aRect)
{
iLabel2=new(ELeave) CEikLabel;
iLabel2->SetContainerWindowL(*this);//将不拥有窗口的控件和窗口关联
iLabel2->SetTextL(_L("iLabel2"));
iLabel2->SetUnderlining(ETrue);
iToDoLabel2=new(ELeave) CEikLabel;
iToDoLabel2->SetContainerWindowL(*this);
iToDoLabel2->SetTextL(_L("iToDoLabel2"));
}
可以看到,仅仅是创建了两个
Label控件而已,而没有之前的创建窗口CreateWindowL()和激活窗口ActiveWindowL()方法了,因为这里的iAppView2仅仅是作为一个复合控件而已,所以就不需要创建窗口了。其它的方法即SizeChanged()、Draw()、CountComponentControls()和ComponentControl()都是不变的。
上面的iLabel2->SetContainerWindowL(*
this);,参数传递的是当前复合控件对象,虽然复合控件并不拥有窗口,但是通过复合控件iAppView2的SetContainerWindowL()方法中的参数是拥有窗口的iAppView,也就是通过层层上传,最后,这两个Label控件也和要显示在上面的窗口联系起来了。而不是把iAppView直接传递给这两个Label的SetContainerWindowL()方法,这里必须注意。
因为要将复合控件iAppView2作为容器iAppView的一个子控件,所以iAppView2要作为iAppView的容器类的一个成员变量即:
private
:
CEikLabel* iLabel;
CEikLabel* iToDoLabel;
CHelloWorldBasicAppView2* iAppView2;//多了复合控件iAppView2这个私有成员变量。
因为iAppView的容器类多了一个新的子控件,即iAppView2,所以这个类的ConstructL()、SizeChanged()、CountComponentControls()、ComponentControl()以及析构函数~ChelloWorldBasicAppView都需要修改,如下:
void
CHelloWorldBasicAppView::ConstructL(const TRect& aRect)
{
// Create a window for this application view
CreateWindowL();//复合控件创建窗口
iLabel=new(ELeave) CEikLabel;
iLabel->SetContainerWindowL(*this);//将不拥有窗口的控件和窗口关联
iLabel->SetTextL(_L("iLabel"));
iLabel->SetUnderlining(ETrue);
iToDoLabel=new(ELeave) CEikLabel;
iToDoLabel->SetContainerWindowL(*this);
iToDoLabel->SetTextL(_L("iToDoLabel"));
//多了下面两行代码
iAppView2=CHelloWorldBasicAppView2::NewL(TRect(TPoint(0,100),TSize(200,200)));
iAppView2->SetContainerWindowL(*this);
// Set the windows size
SetRect(aRect);
// Activate the window, which makes it ready to be drawn
ActivateL();
}
――――――――――――――――――――――――――――――――――――
void
CHelloWorldBasicAppView::SizeChanged()
{
//设置控件的位置
iLabel->SetExtent( TPoint(10,10), iLabel->MinimumSize() );
iToDoLabel->SetExtent( TPoint(10,30), iToDoLabel->MinimumSize() );
//多了下面一行代码,TPoint(10,60)相对于窗口左上角
iAppView2->SetExtent( TPoint(10,60), iToDoLabel->MinimumSize() );
}
―――――――――――――――――――――――――――――――――――――――――――
TInt CHelloWorldBasicAppView::CountComponentControls() const
{
return 3;
}
返回是3,不是4(虽然复合控件有两个子控件)。
――――――――――――――――――――――――――――――――――――――――――
CCoeControl* CHelloWorldBasicAppView::ComponentControl(TInt aIndex) const
{
switch(aIndex)
{
case 0:
return iLabel;
case 1:
return iToDoLabel;
//多了下面两行
case 2:
return iAppView2;
default:
return NULL;
}
}
――――――――――――――――――――――――――――――――――――
CHelloWorldBasicAppView::~CHelloWorldBasicAppView()
{
if(iLabel)
{
delete iLabel;
iLabel=NULL;
}
if(iToDoLabel)
{
delete iToDoLabel;
iToDoLabel=NULL;
}
//多了下面几行
if(iAppView2)
{
delete iAppView2;
iAppView2=NULL;
}
}
问题:
1、 draw()方法,不需要做任何改变。我开始想,可能需要加一个iAppView2.DrawNow()之类的代码,启动绘制复合控件iAppView2的代码,实际是多此一举的。系统会自动运行复合控件的draw()等四个方法。
2、
复合控件以及复合控件中的两个
Label子控件显示的坐标问题,一定记住,它们都是相对于窗口左上角的坐标位置,我曾经将两个子控件的坐标设为相对于它们所属的复合控件了,结果因为超出屏幕范围,没有显示出来。另外屏幕的范围大约在(0,0)--(120,150)之间,如果设置坐标时超出这个范围,就显示不出来了。
当然这只是一些最基本的架构问题,也没有显示一些比较复杂的控件,在这里只是起个熟悉传统程序架构的作用,不正之处,还请各位网友指正。