第十一篇:SOUI系統資源管理


SOUI資源管理模塊

從前篇已經講到在SOUI中所有資源文件通過一個uires.idx文件進行索引。

這里將介紹在程序中如何引用這些資源文件。

在SOUI系統中,資源文件通過一個統一的接口對象讀取:

namespace SOUI
{
    enum BUILTIN_RESTYPE
    {
        RES_PE=0,
        RES_FILE,
    };

    /**
    * @struct     IResProvider
    * @brief      ResProvider對象
    * 
    * Describe  實現各種資源的加載
    */
    struct IResProvider : public IObjRef
    {
        /**
         * Init
         * @brief    資源初始化函數
         * @param    WPARAM wParam --  param 1 
         * @param    LPARAM lParam --  param 2
         * @return   BOOL -- true:succeed
         *
         * Describe  every Resprovider must implement this interface.
         */
        virtual BOOL Init(WPARAM wParam,LPARAM lParam) =0;
        
        /**
         * HasResource
         * @brief    查詢一個資源是否存在
         * @param    LPCTSTR strType --  資源類型
         * @param    LPCTSTR pszResName --  資源名稱
         * @return   BOOL -- true存在,false不存在
         * Describe  
         */    
        virtual BOOL HasResource(LPCTSTR strType,LPCTSTR pszResName)=0;

        /**
         * LoadIcon
         * @brief    從資源中加載ICON
         * @param    LPCTSTR pszResName --  ICON名稱
         * @param    int cx --  ICON寬度
         * @param    int cy --  ICON高度
         * @return   HICON -- 成功返回ICON的句柄,失敗返回0
         * Describe  
         */    
        virtual HICON   LoadIcon(LPCTSTR pszResName,int cx=0,int cy=0)=0;

        /**
         * LoadBitmap
         * @brief    從資源中加載HBITMAP
         * @param    LPCTSTR pszResName --  BITMAP名稱
         * @return   HBITMAP -- 成功返回BITMAP的句柄,失敗返回0
         * Describe  
         */    
        virtual HBITMAP    LoadBitmap(LPCTSTR pszResName)=0;

        /**
         * LoadCursor
         * @brief    從資源中加載光標
         * @param    LPCTSTR pszResName --  光標名
         * @return   HCURSOR -- 成功返回光標的句柄,失敗返回0
         * Describe  支持動畫光標
         */    
        virtual HCURSOR LoadCursor(LPCTSTR pszResName)=0;

        /**
         * LoadImage
         * @brief    從資源加載一個IBitmap對象
         * @param    LPCTSTR strType --  圖片類型
         * @param    LPCTSTR pszResName --  圖片名
         * @return   IBitmap * -- 成功返回一個IBitmap對象,失敗返回0
         * Describe  如果沒有定義strType,則根據name使用FindImageType自動查找匹配的類型
         */    
        virtual IBitmap * LoadImage(LPCTSTR strType,LPCTSTR pszResName)=0;

        /**
         * LoadImgX
         * @brief    從資源中創建一個IImgX對象
         * @param    LPCTSTR strType --  圖片類型
         * @param    LPCTSTR pszResName --  圖片名
         * @return   IImgX   * -- 成功返回一個IImgX對象,失敗返回0
         * Describe  
         */    
        virtual IImgX   * LoadImgX(LPCTSTR strType,LPCTSTR pszResName)=0;

        /**
         * GetRawBufferSize
         * @brief    獲得資源數據大小
         * @param    LPCTSTR strType --  資源類型
         * @param    LPCTSTR pszResName --  資源名
         * @return   size_t -- 資源大小(byte),失敗返回0
         * Describe  
         */    
        virtual size_t GetRawBufferSize(LPCTSTR strType,LPCTSTR pszResName)=0;

        /**
         * GetRawBuffer
         * @brief    獲得資源內存塊
         * @param    LPCTSTR strType --  資源類型
         * @param    LPCTSTR pszResName --  資源名
         * @param    LPVOID pBuf --  輸出內存塊
         * @param    size_t size --  內存大小
         * @return   BOOL -- true成功
         * Describe  應該先用GetRawBufferSize查詢資源大小再分配足夠空間
         */    
        virtual BOOL GetRawBuffer(LPCTSTR strType,LPCTSTR pszResName,LPVOID pBuf,size_t size)=0;

        /**
         * FindImageType
         * @brief    查詢與指定名稱匹配的資源類型
         * @param    LPCTSTR pszImgName --  資源名稱
         * @return   LPCTSTR -- 資源類型,失敗返回NULL
         * Describe  沒有指定圖片類型時默認從這些類別中查找
         */    
        virtual LPCTSTR FindImageType(LPCTSTR pszImgName) =0;
    };

    /**
    * Helper_FindImageType
    * @brief    查詢與指定名稱匹配的資源類型
    * @param    IResProvider * pResProvider --  當前的ResProvider
    * @param    LPCTSTR pszImgName --  資源名稱
    * @return   LPCTSTR -- 資源類型,失敗返回NULL
    * Describe  提供一個公共的輔助函數
    */    

    
}//namespace SOUI

這個接口的實現類通過實現這些既定接口來完成圖標(HICON),光標(HCURSOR),位圖(HBITMAP),一般圖片(IBitmap)的解碼,同時也提供原始數據(RawData)的讀取。

在SOUI系統中內置了兩種類型的資源加載(ResProvider)模塊:SResProviderPE 和 SResProviderFiles,同時也通過外置組件的形式提供了從ZIP文件加載資源的功能。

這三種資源加載方式基本上涵蓋了目前常見的資源加載方式。

為了能夠從PE的資源數據段中加載資源,我們需要將uires.idx中索引的文件轉換成PE資源可以識別的資源類型+資源名(不是資源ID)的形式。

為了達到這個目的,我們只需要在VS的資源文件中(.rc)將SOUI的資源中定義的文件按照uires.idx定義的類型和名稱加進去即可。

手工添加資源文件很難保證不寫錯。為此,我提供了一個工具(tools\uiresbuilder.exe),這個工具接收一組命令參數,用來將uires.idx轉換成一個RC編譯器可以識別的.rc2文件(命令行參見使用向導生成的工程)。要編譯該.rc2文件,需要在.rc的資源包含中加上我們生成的.rc2文件。

如果程序中的資源不從PE資源加載,則不需要編譯soui_res.rc2文件,以減少程序體積。

SResProviderPE, SResProviderFiles 和 SResProviderZIP分別從PE資源,文件夾及ZIP文件包中初始化資源:

#if (RES_TYPE == 0)//從文件加載
        CreateResProvider(RES_FILE,(IObjRef**)&pResProvider);
        if(!pResProvider->Init((LPARAM)_T("uires"),0))
        {
            SASSERT(0);
            return 1;
        }
#elif (RES_TYPE==1)//從EXE資源加載
        CreateResProvider(RES_PE,(IObjRef**)&pResProvider);
        pResProvider->Init((WPARAM)hInstance,0);
#elif (RES_TYPE==2)//從ZIP包加載
        bLoaded=pComMgr->CreateResProvider_ZIP((IObjRef**)&pResProvider);
        SASSERT_FMT(bLoaded,_T("load interface [%s] failed!"),_T("resprovider_zip"));

        ZIPRES_PARAM param;
        param.ZipFile(pRenderFactory, _T("uires.zip"),"souizip");
        bLoaded = pResProvider->Init((WPARAM)&param,0);
        SASSERT(bLoaded);
#endif

資源加載成功后,調用SApplication::AddResProvider(IResProvider *)接口將創建的資源加載器交給SOUI系統管理。

SApplication::AddResProvider可以調用多次,便於加載不同的資源。

資源加載后不再需要了也可以使用SApplication::RemoveResProvider(IResProvider *)來刪除。

程序中要使用一個資源時,首先調用SApplication::HasResource來查詢一個資源是否存在,然后再根據資源類型選擇不同的接口加載資源。

SApplication中管理着一個IResProvider列表,系統采用后進先查的算法處理資源重名,即最后調用AddResProvider加進來的資源加載器優先級最高。

應用程序中資源的組織

一個基於SOUI開發的應用程序通常資源分為兩部分:

1、控件默認的系統資源,也可以理解為主題(theme)資源。

2、應用程序自定義的資源。

系統資源又分為3部分:

1、控件默認的皮膚(Skin):

SOUI中很多控件都必須要定義一個皮膚(ISkinObj,稱之為繪圖對象更好解理),例如繪制checkbox,radiobox,combobox等控件出出現的位圖部分。如何在應用程序中使用了這些控件,則必須為它們定義這些皮膚資源。為了簡化界面配置,我統一將這些資源進行命名,並打包到一起(參見trunk\soui-sys-resource\theme_sys_res\sys_xml_skin.xml)。

下面是系統中使用定義的命名ISkinObj:

const wchar_t * BUILDIN_SKIN_NAMES[]=
{
    L"_skin.sys.checkbox",
    L"_skin.sys.radio",
    L"_skin.sys.focuscheckbox",
    L"_skin.sys.focusradio",
    L"_skin.sys.btn.normal",
    L"_skin.sys.scrollbar",
    L"_skin.sys.border",
    L"_skin.sys.dropbtn",
    L"_skin.sys.tree.toggle",
    L"_skin.sys.tree.checkbox",
    L"_skin.sys.tab.page",
    L"_skin.sys.header",
    L"_skin.sys.split.vert",
    L"_skin.sys.split.horz",
    L"_skin.sys.prog.bkgnd",
    L"_skin.sys.prog.bar",
    L"_skin.sys.slider.thumb",
    L"_skin.sys.btn.close",
    L"_skin.sys.btn.minimize",
    L"_skin.sys.btn.maxmize",
    L"_skin.sys.btn.restore",
    L"_skin.sys.menu.check",
    L"_skin.sys.menu.sep",
    L"_skin.sys.menu.border",
    L"_skin.sys.menu.skin",
    L"_skin.sys.icons",
    L"_skin.sys.wnd.bkgnd"
};

如果程序中沒有使用到特定控件,也可以不在系統資源中提供對應的ISkinObj。

2、系統使用的MsgBox布局模板:

MsgBox是應用程序常用的功能。SOUI通過提供一個MSGBOX的XML布局模板來實現。如果需要修改MsgBox的樣式,只需要修改這個XML。

<SOUI title="mesagebox" width="200" height="100" appwin="0" frameSize="40,30,10,80" minSize="300,100" resize="0" translucent="1" trCtx="messagebox">
  <style>
    <class name="normalbtn" skin="_skin.sys.btn.normal" font="" colorText="#385e8b" colorTextDisable="#91a7c0" textMode="25" cursor="hand" margin-x="0"/>
  </style>
  <root skin="_skin.sys.wnd.bkgnd">
    <caption id="101" pos="0,0,-0,29">
      <text pos="11,9" class="cls_txt_red" name="msgtitle" >title</text>
      <imgbtn id="2" skin="_skin.sys.btn.close"    pos="-45,0" tip="close"/>
    </caption>

    <window pos="5,30,-5,-50">
      <icon name="msgicon" pos="0,0,32,32" display="0"/>
      <text name="msgtext" pos="[0,0" colorText="#0000FF" multilines="1" maxWidth="300"/>
    </window>
    <tabctrl name="btnSwitch" pos="0,-50,-0,-0" tabHeight="0">
      <page>
        <button pos="|-50,10,|50,-10" name="button1st" class="normalbtn">button1</button>
      </page>
      <page>
        <button pos="|-100,10,|-10,-10" name="button1st" class="normalbtn">button1</button>
        <button pos="|10,10,|100,-10" name="button2nd" class="normalbtn">button2</button>
      </page>
      <page>
        <button pos="|-140,10,|-50,-10" name="button1st" class="normalbtn">button1</button>
        <button pos="|-45,10,|45,-10" name="button2nd" class="normalbtn">button2</button>
        <button pos="|50,10,|140,-10" name="button3rd" class="normalbtn">button3</button>
      </page>
    </tabctrl>
  </root>
</SOUI>

上面是SOUI默認提供的模板。

在這個模板中,只提供了必須的命名對象。如果要修改這個模板,這些命名對象不能缺少,不過布局位置可以任意調整。

3、Edit控件使用的右鍵菜單定義XML:

edit控件的菜單相對固定,因此我們也采用系統資源的形式提供一個預定義的右鍵菜單定義。

<editmenu trCtx="editmenu" iconSkin="_skin.sys.icons" itemHeight="26" iconMargin="4" textMargin="8" >
  <item id="1" icon="3">cut</item>
  <item id="2" icon="4">copy</item>
  <item id="3" icon="5">paste</item>
  <item id="4" >delete</item>
  <sep/>
  <item id="5">select all</item>
</editmenu>

和前面兩個不同,菜單資源每一個item必須包含一個id,取值從1-5,菜單項的位置可以任意。

應用程序自定義資源

很顯然,系統資源提供的樣式使得應用中每一個同類控件長得都一樣。但是很多時候我們會希望兩個功能相似的控件有不一樣的長相,這就需要使用用戶自定義資源。

用戶自定義資源和系統資源一樣,只不過它可以包含更多類型(任意類型),資源也可以任意命名(只要不和系統資源沖突)。

下面為demo中使用的自定義資源(uires.idx):

<?xml version="1.0" encoding="utf-8"?>
<resource>
  <UIDEF>
    <file name="xml_init" path="xml\init.xml"  />
  </UIDEF>
  <ICON>
    <file name="LOGO"  path="image\img_logo.ico" />
  </ICON>
  <CURSOR>
    <file name="ANI_ARROW"  path="image\021.ani" />
    <file name="CUR_TST" path="image\camera_capture.cur"/>
  </CURSOR>
  <LAYOUT>
    <file name="maindlg" path="xml\dlg_main.xml"  />
    <file name="menu_test" path="xml\menu_test.xml"  />
    <file name="page_layout" path="xml\page_layout.xml"  />
    <file name="page_treebox" path="xml\page_treebox.xml"  />
    <file name="page_treectrl" path="xml\page_treectrl.xml"  />
    <file name="page_misc" path="xml\page_misc.xml"  />
    <file name="page_webkit" path="xml\page_webkit.xml"  />
    <file name="page_about" path="xml\page_about.xml"  />
  </LAYOUT>
  <IMGX>
    <file name="png_page_icons"   path="image\page_icons.png" />
    <file name="png_small_icons"  path="image\small_icons.png" />

    <file name="webbtn_back"  path="image\webbtn_back.png" />
    <file name="webbtn_forward"  path="image\webbtn_forward.png" />
    <file name="webbtn_refresh"  path="image\webbtn_refresh.png" />
    
    <file name="png_treeicon"             path="image\TreeIcon.png"/>
    <file name="png_menu_border"  path="image\menuborder.png" />

    <file name="png_vscroll"  path="image\vscrollbar.png" />
    
    <file name="png_tab_left"  path="image\tab_left.png" />
    <file name="png_tab_left_splitter"  path="image\tab_left_splitter.png" />
    <file name="png_tab_main"  path="image\tab_main.png" />
    <file name="btn_menu" path="image\btn_menu.png"  />
  </IMGX>
  <GIF>
    <file name="gif_horse"    path="image\horse.gif"/>
    <file name="gif_penguin"    path="image\penguin.gif"/>
  </GIF>
  <rtf>
    <file name="rtf_test"    path="rtf\RTF測試.rtf"/>
  </rtf>
  <script>
    <file name="lua_test" path="lua\test.lua"/>
  </script>
  <translator>
    <file name="lang_cn" path="translation files\lang_cn.xml"/>
  </translator>
</resource>

不管是系統資源還是用戶資源都由資源管理模塊管理。

實際上這兩種資源可以合並到一個資源包中交給系統管理。有心人可能注意到了項目中使用的系統資源使用的是一個資源DLL,並且沒有uires.idx文件。正是因為使用了資源DLL這一形式,才可以不提供uires.idx文件,因為PE資源本身已經有了分類命名(每一個資源都在一個類型下)。

DEMO中使用系統資源和用戶資源

        SApplication *theApp=new SApplication(pRenderFactory,hInstance);

        //定義一人個資源提供對象,SOUI系統中實現了3種資源加載方式,分別是從文件加載,從EXE的資源加載及從ZIP壓縮包加載
        CAutoRefPtr<IResProvider>   pResProvider;
#if (RES_TYPE == 0)//從文件加載
        CreateResProvider(RES_FILE,(IObjRef**)&pResProvider);
        if(!pResProvider->Init((LPARAM)_T("uires"),0))
        {
            SASSERT(0);
            return 1;
        }
#elif (RES_TYPE==1)//從EXE資源加載
        CreateResProvider(RES_PE,(IObjRef**)&pResProvider);
        pResProvider->Init((WPARAM)hInstance,0);
#elif (RES_TYPE==2)//從ZIP包加載
        bLoaded=pComMgr->CreateResProvider_ZIP((IObjRef**)&pResProvider);
        SASSERT_FMT(bLoaded,_T("load interface [%s] failed!"),_T("resprovider_zip"));

        ZIPRES_PARAM param;
        param.ZipFile(pRenderFactory, _T("uires.zip"),"souizip");
        bLoaded = pResProvider->Init((WPARAM)&param,0);
        SASSERT(bLoaded);
#endif
        //將創建的IResProvider交給SApplication對象
        theApp->AddResProvider(pResProvider);

        //加載系統資源
        HMODULE hSysResource=LoadLibrary(SYS_NAMED_RESOURCE);
        if(hSysResource)
        {
            CAutoRefPtr<IResProvider> sysSesProvider;
            CreateResProvider(RES_PE,(IObjRef**)&sysSesProvider);
            sysSesProvider->Init((WPARAM)hSysResource,0);
            theApp->LoadSystemNamedResource(sysSesProvider);
        }

        //加載全局資源描述XML
        theApp->Init(_T("xml_init")); 

可以看到這里根據預定義宏:RES_TYPE提供了3種參考資源加載形式來加載用戶自定義資源。然后再采用SResProviderPE的資源加載器從系統資源DLL中加載系統資源。

加載系統資源一個關鍵步驟在於調用:

theApp->LoadSystemNamedResource(sysSesProvider);

這個函數從系統資源加載器中讀取那些命名的系統資源。

所有資源加載完成后調用:

        //加載全局資源描述XML
        theApp->Init(_T("xml_init")); 

來初始化資源中定義的全局Skin,Style,ObjAttr對象。

大家可以想一想怎么樣把這兩種資源使用一個資源加載器來完成。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM