MFC窗口需要绑定窗口资源或者重载OnCreate()动态添加,为了窗口类的独立性,因此采用绑定窗口资源的方式让生成的窗口类继承WzdDialog类的方式进行自绘。
头文件:
#include "WzdImage.h"
#include "WzdButton.h"
class CWzdDialog : public CDialog
{
DECLARE_DYNAMIC(CWzdDialog)
public:
CWzdDialog(UINT nIDTemplate, CWnd* pParent = NULL); // 标准构造函数
virtual ~CWzdDialog();
// 加载背景图片
bool LoadBackImg(LPCTSTR pszResourcePath);
bool LoadBackImg(HINSTANCE hInstance, LPCTSTR pszResourceName);
// 设置透明度
void SetAlphaDepth(unsigned int nAlphaDepth);
protected:
CWzdImage m_backImg; // 窗口背景
CWzdButton m_btClose; // 关闭按钮
CWzdButton m_btMax; // 最大化按钮
CWzdButton m_btMin; // 最小化按钮
bool m_isInitButton; // 是否已经初始化按钮
bool m_isZoomed; // 是否已经最大化
bool m_bExtrude; // 能否拉伸
unsigned int m_alphaDepth; // 透明度
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 提供接口给继承窗口类进行额外的绘制
virtual void OnClientDraw(CDC*pDC,int nWidth,int nHeight){}
// 重写函数
virtual BOOL OnInitDialog();
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// 消息响应
afx_msg void OnPaint();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point);
afx_msg LRESULT OnNcHitTest(CPoint point);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
};
};
首先需要WzdImage类进行一些图片绘制,所以包含WzdImage.h。需要关闭、最大化(恢复)、最小化按钮,所以需要WzdButton.h,这个按钮类下一篇再讲。
WzdDialog窗口类继承了CDialog类,构造函数需要提供窗口资源ID(nIDTemplate)。
同样有两种加载图片的方式:图片路劲或资源名称。
窗口需要有3个标准按钮:关闭、最大化(恢复)、最小化。
3个按钮是否初始化,保证窗口正常;是否最大化,进行窗口缩放;能否拉伸,窗口能否拉大缩小。
OnClientDraw(),支持继承类自己绘制其他东西。
SetAlphaDepth(),支持窗口透明度,默认为不透明(255)。
从构造函数开始,简单初始化一些变量
CWzdDialog::CWzdDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
: CDialog(nIDTemplate, pParent)
, m_isInitButton(false)
, m_isZoomed(false)
, m_bExtrude(true)
, m_alphaDepth(255)
{
}
窗口初始化函数: 改变窗口旧样式,变为无标题栏无边框的窗口,其中WS_EX_LAYERED支持窗口透明。初始化按钮。
BOOL CWzdDialog::OnInitDialog()
{
CDialog::OnInitDialog();
DWORD dwStyle = GetStyle();
DWORD dwNewStyle = WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX |
WS_MAXIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
dwNewStyle &= dwStyle;
SetWindowLong(m_hWnd, GWL_STYLE, dwNewStyle);
DWORD dwExStyle = GetExStyle();
DWORD dwNewExStyle = WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
dwNewExStyle &= dwExStyle;
SetWindowLong(m_hWnd, GWL_EXSTYLE, dwNewExStyle | WS_EX_LAYERED);
// 默认开始不透明
SetLayeredWindowAttributes(0, m_alphaDepth, LWA_ALPHA);
// 初始化按钮
CRect rcControl(0,0,0,0);
m_btClose.Create(NULL,WS_CHILD | WS_VISIBLE, rcControl, this, IDCANCEL);
m_btClose.SetButtonImage(_T(".//res//close.png"));
m_btMax.Create(NULL, WS_CHILD | WS_VISIBLE, rcControl, this, IDC_BTN_MAX);
m_btMax.SetButtonImage(_T(".//res//maxsize.png"));
m_btMin.Create(NULL,WS_CHILD | WS_VISIBLE, rcControl, this, IDC_BTN_MIN);
m_btMin.SetButtonImage(_T(".//res//minsize.png"));
m_isInitButton = true;
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
重载OnPaint(),用双缓冲来解决重画时的画面闪烁。双缓冲大体的思路是先将画面保存到一张图片中,最后才显示到桌面上。
void CWzdDialog::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rcClient;
CDC memDC; // 用于缓冲作图的内存DC
CBitmap bmp; // 内存中承载临时图象的位图
GetClientRect(&rcClient);
memDC.CreateCompatibleDC(&dc);
bmp.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
memDC.SelectObject(&bmp); // 将位图选择进内存DC
memDC.FillSolidRect(&rcClient, dc.GetBkColor()); // 按原来背景填充客户区
m_backImg.DrawImage(&memDC, 0, 0, rcClient.Width(), rcClient.Height());
// 将缓冲区内容绘画到界面
dc.BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY);
//清理资源
memDC.DeleteDC();
bmp.DeleteObject();
}
同时,还要重载OnEraseBkgnd(),直接返回TRUE防止清除背景带来的闪烁。
BOOL CWzdDialog::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
return CDialog::OnEraseBkgnd(pDC);
}
在初始化按钮的时候,按钮的位置都是0,需要动态改变按钮的位置,响应WM_SIZE消息,添加OnSize()函数。uFlags设定不改变按钮的一些状态,LockWindowUpdate()和UnlockWindowUpdate()让窗口在大小改变完成后才重画界面。DeferWindowPos()系列函数用来移动按钮位置。
void CWzdDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (!m_isInitButton) return;
const UINT uFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOSIZE;
LockWindowUpdate(); // 锁定屏幕
// 移动控件
HDWP hDwp = BeginDeferWindowPos(32);
CRect rcButton;
m_btClose.GetWindowRect(&rcButton);
if (m_isZoomed) // 最大化时显示恢复样式,反之显示最大化样式
m_btMax.SetButtonImage(_T(".//res//restore.png"));
else
m_btMax.SetButtonImage(_T(".//res//maxsize.png"));
DeferWindowPos(hDwp, m_btClose, NULL, cx - rcButton.Width() - 2, 2, 1, 0, uFlags);
DeferWindowPos(hDwp, m_btMax, NULL, cx - rcButton.Width() * 2 - 2, 2, 0, 0, uFlags);
DeferWindowPos(hDwp, m_btMin, NULL, cx - rcButton.Width() * 3 - 2, 2, 0, 0, uFlags);
EndDeferWindowPos(hDwp);
// 重画界面
Invalidate(FALSE);
UpdateWindow();
UnlockWindowUpdate();
}
按钮的显示完成了,接着就是响应。重载OnCommand函数
BOOL CWzdDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{
switch (LOWORD(wParam))
{
case IDC_BTN_MAX: //最大化消息
{
static CRect rcClient(0,0,0,0);
if (m_isZoomed)
{
m_isZoomed = false;
MoveWindow(&rcClient);
}
else
{
GetWindowRect(&rcClient); // 保存现在的窗口位置
CRect rc;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
m_isZoomed = true;
MoveWindow(&rc);
}
break;
}
case IDC_BTN_MIN: //最小化消息
{
ShowWindow(SW_MINIMIZE);
break;
}
}
return CDialog::OnCommand(wParam, lParam);
}
接着实现窗口的拖动,重载OnLButtonDown()函数模拟左键点击拖动。
void CWzdDialog::OnLButtonDown(UINT nFlags, CPoint point)
{
if (!m_isZoomed)
{
// 模拟左键点击拖动
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x,point.y));
return;
}
CDialog::OnLButtonDown(nFlags, point);
}
最后是拉动边框改变窗口大小,先重载OnNcHitTest()模拟鼠标移动到边框时,从而显示双箭头。BORDER_WIDTH时边框宽度的宏定义。
LRESULT CWzdDialog::OnNcHitTest(CPoint point)
{
if (m_bExtrude && !m_isZoomed)
{
CRect rcWindow;
GetWindowRect(&rcWindow);
if ((point.x <= rcWindow.left+BORDER_WIDTH) && (point.y>BORDER_WIDTH) && (point.y<rcWindow.bottom-BORDER_WIDTH*2) )
return HTLEFT;
else if ((point.x >= rcWindow.right-BORDER_WIDTH) && (point.y>BORDER_WIDTH) && (point.y<rcWindow.bottom-BORDER_WIDTH*2) )
return HTRIGHT;
else if ((point.y <= rcWindow.top+BORDER_WIDTH) && (point.x>BORDER_WIDTH) && (point.x<rcWindow.right-BORDER_WIDTH*2))
return HTTOP;
else if ((point.y >= rcWindow.bottom-BORDER_WIDTH) && (point.x>BORDER_WIDTH) && (point.x<rcWindow.right-BORDER_WIDTH*2))
return HTBOTTOM;
else if ((point.x <= rcWindow.left+BORDER_WIDTH*2) && (point.y <= rcWindow.top+BORDER_WIDTH*2))
return HTTOPLEFT;
else if ((point.x >= rcWindow.right-BORDER_WIDTH*2) && (point.y <= rcWindow.top+BORDER_WIDTH*2))
return HTTOPRIGHT;
else if ((point.x <= rcWindow.left+BORDER_WIDTH*2) && (point.y >= rcWindow.bottom-BORDER_WIDTH*2))
return HTBOTTOMLEFT;
else if ((point.x >= rcWindow.right-BORDER_WIDTH*2) && (point.y >= rcWindow.bottom-BORDER_WIDTH*2))
return HTBOTTOMRIGHT;
else
return CWnd::OnNcHitTest(point);
}
return CDialog::OnNcHitTest(point);
}
再模拟鼠标点击边框进行拖动,重载OnNcLButtonDown(),发送响应消息进行模拟。
void CWzdDialog::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
if(m_bExtrude)
{
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));
else if (nHitTest==HTCAPTION)
SendMessage(WM_SYSCOMMAND, SC_MOVE | WMSZ_TOPLEFT, MAKELPARAM(point.x, point.y));
}
CDialog::OnNcLButtonDown(nHitTest, point);
}