基于控件的传统symbian OS架构之调试小结

基于控件的传统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)之间,如果设置坐标时超出这个范围,就显示不出来了。
 
当然这只是一些最基本的架构问题,也没有显示一些比较复杂的控件,在这里只是起个熟悉传统程序架构的作用,不正之处,还请各位网友指正。
 
 

你可能感兴趣的:(UI,OS,null,application,delete,Symbian)