解決duilib使用zip換膚卡頓的問題(附將資源集成到程序中的操作方法)

轉載請說明原出處,謝謝~~

       今天在做單子是,客戶要求做換膚功能,爲此我專門寫了一個換膚函數,並且把各種皮膚資源壓縮爲各個zip文件來換膚。但是客戶反映程序運行緩慢,我測試後發現的確明顯可以看出慢了不少。最後發現問題在於把皮膚資源都集成到了zip文件中,程序在刷新界面時會重新從zip文件中讀取對應的資源,導致了界面反映卡頓。之前直接把z資源放到目錄裏或者把zip集成到程序內部,都是沒問題的。但是如果要換膚就需要用到zip來壓縮資源了。

       duilib的WinImplBase類爲我們提供了4種加載資源的方法:


	enum UILIB_RESOURCETYPE
	{
		UILIB_FILE=1,		// 來自磁盤文件
		UILIB_ZIP,		// 來自磁盤zip壓縮包
		UILIB_RESOURCE,		// 來自資源
		UILIB_ZIPRESOURCE,	// 來自資源的zip壓縮包
	};

       使用磁盤文件是最簡單的方法,開發時選擇這個方法,但是實際發佈程序後爲了資源的保密就很少這樣做了;使用zip文件也是常用的方法,但是問題就在於資源比較多時界面就有明顯卡頓;直接使用資源肯定是快速的,但是這個方法就太繁瑣了,需要逐個去處理每個資源,用法見MenuDemo;使用資源的zip壓縮包,這個是我最常用的,把資源壓縮爲zip然後集成到程序中,這樣不但可以保密資源,而且不會有卡頓的現象。

     

一、 這裏先把使用“資源的zip壓縮包”方法說明一下:

        1.讓自己的窗體類繼承WinImplBase類,並且重寫GetSkinFile、GetSkinFolder、GetResourceType、GetResourceID這四個方法

        2.在vs中添加自定義資源,找到自己的zip文件並添加,資源類型填寫爲“ZIPRES”,得到資源的ID號,比如這裏爲“IDR_ZIPRES2”

        3.GetSkinFile中返回主窗體的xml文件的名字

        4.GetSkinFolder中返回資源文件所在的目錄

        5.GetResourceType中返回資源類型,此時應該寫爲“return UILIB_ZIPRESOURCE;”

        6.GetResourceID中返回對應的zip資源的ID,例如:“return MAKEINTRESOURCE(IDR_ZIPRES2);”

       7.編譯程序,這樣就可以使用資源的zip壓縮包了。

    (ps:在WinMain函數裏只要寫一句 CPaintManagerUI::SetInstance(hInstance);代碼就夠了,不需要其他任何CPaintManagerUI的代碼,其他代碼WinImplBase會處理的!


二、再說明一下常用的zip文件換膚方法

     使用這種方法來換膚,要求加載資源的方式使用第二種“來自磁盤的zip壓縮包”方式,用法我就不說明了,duilib的多數demo都是用這種方法。

     如果要換膚,直接使用如下兩句代碼就可以了:

   CPaintManagerUI::SetResourceZip(_T("skin2.zip")); // 這裏寫入新的皮膚包的文件名就行了
   CPaintManagerUI::ReloadSkin();


三、使用“來自資源的zip壓縮包”方法換膚

     這樣做有兩個好處,第一是不會有使用單獨zip文件那種卡頓現象,第二是資源文件會相對更安全一些。

     我測試了一下,默認情況下不能讓duilib使用這種方法來換膚,原因會在後面給出。接下來直接說明怎麼使用這個方法:

     從常用的zip文件換膚方法中可以看出,換膚的關鍵就是重新設置zip文件,也就是說SetResourceZip是換膚的關鍵函數,他重新指定了zip文件。這個函數有兩個版本,一個是加載文件中的zip,另一個是加載資源中的zip,我們需要的就是第二個版本的SetResourceZip。程序調用ReloadSkin函數後,會通知所有控件去重新加載圖片資源,圖片資源的加載會通過LoadImage函數,這個函數會根據加載資源類型的不同而去選擇從不同的地方去試圖找到資源並加載。

      在使用“資源的zip壓縮包”方法的前提下,如果要換膚就使用如下函數,函數的參數是新換皮膚的資源ID,比如“IDR_ZIPRES2”,函數實際就是從程序資源中找到對應的皮膚zip文件,並且調用對應的SetResourceZip函數加載資源:


void CFrameWnd::ReloadZipResource(int ID)
{

	HRSRC hResource = ::FindResource(m_PaintManager.GetResourceDll(), MAKEINTRESOURCE(ID), _T("ZIPRES"));
	if( hResource == NULL )
		return ;
	DWORD dwSize = 0;
	HGLOBAL hGlobal = ::LoadResource(m_PaintManager.GetResourceDll(), hResource);
	if( hGlobal == NULL ) 
	{
#if defined(WIN32) && !defined(UNDER_CE)
		::FreeResource(hResource);
#endif
		return ;
	}
	dwSize = ::SizeofResource(m_PaintManager.GetResourceDll(), hResource);
	if( dwSize == 0 )
		return ;

	CPaintManagerUI::SetResourceZip((LPBYTE)::LockResource(hGlobal), dwSize);

#if defined(WIN32) && !defined(UNDER_CE)
	::FreeResource(hResource);
#endif

	CPaintManagerUI::ReloadSkin();
}



           理論上這就應該就可以了,但是實際測試還有問題,後來發現是SetResourceZip函數的定義有些問題:

void CPaintManagerUI::SetResourceZip(LPVOID pVoid, unsigned int len)
{
    if( m_pStrResourceZip == _T("membuffer") ) return;
    if( m_bCachedResourceZip && m_hResourceZip != NULL ) {
        CloseZip((HZIP)m_hResourceZip);
        m_hResourceZip = NULL;
    }
    m_pStrResourceZip = _T("membuffer");
    m_bCachedResourceZip = true;
    if( m_bCachedResourceZip ) 
        m_hResourceZip = (HANDLE)OpenZip(pVoid, len, 3);
}

          可以看到如果使用資源zip文件,那麼m_pStrResourceZip變量就會保存_T("membuffer")字符串,當再次調用SetResourceZip函數時,由於第一句代碼的判斷就會導致函數直接返回,所以這裏直接註釋掉第一句代碼就可以了。

         至此,就可以使用資源中的zip文件來換膚了,兩全其美。這裏還可以擴展,可以把zip資源都繼承到一個dll文件中,然後在加載函數裏先加載dll,然後從dll加載資源,這樣既可以讓皮膚資源獨立爲文件,加載也快速,並且資源也安全。這個代碼很好寫,我這裏就不提供了。

         如有錯誤,請在博客留言!


         Redrain   2014.10.16

發佈了84 篇原創文章 · 獲贊 208 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章