基于上篇已经解决了富图软件窗口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。代码上面已经提供,否则还是会出现边框被子窗口遮挡的现象。最后上个效果图
标题栏再加上关闭按钮: