1. 程式人生 > >duilib建立自定義控制元件

duilib建立自定義控制元件

我之前也寫過一片封裝xml為一個容器的文章,只是寫的很隨意,僅僅貼出了一個demo的地址。

在群裡還有一些剛剛接觸duilib的朋友們問到duilib自定義控制元件的問題,這裡我轉載一篇redrain大佬的博文。主要是這篇文章寫的太好了,我們直接參考理解就好,我寫的肯定沒這個好。原文地址:http://blog.csdn.net/zhuhongshu/article/details/45362751。需要注意redrain大佬的這篇文章寫的時間比較早,是基於早期的duilib版本,新版的duilib略有改動,我在下文中已經進行了修改。


       用Duilib開發介面時,很多情況下庫自帶的控制元件不滿足需求,就需要基於Duilib建立自定義控制元件(自繪新的控制元件,或者用來封裝win32的子窗體,來顯示視訊、網頁等)。

       在群裡經常會有剛接觸Duilib的朋友問題怎麼建立自己的自定義控制元件,或者建立的控制元件無法正常創建出來。我簡單寫一篇部落格,把建立自定義控制元件的完整過程,和一些注意事項說明一下。另外說一下如果把win32的子窗體封裝為控制元件,希望能有幫助。
       建立自定義控制元件包含兩個過程:        1、繼承現有的控制元件類建立新的控制元件類        2、讓程式識別新的控制元件並可以在xml中使用


建立新的控制元件類:
       首先從的現有的Duilib控制元件中選擇一個最合適的控制元件類作為父類用來派生,比如你想自定義一個按鈕,那麼你可以從CButtonUI派生出新的類,然後重寫幾個介面。 因為CButtonUI控制元件本身就已經包含了normal、hot、pushed等狀態,同時包含單擊事件。
    一般來說,建立新控制元件後,最先應該重寫的兩個函式是GetClass和GetInterface。他們是用來區分控制元件的型別的虛擬函式,用於動態識別控制元件型別和做控制元件的型別轉換。

       從Duilib的自帶控制元件上可以看出,那麼GetClass函式返回的字串一般是DUI_CTR_XXXX,這個經常用於duilib內部識別具體控制元件型別用。而GetInterface函式是根據傳入的引數,是否與自身的字串匹配,來決定能否把自己轉換為需要的控制元件型別。GetInterface中用來匹配的字串,應該與xml中的對應的控制元件的標籤名稱一致。
      比如CButtonUI類,GetClass對應DUI_CTR_BUTTON,GetInterface對應DUI_CTR_BUTTON。這不是強制的,但是保持這個風格很重要!這裡一般來說是使用DUI_CTR_BUTTON巨集,也可以直接寫DUI_CTR_BUTTON對應的字串Button,不過不建議這樣做。在自己的程式中如果用到相關的,最好也用巨集,這樣一般需要修改某個巨集對應的字串,僅改宣告就行了。

      理論上,完成這兩個介面就建立好最基本的自定義控制元件了。但是為了讓自定義控制元件的行為和外觀更豐富,就需要重寫更多的函數了,我這裡把經常會重寫的函式說明一下!
[cpp]  view plain  copy
  1. virtual LPVOID GetInterface(LPCTSTR pstrName);
    virtual UINT GetControlFlags() const;
    virtual HWND GetNativeWindow() const;
  2. virtual void SetPos(RECT rc, bool bNeedInvalidate = true);
  3. virtual void DoInit();
  4. virtual void DoEvent(TEventUI& event); 
  5. virtual bool DoPaint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl);
  6. virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);   
  7. virtual void SetInternVisible(bool bVisible = true); // 僅供內部呼叫,有些UI擁有視窗控制代碼,需要重寫

 
   
  
 
   
  
   
   
  
   
          以上列出的函式,是最常被重寫的。
  
  
         
  
  
         DoEvent函式:控制元件的核心函式,他是訊息處理函式,用來處理Duilib封裝過的各個訊息,比如滑鼠的移入移出、出現的懸停、單擊雙擊、右擊、滾輪滑動、獲取焦點、設定游標等等。所以如果你的控制元件需要修改這些行為,必須重寫這個函式,具體的處理方法可以參考Duilib現有的控制元件。
  
  
  
       DoPaint函式:控制元件的核心函式,他是控制元件的繪製處理函式,當Duilib底層要重新繪製這個控制元件,或者控制元件自己呼叫Invalidata函式強制自己重新整理時,這個函式就會被觸發,在這個函式裡完成了各種狀態下的背景前景繪製,背景色繪製,文字繪製,邊框繪製。而這個函式會呼叫PaintBkColor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder等函式來完成各個繪製步驟。所以你可以根據需求來決定重寫DoPaint或者只重寫某幾個PaintXXX函式。DoPaint函式經常和DoEvent函式結合使用,DoEvent得到了某個訊息後,改變控制元件的狀態,然後呼叫Invalidate函式讓控制元件重繪。
      SetAttribute函式:用於擴充套件自定義控制元件的屬性,Duilib的控制元件本身已經包含name、text、bkimage等屬性,如果要增加新屬性,就需要重寫此函式來擴充套件屬性。
      DoInit函式:當控制元件被新增到容器後,由容器呼叫的函式。在這裡,整個Duilib程式框架已經完成,當需要做一些介面的初始操作時可以重寫此函式,常見的用法就是在此建立Win32子窗體並且封裝它,相關內容我在後面再說。
    SetInternelVisible、SetPos:這幾個函式同樣也是,當控制元件封裝了Win32子視窗後,重寫這幾個函式來控制子視窗的顯示和隱藏、和位置。

      這樣就建立完成了自定義控制元件。
識別新控制元件:
       自定義控制元件建立完畢後,需要做的就是讓控制元件可以被xml佈局識別出來。為此我們需要完成Duilib的IDialogBuilderCallback介面,重寫這個介面中的CreateControl函式。
       通常情況下,可以讓窗體類繼承IDialogBuilderCallback介面並且重寫CreateControl(DuiLib自帶的WindowImplBase窗體類已經繼承了這個介面,如果是繼承WindowImplBase的話就直接重寫CreateControl就可以了)。函式處理方法是比較傳入的字串,根據字串來決定返回什麼控制元件的指標,這個傳入的字串就是xml檔案中控制元件的標籤,比如<Button />中的字串Button。
      習慣上,在xml中自定義控制元件的標籤名稱應該和控制元件的GetInterface中的判斷字串一致。這樣,在解析xml過程中,當解析到標籤名為對應的字串時,就會創建出對應的控制元件了。
       實際上,誰來繼承IDialogBuilderCallback介面肯定都可以,比如QQDemo裡,是給自定義控制元件本身繼承了這個介面。
              當程式響應WM_CREATE訊息時,會建立一個CDialogBuilder物件,並且呼叫他的Create方法來解析xml檔案。

[cpp]  view plain  copy
  1. CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback,   
  2.         CPaintManagerUI* pManager, CControlUI* pParent)  


        這個函式 的第一個引數指定為xml檔案的路徑;第二個引數一般指定為NULL,我這裡不詳解了;第三個引數,就是識別自定義控制元件的關鍵了,這個引數要指定為繼承了IDialogBuilderCallback介面的類物件的指標,比如窗體類繼承IDialogBuilderCallback,這個引數就填寫窗體類物件的指標。只有填寫了這個引數,自定義控制元件才會被識別,經常有人問自己的自定義控制元件為什麼無法被識別。多數情況就是這裡沒處理好;第四個引數指定CPaintManagerUI類物件指標,這個肯定會伴隨著窗體類物件一起存在。最後一個引數一般為NULL。

        這幾步都完成後,你的自定義控制元件就可以被xml佈局正確的識別並建立了。至此,建立自定義控制元件的基本過程就完成了!如果有不明白的,可以多看看QQDemo等程式碼。

封裝Win32控制元件或者Win32子視窗:


        如果要給Duilib,增加一個視訊播放控制元件,一般來說視訊播放庫都需要依賴一個子視窗。這時,就應該自定義個控制元件,並且封裝維護一個子視窗了。
       封裝的子視窗有三種:第一種比較簡單、單純封裝一個子視窗、讓視訊庫一類的庫依賴;第二種麻煩一些、封裝子視窗、並且處理子視窗的訊息;第三種和第二種類似、封裝Win32的控制元件並且處理他的訊息。

       單純封裝子視窗:

      這時就需要重寫我之前提到的DoInit函式和SetVisbile等函數了。首先在自定義控制元件內宣告HWND型別的m_hWnd成員變數來儲存子窗體指標。
      在DoInit函式裡,呼叫CreateWindowEx函式,建立一個win32子窗體,並且用m_hWnd儲存控制代碼。比如:

[cpp]  view plain  copy
  1. m_hhWnd = CreateWindow(_T("#32770"), _T("WndMediaDisplay"), WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, m_PaintManager.GetPaintWindow(), (HMENU)0, NULL, NULL);  


      然後在SetVisible等函式內控制子窗體的顯示隱藏;在SetPos函式內控制子窗體的位置、限制在本控制元件的範圍內。
     這樣就封裝好了win32子視窗,然後可以把這個窗體控制代碼用於視訊播放等。
         封裝子視窗並處理他的訊息:

      這時就比較麻煩了,參見Duilib的CEditUI控制元件等。我們需要繼承CWindowWnd另外封裝一個窗體類,窗體類的封裝不屬於本文範圍,我就不細說了。重寫窗體類的HandleMessage函式,來響應各種WM_XXX訊息。
      然後在我們的自定義控制元件內,不再宣告HWND型別m_hWnd變量了,而是自定剛才的窗體類的物件。然後在DoInit函式內呼叫這個對應的Create函式函式來建立窗體類。然後同樣還是維護這個窗體的顯示隱藏、和位置。
     關於這種控制元件的封裝,可以參考我寫的webkit核心瀏覽器控制元件、裡面是完整的封裝了Win32子窗體、並且處理了他的訊息,用於顯示webkit核心渲染的網頁。地址: http://blog.csdn.net/zhuhongshu/article/details/38540711

       封裝Win32控制元件並處理他的訊息:

      這個可以參考CEditUI控制元件的處理程式碼,思想上和封裝子視窗並處理訊息是一樣的。同樣也可以參考webkit核心瀏覽器控制元件程式碼。不過與之不同的是,我們需要重寫兩個函式
[cpp]  view plain  copy
  1. LPCTSTR GetWindowClassName() const;  
  2. LPCTSTR GetSuperClassName() const;  



      這裡最主要的就是處理GetSuperClassName函式,這個函式的作用就是超類化,而封裝子視窗並處理訊息是子類化,這兩個操作恰好相反。在 GetSuperClassName函式內,要範圍Win32控制元件對應的類名、Duilib檢測到GetSuperClassName函式函式後就會建立Win32控制元件。這時我們處理HandleMessage函式,就可以處理到Win32控制元件的訊息的。具體的處理邏輯請參見CEditUI控制元件。
額外說一點:
擴充套件到當前流行的wke,miniblink等流行的瀏覽器元件,包括cef等,要合併到duilib中使用:1. 使用WS_CHILD嵌入式真子視窗,具體demo請自己找,可以參考CEditUI控制元件。2.使用WS_POPUP彈出式真子視窗。3.瀏覽器元件使用OSR等方式,自己繪製到視窗dc上,這是無視窗控制元件。具體的使用哪一種自己根據需求。對應的demo還需要自己去找,我雖然3種方式都用過寫過,但是沒有剝離過demo,以後有機會了把3中都搞個demo再分享給大家吧。
總結:
        差不多就說道這裡了,把常見的自定義控制元件的基本步驟說明了一下,實際開發時還要多看Duilib的原始碼,才能稱心如意的開發控制元件,希望對剛接觸Duilib的朋友有幫助!