DuiLib功能添加, 問題修復, 代碼分析


1【問題修復】 Button的ShowHtml=true時, 設置{n}換行失效. 因為CLabelUI的text默認是不能換行的. 已經修復.

2,【代碼分析】DuiLib中漸變色的使用和實現.

在一個Layout里面可以使用三種漸變色, DuiLib的繪制代碼如下:

void CControlUI::PaintBkColor(HDC hDC)
{
    if( m_dwBackColor != 0 ) {
        if( m_dwBackColor2 != 0 ) {
            if( m_dwBackColor3 != 0 ) {
                RECT rc = m_rcItem;
                rc.bottom = (rc.bottom + rc.top) / 2;
                CRenderEngine::DrawGradient(hDC, rc, GetAdjustColor(m_dwBackColor), GetAdjustColor(m_dwBackColor2), true, 8);
                rc.top = rc.bottom;
                rc.bottom = m_rcItem.bottom;
                CRenderEngine::DrawGradient(hDC, rc, GetAdjustColor(m_dwBackColor2), GetAdjustColor(m_dwBackColor3), true, 8);
            }
            else 
                CRenderEngine::DrawGradient(hDC, m_rcItem, GetAdjustColor(m_dwBackColor), GetAdjustColor(m_dwBackColor2), true, 16);
        }
        else if( m_dwBackColor >= 0xFF000000 ) CRenderEngine::DrawColor(hDC, m_rcPaint, GetAdjustColor(m_dwBackColor));
        else CRenderEngine::DrawColor(hDC, m_rcItem, GetAdjustColor(m_dwBackColor));
    }
}

 也就是說, 兩種顏色的時候對半漸變, 3種顏色的時候, 分成兩部分, 分別進行漸變. 而且貌似只能垂直漸變.

繪制漸變的原理, 關鍵在於GradientFill這個函數, 它可以用來對一塊區域進行漸變色繪制, 如果這個函數不可用, 就換成用代碼縱向模擬.

代碼如下:

void CRenderEngine::DrawGradient(HDC hDC, const RECT& rc, DWORD dwFirst, DWORD dwSecond, bool bVertical, int nSteps)
    {
        typedef BOOL (WINAPI * LPALPHABLEND)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION);
        static LPALPHABLEND lpAlphaBlend = (LPALPHABLEND) ::GetProcAddress(::GetModuleHandle(_T("msimg32.dll")), "AlphaBlend");
        if( lpAlphaBlend == NULL ) lpAlphaBlend = AlphaBitBlt;
        typedef BOOL (WINAPI * PGradientFill)(HDC, PTRIVERTEX, ULONG, PVOID, ULONG, ULONG);
        static PGradientFill lpGradientFill = (PGradientFill) ::GetProcAddress(::GetModuleHandle(_T("msimg32.dll")), "GradientFill");

        BYTE bAlpha = (BYTE)(((dwFirst >> 24) + (dwSecond >> 24)) >> 1);
        if( bAlpha == 0 ) return;
        int cx = rc.right - rc.left;
        int cy = rc.bottom - rc.top;
        RECT rcPaint = rc;
        HDC hPaintDC = hDC;
        HBITMAP hPaintBitmap = NULL;
        HBITMAP hOldPaintBitmap = NULL;
        if( bAlpha < 255 ) //需要使用alpha通道, 轉為在內存中繪制.
        {
            rcPaint.left = rcPaint.top = 0;
            rcPaint.right = cx;
            rcPaint.bottom = cy;
            hPaintDC = ::CreateCompatibleDC(hDC);
            hPaintBitmap = ::CreateCompatibleBitmap(hDC, cx, cy);
            ASSERT(hPaintDC);
            ASSERT(hPaintBitmap);
            hOldPaintBitmap = (HBITMAP) ::SelectObject(hPaintDC, hPaintBitmap);
        }
        if( lpGradientFill != NULL )
        {
            TRIVERTEX triv[2] =
            {
                { rcPaint.left, rcPaint.top, GetBValue(dwFirst) << 8, GetGValue(dwFirst) << 8, GetRValue(dwFirst) << 8, 0xFF00 },
                { rcPaint.right, rcPaint.bottom, GetBValue(dwSecond) << 8, GetGValue(dwSecond) << 8, GetRValue(dwSecond) << 8, 0xFF00 }
            };
            GRADIENT_RECT grc = { 0, 1 };
            lpGradientFill(hPaintDC, triv, 2, &grc, 1, bVertical ? GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H);
        }
        else
        {
            // Determine how many shades
            int nShift = 1;
            if( nSteps >= 64 ) nShift = 6;
            else if( nSteps >= 32 ) nShift = 5;
            else if( nSteps >= 16 ) nShift = 4;
            else if( nSteps >= 8 ) nShift = 3;
            else if( nSteps >= 4 ) nShift = 2;
            int nLines = 1 << nShift;
            for( int i = 0; i < nLines; i++ )
            {
                // Do a little alpha blending
                BYTE bR = (BYTE) ((GetBValue(dwSecond) * (nLines - i) + GetBValue(dwFirst) * i) >> nShift);
                BYTE bG = (BYTE) ((GetGValue(dwSecond) * (nLines - i) + GetGValue(dwFirst) * i) >> nShift);
                BYTE bB = (BYTE) ((GetRValue(dwSecond) * (nLines - i) + GetRValue(dwFirst) * i) >> nShift);
                // ... then paint with the resulting color
                HBRUSH hBrush = ::CreateSolidBrush(RGB(bR, bG, bB));
                RECT r2 = rcPaint;
                if( bVertical )
                {
                    r2.bottom = rc.bottom - ((i * (rc.bottom - rc.top)) >> nShift);
                    r2.top = rc.bottom - (((i + 1) * (rc.bottom - rc.top)) >> nShift);
                    if( (r2.bottom - r2.top) > 0 ) ::FillRect(hDC, &r2, hBrush);
                }
                else
                {
                    r2.left = rc.right - (((i + 1) * (rc.right - rc.left)) >> nShift);
                    r2.right = rc.right - ((i * (rc.right - rc.left)) >> nShift);
                    if( (r2.right - r2.left) > 0 ) ::FillRect(hPaintDC, &r2, hBrush);
                }
                ::DeleteObject(hBrush);
            }
        }
        if( bAlpha < 255 )
        {
            BLENDFUNCTION bf = { AC_SRC_OVER, 0, bAlpha, AC_SRC_ALPHA };
            lpAlphaBlend(hDC, rc.left, rc.top, cx, cy, hPaintDC, 0, 0, cx, cy, bf);
            ::SelectObject(hPaintDC, hOldPaintBitmap);
            ::DeleteObject(hPaintBitmap);
            ::DeleteDC(hPaintDC);
        }
    }

3【問題修復】DuiLib里面, 對於VerticalLayout有sepheight屬性, 而HorizontalLayout卻又sepwidth, 我覺得有點不合理.

因為常規用法, 是一個HorizontalLayout里面並列幾個VerticalLayout, 而這時, 卻是允許VerticalLayout垂直改高度, 而這時候更需要做到的應該是修改寬度吧.

 目前給拖動SEP的添加了通知主窗口的功能, 這樣, 當LayoutResize的時候, 主窗口就可以做一些自己的響應, 添加了DUI_MSGTYPE_SEPRESIZED消息.

但是, 沒有處理上面抱怨的問題, 因為目前用HorizontalLayout里面放HorizontalLayout也是可以的, 而且改起來會麻煩一點.

4, 【問題修復】TreeNodeUI, 葉節點默認雙擊會出現白色的方塊, 應該是FolderButton的背景圖片或者CheckBtn的。

問題已經查明, 是TreeViewUI::Add函數的問題。

這個函數, will把所有的TreeNode的folder屬性設置成true, 因為你一般肯定會需要非葉節點是有folder屬性的。

這樣, 導致葉節點在你沒有設置folder圖片的時候, 雙擊會出現白色背景。

修改后的代碼如下, 不過還有一些其他細節需要處理處理, 比如還有AddAt, 或者一個相關的修改工作。

    bool CTreeViewUI::Add( CTreeNodeUI* pControl )
    {
        if (!pControl)
            return false;

        if (_tcsicmp(pControl->GetClass(), _T("TreeNodeUI")) != 0)
            return false;

        pControl->OnNotify += MakeDelegate(this,&CTreeViewUI::OnDBClickItem);
        pControl->GetFolderButton()->OnNotify += MakeDelegate(this,&CTreeViewUI::OnFolderChanged);
        pControl->GetCheckBox()->OnNotify += MakeDelegate(this,&CTreeViewUI::OnCheckBoxChanged);
        pControl->SetVisibleCheckBtn(m_bVisibleCheckBtn);
        
        if(m_uItemMinWidth > 0)
            pControl->SetMinWidth(m_uItemMinWidth);

        CListUI::Add(pControl);

        if(pControl->GetCountChild() > 0)
        {
            pControl->SetVisibleFolderBtn(m_bVisibleFolderBtn);
            int nCount = pControl->GetCountChild();
            for(int nIndex = 0;nIndex < nCount;nIndex++)
            {
                CTreeNodeUI* pNode = pControl->GetChildNode(nIndex);
                if(pNode)
                    Add(pNode);
            }
        }
        else
        {
            pControl->SetVisibleFolderBtn(false);
            
        }

        pControl->SetTreeView(this);
        return true;
    }

 5 【問題修復】 DuiLib的CWindowWnd::ShowWindow函數, 里面用了SW_SHOWNORMAL, 假如窗口最大化之后隱藏了, 調用這個函數顯示出來的窗口不是最大化的, 原因參見msdn.

參數我給修改成了SW_SHOW.

6, 【代碼分析】 DuiLib最大化時, 我在WM_SIZE處理過程中獲得的Layout的大小是窗口最大化之前的。

后來發現這和DuiLib的渲染機制有關。

窗口大小改變, 或者空間的可見性修改的時候, 都會重新排布控件。

但是重新排布控件開銷昂貴,所以要推遲到繪制的時候才做。

WM_SIZE在繪制之前, 這是取到的Layout的大小就是舊的, 未更新的。

解決方法, 在CPaintManager處理WM_PAINT消息的代碼最后面, 加入一個通知語句。

SendNotify(m_pRoot, DUI_MSGTYPE_AFTER_PAINT,  0, 0, false); 通知主窗口完成了繪制, 也完成了控件的重新排布。

7, 【功能改進】DuiLib的業務同UI之分離.

如果你做的庫想要給別人用, 就必須要處理好業務邏輯同UI的分離, 不然, 寫起代碼來會很心碎的.

當然可以通過重載控件類的方法進行擴展, 但是, 你會在開發html網頁的時候, 為了你要的功能而去重載html渲染引擎嗎?..

現在我介紹一下我目前使用的方法, 有點像MVC模式.

在CControlUI里面添加一個IController, 每當CControlUI里面有事件觸發, 需要操作業務邏輯的時候, 就對IController進行調用.

而我們實際使用的時候, 會更具需求重載IController, 這樣利用多態性, 就可以調用我們重載的IController類了.

流程是這樣的, 用戶進行界面操作, 觸發對控件的操作, 控件調用IController重載類, IController里面進行業務邏輯處理, 同時可以調用CControlUI進行界面繪制.

這樣, 以后你就就可以把對UI庫的改進和對業務邏輯的適應進行隔離了. 而再也不需要因為不同的業務而去修改或重載你的UI類.

//IContrller聲明

//By Jackie 2013-10-30 這個類, 會被使用者當做業務邏輯類的基本類來用.
class UILIB_API IController
{
public:
    IController(){}
    virtual ~IController(){} //By Jackie 2013-10-30 這個注意虛函數.
    virtual public void SetControl(CControlUI* pControl) = 0;
};

class UILIB_API CControlUI
{
public:
    CControlUI();
    virtual ~CControlUI();
        //...
        //By Jackie 2013-10-30 這個類會被外面的業務邏輯層重載, 里面封裝各種業務邏輯.
    IController* m_pController; 
};



//重載過的IContrller子類
class CPageViewController: public IController
{
public:
    CPageViewController(){}
    ~CPageViewController(){}
    void SetControl(CControlUI* pControl)
    {
        m_pControl = dynamic_cast<CHorizontalLayoutUI*>(pControl);
        pControl->SetController(this);
    }
    void InitPageView(int nPages);
    void NextPage();
    void PrevPage();
    void Turn2Page(int iPage);
    void SetCurrentLocation(char szName[]);

    CHorizontalLayoutUI* m_pControl;
    int m_iCurrentPage;
    int m_nPages;
    char m_szLocationName[STR_SIZE];
};

 8【經驗分享】

DuiLib窗口如何做邊緣的玻璃毛邊效果? 直接用一個帶有透明度邊框的背景圖片就可以, 還要一個附帶條件, 就是Window的 bktrans屬性設置為true,  就這么簡單.

DuiLib Demo里面有個CMenuWnd的類, 為了做毛邊的效果還用了CShadowWnd這樣一個輔助類, 其實這個類是多余的, 直接用DuiLib+透明的貼圖背景就可以直接實現.

 9【經驗分享】 

bkimage的corner屬性, corner屬性是固定圖片4個邊框不讓其進行顏色拉伸, 具體起作用的代碼以后呈上。


免責聲明!

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



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