Jackiesteed

www.github.com/jackiesteed

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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个边框不让其进行颜色拉伸, 具体起作用的代码以后呈上。

posted on 2013-09-29 18:33  Jackiesteed  阅读(5875)  评论(5编辑  收藏  举报