zoukankan      html  css  js  c++  java
  • htmlcontrolforsymbian 源码解析

    正文:
            ytom哥的HtmlControl是一个开源的轻量级的HTML/CSS解析和渲染的控件,可以轻松支持复杂的界面效果,也可以用来显示Web内容。大家可以从http://code.google.com/p/htmlcontrol-for-symbian/获得,首先感谢ytom哥给大家提供了这么好的一个选择。
            本文主要从源代码的角度来分析HtmlControl, 有需要从应用的角度多了解的朋友们可以到前面那个网址看ytom哥的例子。




    HtmlControl使用方法:
            代码中最先与大家接触的就是CHtmlControl啦,它就是我们与此控件交互的接口,使用很简单,就像标准控件一样创建:
    iControl = CHtmlControl::NewL(NULL);
    iControl->SetMopParent(this);
    iControl->SetRect(ClientRect());
    iControl->SetEventObserver(this);
    iControl->ActivateL();
    AddToStackL(iControl);
            然后将包含html字符的描述符加入控件并刷新就行了:
                            _LIT(KHtml, "<body style='overflow:auto'><p align='center'>"
                            "<font size='large' color='#FF0000'>Hello World!</font><br>"
                            "<font size='20'><b><i>Hello World!</i></b></font><br>"
                            "<text res='"MAKESTR(R_COMMAND1_TEXT)"'><br>"
                            "<div style='line-height:+30'>Hello World!</div><br>"
                            "<a href='http://www.abc.com'>Hello World!</a><br>"
                            "<a href='#abc'><text res='"MAKESTR(R_COMMAND1_TEXT)"'></a><br>"
                            "</p>"
                            );
                            iControl->AppendContentL(KHtml);
                            iControl->RefreshAndDraw();
            这样就得到了本文第一张图片那样的界面。

    CHtmlControl的介绍
            CHtmlControl最重要的两个函数就是上面的AppendContentL和RefreshAndDraw,其他的有InsertContentL,在指定位置插入html代码。而Element和ElementByTag则通过元素ID和元素标签两种方法得到元素实例的指针供下一步操作。FocusedElement直接得到处于焦点状态的元素。SetEventObserver通过Symbian Observer模式指定得到htmlcontrol里事件的实例,比如得到EOnClick事件后可以通过aEvent.iElement->GetProperty(KHStrHref,buf)得到用户点的超链接。

    解析Html的流程
            CHtmlControl里面包含了CHtmlControlImpl实例的一个指针,实际上几乎所有操作都是通过此指针间接的调用CHtmlControlImpl来操作的,由于使用这种设计隐藏了实现的细节,因此使用此控件的时候会觉得很简单,用作者的话来讲就是“发布的头文件可以干净很多”。
            CHtmlControlImpl最重要的三个函数就是InsertContentL、 ParseL、Refresh,前两个与解析html有关,第三个是解析完成后显示阶段使用。按照程序流程,现在先介绍前两个,CHtmlControl里的AppendContentL、InsertContentL实际上是间接的调用这里的InsertContentL,然后在函数内部又调用了ParseL,在ParseL里又继续调用CHtmlParser里的ParseL,在CHtmlParser的ParseL又调用HcUtils里面EnumTag将Html文档解析为一个个的标签,形如:<a …></a>、<br />,然后在CHtmlParser里调用AppendElementL将完整的标签加入到一个链表里,这个链表完整的串联了所有的从CHtmlElementImpl继承而来的实例,表示各个html元素。现在可以支持的html标签有body、img、a、div、form、input、select、textarea等等,对应的从CHtmlElementImpl继承而来的类是CHtmlElementBody、CHtmlElementImg、CHtmlElementA、CHtmlElementDiv、CHtmlElementForm、CHtmlElementInput、CHtmlElementSelect、CHtmlElementTextArea,而实际上CHtmlElementImpl也是从CHtmlElement继承而来,CHtmlElement保存了一个html元素基本的信息,比如它的Id,标签名等等。
            下面有个简单的序列图可以参考:

            AppendElementL继续调用HtmlParser里的ParseTag,在ParseTag将循环调用HcUtils里的EnumAttribute,将一个完整的标签分解为一个个的属性,比如:
            "<a href='http://www.abc.com'>Hello World!</a>"的属性为href值为http://www.abc.com,创建一个CHtmlElementA并调用它的SetProperty将这些数据保存到CHtmlElementA里,对于不支持的属性将忽略,然后把CHtmlElementA加入到元素队列,用于后面遍历元素,计算各元素的位置并显示。
            每个元素都有SetProperty和GetProperty,负责把自己支持的属性保存起来,不支持的忽略,比如CHtmlElementA:
    TBool CHtmlElementA::SetProperty(const TDesC& aName, const TDesC& aValue)
    {//先调用基类的,用以处理所有元素的共有属性,比如id、tag、name等等。
            if(CHtmlElementImpl::SetProperty(aName, aValue))
                    return ETrue;
            //以下处理不同的元素的不同属性
            if(aName.CompareF(KHStrHref)==0) 
            {//href
                    delete iHref;
                    iHref = aValue.AllocL();
            }
            else if(aName.CompareF(KHStrTarget)==0) 
            {//target
                    delete iTarget;
                    iTarget = aValue.AllocL();
            }
            else if(aName.CompareF(KHStrInnerText)==0)
            {
                    ClearContent();
                    
                    if(aValue.Length()>0)
                    {
                            CHtmlElementText* sub = new (ELeave)CHtmlElementText(iOwner);
                            sub->iParent = iParent;
                            CleanupStack::PushL(sub);
                            sub->PrepareL();
                            sub->SetTextL(aValue);
                            iOwner->Impl()->InsertContent(sub, sub, this, EAfterBegin);
                            CleanupStack::Pop();//sub
                    }
                    …………………………
            }
            else
                    return EFalse;        //对于暂时不支持的属性则忽略
            return ETrue;
    }

    整个循环结束后,html字符解析就基本结束,下面就是要刷新显示了。

    HtmlControl的刷新和显示
            使用的时候很简单,只要调用CHtmlControl的RefreshAndDraw就行了,
    void CHtmlControl::RefreshAndDraw()
    {
            Refresh();
            Window().Invalidate(Rect());
    }
            其中,最重要的就是Refresh,它通过自己保存的CHtmlControlImpl实例指针间接的调用了CHtmlControlImpl的Refresh,这个实现可不简单,遍历先前创建的元素队列,对每个元素进行了3个操作,Measure、Layout、Refresh,Measure主要根据解析的结果对元素的风格、大小、位置进行了设置,Layout主要是对div类型的元素进行设置,其他的采用基类的通用的方法设置本元素的坐标位置,部分元素使用Refresh重新计算内部文字的位置等信息。
            前期准备工作做完后,就可以画出来了,最主要的就是CHtmlControlImpl的Draw,然后通过DrawOffscreen遍历元素列表,调用每个元素的Draw依次画在新创建的CFbsBitGc上,最后再一次性的把CFbsBitGc上的内容画到SystemGc上,核心代码如下:
    void CHtmlControlImpl::DrawOffscreen()
    {
            iOffScreenBitmap->Gc().CancelClippingRect();
            CHtmlElementImpl* current = iBody;
            do
            {
                    if(!current->iState.IsSet(EElementStateHidden))
                            current->Draw(iOffScreenBitmap->Gc());
                    current = current->iNext;
            }
            while(current && current!=iBody);
            iState.Clear(EHCSNeedRedraw);
    }

    按键事件处理
            同系统标准控件一样,按键控件的入口是CHtmlControl的OfferKeyEventL,间接的调用了CHtmlControlImpl的OfferKeyEventL,主要核心处理代码又放在了OfferKeyEventL2中,
    TKeyResponse CHtmlControlImpl::OfferKeyEventL2 (CHtmlElementDiv* aContainer, const TKeyEvent &aKeyEvent, TEventCode aType) 
    {        
            if(aContainer->iFocusedElement
                    && aContainer->iFocusedElement->CanFocus() 
                    && !aContainer->iFocusedElement->iState.IsSet(EElementStateHidden))
            {
                    if(aContainer->iFocusedElement->TypeId()==EElementTypeDiv
                                    && ((CHtmlElementDiv*)aContainer->iFocusedElement)->IsContainer())
                    {//如果是容器,将此消息再次发给容器处理
                            if(OfferKeyEventL2((CHtmlElementDiv*)aContainer->iFocusedElement, aKeyEvent, aType)==EKeyWasConsumed)
                                    return EKeyWasConsumed;
                    }
                     
                    if((VisibilityTest(aContainer->iFocusedElement, aContainer->iDisplayRect)
                            )如果控件可见,将按键消息发给控件处理
                            && aContainer->iFocusedElement->OfferKeyEventL(aKeyEvent, aType)==EKeyWasConsumed)
                            return EKeyWasConsumed;
            }
            if(aContainer->iList && !aContainer->iList->IsEmpty())
                    return aContainer->iList->OfferKeyEventL(aKeyEvent, aType);

            if(aType!=EEventKey || aContainer->iNext==aContainer->iEnd)
                    return EKeyWasNotConsumed;
    //如果控件未处理该消息
            aContainer->iState.Clear(EElementStateFocusChanged);
            TInt keyCode = HcUtils::TranslateKey(aKeyEvent.iCode);
            switch(keyCode)
            {//如果是上下左右方向键,则进行元素焦点的转移和显示内容往上或者往下移动
                    case EKeyLeftArrow:
                            iState.Set(EHCSNavKeyPrev);
                            HandleKeyLeft(aContainer);
                            break;
                            
                    case EKeyRightArrow:
                            iState.Set(EHCSNavKeyNext);
                            HandleKeyRight(aContainer);
                            break;
                            
                    case EKeyUpArrow:
                            iState.Set(EHCSNavKeyPrev);
                            HandleKeyUp(aContainer);
                            break;
                            
                    case EKeyDownArrow:
                            iState.Set(EHCSNavKeyNext);
                            HandleKeyDown(aContainer); 
                            break;
            }
            ………………………………
    }
    列举个按了右方向键的处理:
    void CHtmlControlImpl::HandleKeyRight(CHtmlElementDiv* aContainer, TBool aScrolled)
    {
            CHtmlElementImpl* found = NULL;//找到右边或者下一行第一个可视又可获得焦点的元素
            UpdateVFEs(aContainer);//首先获得可视可获得焦点的元素集合
            if(iVFEs.Count()==0) //如果没有符合要求的元素
            {
                    if(!aScrolled && aContainer->iScrollbar->AddStepPos()) //向下滚动视图
                    {
                            UpdateVFEs(aContainer);//重新检查是否有可视可获得焦点的元素
                            if(iVFEs.Count()>0) //如果有那么第一个元素就是目标元素,否则不作处理
                                    found = iVFEs[0];
                    }
            }
            else
            {//如果有符合要求的元素
                    TInt index = iVFEs.Find(aContainer->iFocusedElement);
                    if(index==KErrNotFound) //如果当前没有焦点元素,那么设置第一个就是焦点元素
                            found = iVFEs[0];
                    else if(index<iVFEs.Count()-1) //如果现有的焦点元素不是最后一个,那么它的下一个元素就成为即将设置焦点的元素
                            found = iVFEs[index+1];
                    else if(!aScrolled && aContainer->iScrollbar->AddStepPos()) //如果现有元素是可视元素集合中的最后一个,那么下面滚动视图重新处理该消息
                            HandleKeyRight(aContainer, ETrue); 
            }
            if(found)
            {//如果找到了符合要求的元素
                    TInt adjustY = aContainer->iScrollbar->RealPos() - aContainer->iScrollbar->Pos();
                    TInt offset = found->iPosition.iY + adjustY + found->iSize.iHeight  - aContainer->iDisplayRect.iBr.iY + 2;
                    if(offset>0 && offset<aContainer->iDisplayRect.Height())  //可能需要滚动视图
                            aContainer->iScrollbar->AddPos(offset);
                    aContainer->FocusChangingTo(found);//设置新焦点元素
            }
    }

    至此,本文简单介绍了HtmlControl的大体流程,只需要知道怎么用的朋友只要看第一点就够了,如果现有HtmlControl有不符合使用要求或者需要支持的更多的html标签的情况,则大家可以全部看完,在了解了整个流程后,对自己需要修改的方面再详细阅读源代码,多了解细节方面则可以自己修改HtmlControl以适应自己的需求。

    HtmlControl值的我们学习和借鉴的是:
            1)将WSD(可写静态数据)放在控件环境CHtmlCtlEnv中,这种方法很巧妙
            2)作者创建了图片池,利用CImageDecoder对各种类型的图片进行解析。
            3)经典的观察者模式的使用
            4)富有层次结构的类设计,如CHtmlControl、CHtmlControlImpl,CHtmlElement <= CHtmlElementImpl <= CHtmlElementA等等
            5)公用属性、代码与各自独有属性、代码的分层设计,比如SetProperty、GetProperty
            6)对不同版本SDK宏的大量运用,一套代码适应不同的SDK

    最后再次感谢作者ytom哥给大家带来如此好的开源作品。
    大部分转载 小部分自写
  • 相关阅读:
    Note/Solution 转置原理 & 多点求值
    Note/Solution 「洛谷 P5158」「模板」多项式快速插值
    Solution 「CTS 2019」「洛谷 P5404」氪金手游
    Solution 「CEOI 2017」「洛谷 P4654」Mousetrap
    Solution Set Border Theory
    Solution Set Stirling 数相关杂题
    Solution 「CEOI 2006」「洛谷 P5974」ANTENNA
    Solution 「ZJOI 2013」「洛谷 P3337」防守战线
    Solution 「CF 923E」Perpetual Subtraction
    KVM虚拟化
  • 原文地址:https://www.cnblogs.com/8586/p/2222716.html
Copyright © 2011-2022 走看看