基于控件的传统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 
 const TRect& aRect) const;
 
virtual 
 const;
 
virtual 
 const;
 
void 
  SizeChanged();


 


在本例中,我是这样重写这四个方法的:


CCoeControl* CHelloWorldBasicAppView::ComponentControl(TInt aIndex) const
 
{
 
switch(aIndex)
 
     {
 
case
 
return
 
case
 
return
 
default:
 
return
 
     }
 
}
 
获取容器中每个控件的指针。
 

    

 
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
 
}
 

  返回容器中控件的数目,在这里有两个 
 Label,所以返回2。 

 

    

 
void 
 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.SetBrushStyle( CGraphicsContext::ESolidBrush );
 
    gc.DrawRect( aRect );
 
 }
 

  将整个窗口的aRect范围,涂成红色。通过一个TRect对象可以设置Draw的区域即:gc.DrawRect( aRect );也就是说,并不一定要绘制整个窗口。 

 

    

 

  下面看 
 Container的ConstructL方法: 

 
void 
 const
 
    {
 
// Create a window for this application view
 
//复合控件创建窗口
 

    

 
new(ELeave) CEikLabel;
 
this);//将不拥有窗口的控件和窗口关联
 
     iLabel->SetTextL(_L("iLabel"));
 
     iLabel->SetUnderlining(ETrue);
 

    

 
new(ELeave) CEikLabel;
 
this);
 
     iToDoLabel->SetTextL(_L("iToDoLabel"));
 

    

 
// Set the windows size
 
    SetRect(aRect);
 

    

 
// Activate the window, which makes it ready to be drawn
 
    ActivateL();
 
}


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=NULL;
 
     }
 
if(iToDoLabel)
 
     {
 
delete
 
          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。 

 
this );和AddToStackL(iAppView);在本例中并不是必须的:
 
this );是为了设置父控件用的。据网友pan讲:通过这个方法可以设置控件之间的父子关系,然后在子控件中就可以访问父控件或其他子控件,父控件中也可以访问子控件。需要注意的就是要讲这个方法和SetContainerWindowL()区分开。
 

  AddToStackL(iAppView);将容器放到栈顶,从而可以接收用户的事件,如果想让其他容器接收事件的话,就可以通过另一个方法,RemoveFromStatck(iAppView)将当前容器从栈顶移出,然后在将其他容器移入该栈顶即:AddToStackL(iAppView2); 

 

    

 
CHelloWorldBasicAppUi::~CHelloWorldBasicAppUi()
 
{
 
if
 
        {
 
        iEikonEnv->RemoveFromStack(iAppView);
 
delete
 
        iAppView = NULL;//注意,一定要将指针置空,否则就会称为悬空指针,也就是野指针。
 
        }
 
}


在析构函数中销毁创建的容器实例。


 


需要注意:容器实例iAppView必须是UI类的私有成员,即:


private: 

 
CHelloWorldBasicAppView* iAppView;

 


小结:这里面出现几个概念,比如屏幕、窗口、容器、复合控件等,比较容易让人混淆。我的理解是这样子的:屏幕就只有一个,也就是手机的显示屏,是一个物理概念;而窗口是逻辑上的概念,一个程序里我们可以创建多个窗口,下面的一个例子我就来说明这个问题;容器和复合控件都是继承自控件基类CCoeControl,并且它们的类定义中都有子控件成员,不同之处就是容器拥有自己的窗口,而复合控件没有,我们可以把复合控件加到其他的复合控件或容器上,我也会在下面的例子里加以实现。


 


二、屏幕上面切换显示两个窗口


每个窗口都有两个Label。


最开始,我是想这样来实现:让第一个iAppView创建一个窗口,而第二个iAppView2(实际上就是一个复合控件)不再创建新的,而是共用iAppView创建的窗口。试验的时候,在UI的ConstructL()方法里,先创建iAppView,在将拥有窗口的容器iAppView作为参数传递到iAppView2的构造函数中,但却发现并不能实现。


因此,我只能也给iAppView2也创建一个窗口,看这两个容器类的构造函数如下:


void 
 const
 
    {
 
// Create a window for this application view
 
//复合控件创建窗口
 

    

 
new(ELeave) CEikLabel;
 
this);//将不拥有窗口的控件和窗口关联
 
     iLabel->SetTextL(_L("iLabel"));
 
     iLabel->SetUnderlining(ETrue);
 

    

 
new(ELeave) CEikLabel;
 
this);
 
     iToDoLabel->SetTextL(_L("iToDoLabel"));
 

    

 
// Set the windows size
 
    SetRect(aRect);
 

    

 
// Activate the window, which makes it ready to be drawn
 
    ActivateL();
 
}
 

    

 
void 
 const
 
    {
 
// Create a window for this application view
 
//复合控件创建窗口
 

    

 
new(ELeave) CEikLabel;
 
//将不拥有窗口的控件和窗口关联
 
     iLabel2->SetTextL(_L("iLabel2"));
 
     iLabel2->SetUnderlining(ETrue);
 

    

 
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 
 const
 
    {
 
new(ELeave) CEikLabel;
 
this);//将不拥有窗口的控件和窗口关联
 
     iLabel2->SetTextL(_L("iLabel2"));
 
     iLabel2->SetUnderlining(ETrue);
 

    

 
new(ELeave) CEikLabel;
 
this);
 
     iToDoLabel2->SetTextL(_L("iToDoLabel2"));
 
}

 


可以看到,仅仅是创建了两个 Label控件而已,而没有之前的创建窗口CreateWindowL()和激活窗口ActiveWindowL()方法了,因为这里的iAppView2仅仅是作为一个复合控件而已,所以就不需要创建窗口了。其它的方法即SizeChanged()、Draw()、CountComponentControls()和ComponentControl()都是不变的。


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 
 const
 
    {
 
// Create a window for this application view
 
//复合控件创建窗口
 

    

 
new(ELeave) CEikLabel;
 
this);//将不拥有窗口的控件和窗口关联
 
     iLabel->SetTextL(_L("iLabel"));
 
     iLabel->SetUnderlining(ETrue);
 

    

 
new(ELeave) CEikLabel;
 
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,不是4(虽然复合控件有两个子控件)。


――――――――――――――――――――――――――――――――――――――――――


CCoeControl* CHelloWorldBasicAppView::ComponentControl(TInt aIndex) const
 
{
 
switch(aIndex)
 
     {
 
case
 
return
 
case
 
return
 
//多了下面两行
 
case 2:
 
         return iAppView2;
 
default:
 
return
 
     }
 
}
 

    

 

  ―――――――――――――――――――――――――――――――――――― 

 
CHelloWorldBasicAppView::~CHelloWorldBasicAppView()
 
{
 
if(iLabel)
 
     {
 
delete
 
          iLabel=NULL;
 
     }
 

    

 
if(iToDoLabel)
 
     {
 
delete
 
          iToDoLabel=NULL;
 
     }
 
//多了下面几行
 
if(iAppView2)
 
     {
 
         delete iAppView2;
 
          iAppView2=NULL;
 
     }
 
}


 


 


问题:


1、 draw()方法,不需要做任何改变。我开始想,可能需要加一个iAppView2.DrawNow()之类的代码,启动绘制复合控件iAppView2的代码,实际是多此一举的。系统会自动运行复合控件的draw()等四个方法。


  复合控件以及复合控件中的两个 Label子控件显示的坐标问题,一定记住,它们都是相对于窗口左上角的坐标位置,我曾经将两个子控件的坐标设为相对于它们所属的复合控件了,结果因为超出屏幕范围,没有显示出来。另外屏幕的范围大约在(0,0)--(120,150)之间,如果设置坐标时超出这个范围,就显示不出来了。


 


当然这只是一些最基本的架构问题,也没有显示一些比较复杂的控件,在这里只是起个熟悉传统程序架构的作用,不正之处,还请各位网友指正。