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個邊框不讓其進行顏色拉伸, 具體起作用的代碼以后呈上。