在windows开发当中做界面的主要技术之一就是使用MFC,通常我们看到的QQ,360,暴风影音这些漂亮的界面都可以用MFC来实现。今天我们来说一下如何用MFC美化对话框,默认情况下,对话框的背景如下:
那么,我们如何将它的背景变成如下界面呢,而且还要保留对话框的移动功能,漂亮背景如下:
为了实现美化对话框背景的效果,我们需要让我们的对话框响应WM_CTLCOLOR消息,每当我们的对话框或者它的子控件需要重绘时,我们的对话框都会收到这个消息,
因此,我们需要为对话框添加WM_CTLCOLOR的消息响应函数,完成对消息的处理,WM_CTLCOLOR的响应函数定义如下:
-
HBRUSH CMFCDialogUIDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
-
{
-
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
-
if (pWnd == this)
-
{
-
return m_bkBrush;
-
}
-
return hbr;
-
}
当我们的对话框需要重绘的时候,我们的对话框就会收到WM_CTLCOLOR消息,然后我们的对话框处理函数会调用OnCtlColor函数来处理该消息,在这个函数中,pDC代表我们要绘制的上下文环境,pWnd代表我们要绘制的窗口指针,nCtlColor代表我们要绘制的窗口类型,在函数的内部我们首先调用父类的OnCtlColor,目的是为了对该消息做默认处理,这是MFC消息响应函数的惯用写法,但是在我们这里,我们不能使用默认处理返回的画刷,所以我们需要做一个特殊的判断,如果pWnd指向的窗口地址是当前对话框,那么我们就返回m_bkBrush,这个画刷是一个图像画刷,它会在我们的对话框客户区绘制我们想让它显示的图片。下面我们看一下m_bkBrush是如何创建的,首先在我们的对话框的头文件中增加一个CBrush变量,变量名是m_bkBrush,然后在对话框的OnInitDialog中初始化它,OnInitDialog的定义如下:
-
BOOL CMFCDialogUIDlg::OnInitDialog()
-
{
-
CDialogEx::OnInitDialog();
-
// 将“关于...”菜单项添加到系统菜单中。
-
// IDM_ABOUTBOX 必须在系统命令范围内。
-
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
-
ASSERT(IDM_ABOUTBOX < 0xF000);
-
CMenu* pSysMenu = GetSystemMenu(FALSE);
-
if (pSysMenu != NULL)
-
{
-
BOOL bNameValid;
-
CString strAboutMenu;
-
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
-
ASSERT(bNameValid);
-
if (!strAboutMenu.IsEmpty())
-
{
-
pSysMenu->AppendMenu(MF_SEPARATOR);
-
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
-
}
-
}
-
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
-
// 执行此操作
-
SetIcon(m_hIcon, TRUE); // 设置大图标
-
SetIcon(m_hIcon, FALSE); // 设置小图标
-
// TODO: 在此添加额外的初始化代码
-
CString strBmpPath = _T(".\\res\\Background.png");
-
CImage img;
-
img.Load(strBmpPath);
-
MoveWindow(0, 0, img.GetWidth(), img.GetHeight());
-
CBitmap bmpTmp;
-
bmpTmp.Attach(img.Detach());
-
m_bkBrush.CreatePatternBrush(&bmpTmp);
-
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
-
}
现在我们看一下,在OnInitDialog中,我们都做了什么,首先,我们创建了一个CString变量strBmpPath,用它指向我们的图片文件,然后我们创建了一个CImage变量img,这个变量可以方便的加载各种格式的图像文件,所以我们使用它的目的是为了方便加载png格式的文件,MoveWindow的目的是为了调整我们的对话框客户区的大小,使客户区的大小与图片的大小一致,然后我们创建了一个CBitmap类型变量bmpTmp,使用它是因为CBrush的成员函数CreatePatternBrush的参数要求输入这种类型的参数,所以必须将img转换成CBitmap,转换的方法是bmpTmp.Attach(img.Detach()),img.Detach会释放图像的句柄,并且返回这个句柄,bmpTmp使用Attach绑定img返回的图像句柄,从而完成了对象类型的转换,最后调用CreatePatternBrush,这个函数的功能是使用传递给它的图像创建一个图像画刷,然后在OnCtlColor中,使用它填充对话框的背景,程序运行效果如下:
现在虽然程序的客户区已经变成了对话框的背景,但是对话框原来的标题栏和背景图片的标题栏重复,看起来很别扭,通过设置对话框的Border属性可以消除原来的标题栏,设置如下:
Border:None
再次编译,运行程序,效果如下:
现在的对话框背景已经和我们设想的基本一致,还有一点小瑕疵,大家仔细观察对话框的底边,左下角和右下角有多于的像素,下面我们通过代码消除它。
在OnInitDialog尾部追加如下代码:
-
CRgn rgnTmp;
-
RECT rc;
-
GetClientRect(&rc);
-
rgnTmp.CreateRoundRectRgn(rc.left + 3, rc.top + 3, rc.right - rc.left - 3, rc.bottom-rc.top -3, 6, 6);
-
SetWindowRgn(rgnTmp, TRUE);
通过以上的代码可以让对话框变成一个圆角矩形,这样就可以去掉边角的点,程序最终运行效果如下:
现在这个对话框的背景已经完全符合我们的要求,但是它现在不能拖动,因为它的标题栏是假的,所以,我们最后一个目标就是让这个窗口可以拖动,如何才能让它移动呢?
Windows只允许我们拖动对话框的标题栏,当我们的鼠标在对话框上拖动的时候,对话框会收到一个WM_NCHITTEST消息,默认的消息处理函数会判断当前的鼠标是否在对话框的标题栏,如果在就返回HTCAPTION标志,否则就返回其它标志,当返回HTCAPTION标志的情况下,系统就会允许对话框拖动,所以我们可以欺骗windows系统,让WM_NCHITTEST的响应函数永远返回HTCAPTION标志就可以了,为对话框添加WM_NCHITTEST响应函数,代码如下:
-
LRESULT CMFCDialogUIDlg::OnNcHitTest(CPoint point)
-
{
-
// TODO: 在此添加消息处理程序代码和/或调用默认值
-
LRESULT ret = CDialogEx::OnNcHitTest(point);
-
return (ret == HTCLIENT) ? HTCAPTION : ret;
-
}
重新编译代码,现在的对话框背景已经美化完成,并且这个对话框可以拖动。