1. 程式人生 > >開發介面之視窗邊框重繪

開發介面之視窗邊框重繪

基於上篇已經解決了富圖軟體視窗child的效果。這邊主要仿照其重繪邊框——即活動視窗邊框高亮,本文不講標題欄/border重繪(難度是比Client繪製要難,但是社群也有人做了相應的demo),基於富圖考慮,它不要標題欄,我們也用無邊框視窗繪製就好了。

1.邊框及標題欄繪製

void 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);
	}
	
}
2.此時還不能move/resize視窗,於是重寫OnNcHitTest
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;
}
這裡本人發現一個有趣的問題,當視窗為子視窗的時候,至此,是可以實現移動/resize了。但是如果你modify成popup了,只能移動,不能resize。實質是popup會丟失訊息,於是手動加上
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));
}
這是popup視窗resize也ok了。

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。程式碼上面已經提供,否則還是會出現邊框被子視窗遮擋的現象。最後上個效果圖

標題欄再加上關閉按鈕: