開發介面之視窗邊框重繪
阿新 • • 發佈:2019-01-22
基於上篇已經解決了富圖軟體視窗child的效果。這邊主要仿照其重繪邊框——即活動視窗邊框高亮,本文不講標題欄/border重繪(難度是比Client繪製要難,但是社群也有人做了相應的demo),基於富圖考慮,它不要標題欄,我們也用無邊框視窗繪製就好了。
1.邊框及標題欄繪製
2.此時還不能move/resize視窗,於是重寫OnNcHitTestvoid CDlgFrm::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: 在此處新增訊息處理程式程式碼 // 不為繪圖訊息呼叫 CDialogEx::OnPaint() //畫邊框 HPEN newPen = CreatePen(PS_SOLID, 2, m_colFrm); HPEN oPen = (HPEN)dc.SelectObject(newPen); CRect rc, rc2; GetClientRect(rc); rc.left = rc.top = 2; dc.Rectangle(rc); dc.SelectObject(oPen); //畫標題欄 oPen = (HPEN)dc.SelectObject(GetStockObject(NULL_PEN)); HBRUSH newBrsh = CreateSolidBrush(RGB(130, 130, 130)); HBRUSH oldBrsh = (HBRUSH)dc.SelectObject(newBrsh); dc.Rectangle(3, 3, rc.Width()+1, 33); dc.SelectObject(oldBrsh); ::DeleteObject(newBrsh); dc.SelectObject(oPen); //畫標題文字 if (m_strTitle.IsEmpty() == FALSE) { rc = CRect(0, 0, 100, 30); dc.SetTextColor(RGB(255, 250, 250)); dc.SetBkMode(TRANSPARENT); dc.DrawText(m_strTitle, rc, DT_VCENTER | DT_CENTER | DT_SINGLELINE); } }
這裡本人發現一個有趣的問題,當視窗為子視窗的時候,至此,是可以實現移動/resize了。但是如果你modify成popup了,只能移動,不能resize。實質是popup會丟失訊息,於是手動加上LRESULT CDlgFrm::OnNcHitTest(CPoint point) { // TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值 UINT nHitTest = CDialogEx::OnNcHitTest(point); CPoint pt(0, 0); ClientToScreen(&pt); if (nHitTest == HTCLIENT && point.y - pt.y < 32) { nHitTest = HTCAPTION; } RECT rcWindow; ::GetWindowRect(m_hWnd, &rcWindow); // 最好將四個角的判斷放在前面 if (point.x <= rcWindow.left + RESIZE_REGION_SIZE && point.y <= rcWindow.top + RESIZE_REGION_SIZE) return HTTOPLEFT; else if (point.x >= rcWindow.right - RESIZE_REGION_SIZE && point.y <= rcWindow.top + RESIZE_REGION_SIZE) return HTTOPRIGHT; else if (point.x <= rcWindow.left + RESIZE_REGION_SIZE && point.y >= rcWindow.bottom - RESIZE_REGION_SIZE) return HTBOTTOMLEFT; else if (point.x >= rcWindow.right - RESIZE_REGION_SIZE && point.y >= rcWindow.bottom - RESIZE_REGION_SIZE) return HTBOTTOMRIGHT; else if (point.x <= rcWindow.left + RESIZE_REGION_SIZE) return HTLEFT; else if (point.x >= rcWindow.right - RESIZE_REGION_SIZE) return HTRIGHT; else if (point.y <= rcWindow.top + RESIZE_REGION_SIZE) return HTTOP; else if (point.y >= rcWindow.bottom - RESIZE_REGION_SIZE) return HTBOTTOM; return nHitTest; }
這是popup視窗resize也ok了。void CDlgFrm::OnNcLButtonDown(UINT nHitTest, CPoint point) { // TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值 CDialogEx::OnNcLButtonDown(nHitTest, point); if (nHitTest == HTTOP) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTBOTTOM) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOM, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTLEFT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_LEFT, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTRIGHT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_RIGHT, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTTOPLEFT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPLEFT, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTTOPRIGHT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPRIGHT, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTBOTTOMLEFT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMLEFT, MAKELPARAM(point.x, point.y)); else if (nHitTest == HTBOTTOMRIGHT) SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT, MAKELPARAM(point.x, point.y)); }
3.視窗resize陰影嚴重需要update
void CDlgFrm::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
// TODO: 在此處新增訊息處理程式程式碼
if (m_pDlg)
{
//::SendMessage(m_pDlg->m_hWnd, WM_SIZE, nType, MAKELPARAM(cx, cy));
m_pDlg->SetWindowPos(NULL, 0, 0, cx - 4, cy - 32 - 2, SWP_NOMOVE);
m_pDlg->Invalidate();
}
Invalidate();
}
其實至此,視窗繪製是完了,但是如果視窗中有子視窗或者控制元件,你會發現,當縮小到控制元件交叉時,會有邊框被控制元件覆蓋的效果如下圖:解決上圖有兩種方法:
1.使用自己管理的透明對話方塊(邊框不透明)去覆蓋在視窗上,然後讓該對話方塊跟著Active視窗移動,resize以及視窗非Active時隱藏。
CRect rc;
GetWindowRect(rc);
if (m_dlgBorder.Create(CDlgBorder::IDD, this))
{
m_dlgBorder.m_hWndOwner = m_hWnd;
m_dlgBorder.SetWindowPos(this, 0, 0, rc.Width(), rc.Height(), SWP_NOZORDER);
m_dlgBorder.ModifyStyleEx(NULL, WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT);
SetWindowLong(m_dlgBorder.m_hWnd, GWL_EXSTYLE,
GetWindowLongPtr(m_dlgBorder.m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
m_dlgBorder.SetLayeredWindowAttributes(RGB(255, 255, 255), (255 * 0) / 100, LWA_COLORKEY/*LWA_ALPHA*/);
m_dlgBorder.ShowWindow(SW_SHOW);
int a = 0;
}
注意該視窗要透明一定是popup屬性,擴充套件屬性必須有LAYERED|NOACTIVATE|TRANSPARENT,最後SetLayeredWindowAttributes(...)
設定成透明。至於透明視窗中的程式碼就是繪製邊框/重繪重新整理了
void CDlgBorder::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此處新增訊息處理程式程式碼
// 不為繪圖訊息呼叫 CDialogEx::OnPaint()
if (IsWindow(m_hWndOwner))
{
HWND m_hOwnerParent = ::GetParent(m_hWndOwner);
HPEN newPen = CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
HPEN oPen = (HPEN)dc.SelectObject(newPen);
CRect rc,rc2;
GetClientRect(rc);
rc.left = rc.top = 2;
::GetClientRect(m_hOwnerParent, rc2);
ClientToScreen(rc2);
//if (rc.TopLeft)
//dc.MoveTo()
dc.Rectangle(rc);
dc.SelectObject(oPen);
}
}
void CDlgBorder::DoPaintBorder()
{
CRect rc;
if (IsWindow(m_hWndOwner))
{
::GetWindowRect(m_hWndOwner, rc);
SetWindowPos(0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER);
}
}每次resize,move後呼叫DoPaintBorder更新視窗位置大小即可。
但是當視窗移動到邊框的時候因為popup屬性還會顯示出來,所以,還要將該視窗修復一下,方式兩種,1,自己計算邊框控制繪圖越界。2,將透明視窗設定成該視窗的父視窗的子視窗(也是該視窗的兄弟視窗)即可。
2.使用父視窗重繪邊框,再在其客戶區放置不可移動的子視窗,再在子視窗上放控制元件。相當於多巢狀一層子視窗。然後讓子視窗跟著一起resize。
如文章開始提到的封裝一個CDlgFrm當容器,但要注意的是OnSize中一定要將其子視窗一起resize。程式碼上面已經提供,否則還是會出現邊框被子視窗遮擋的現象。最後上個效果圖
標題欄再加上關閉按鈕: