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