在MFC应用程序中,有时会遇到需要让指定的控件实现自绘。但是看该控件的事件,没有一个像是能承担这种责任的。
我们都知道控件也是窗口,也都有消息循环。所以:
方案一:写个新类,继承自某个窗口类,在它的WM_PAINT消息中实现自绘。这种方法需要定义一个新类,不是太方便。
方法二:利用SetWindowLong修改该控件的消息处理函数,在WM_PAINT消息中实现自绘。本文就采用此方案。
------------------------------------------------------------
方案二实现过程,环境:Win XP + VC2010(MFC)
完整源码
1. 界面:
2. 为Button1添加点击事件
void CdelDlg::OnBnClickedButton1() { m_bgIndex = (m_bgIndex + 1) % 7; Invalidate(); // 刷新背景 }
3. 添加2个全局变量和2个全局函数(绘图函数和子控件新的消息处理函数)
// 定义全局变量和全局函数 WNDPROC oldProc_PIC1 = 0; // 保存IDC_PIC1控件默认的消息处理函数地址 CString imgPath; // 保存背景图片地址 BOOL DrawPic(HWND hWnd) // 在指定的控件上画图 { CImage img; if(SUCCEEDED(img.Load(imgPath))) { CWnd *pWnd = CWnd::FromHandle(hWnd); CPaintDC dc(pWnd); // dc必须用指定的控件窗口来初始化,否则将看不到绘图结果 CRect rect; pWnd->GetClientRect(rect); // 获取控件的大小 img.Draw(dc.m_hDC, rect); return TRUE; } return FALSE; // 绘图失败 } LRESULT NewProc_PIC1(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) // IDC_PIC1控件对应的新的消息函数 { switch (message) { case WM_PAINT: if(DrawPic(hWnd)) return S_OK; else break; default: break; } return CallWindowProc(oldProc_PIC1, hWnd, message, wParam, lParam); // 调用默认的消息函数 }
BOOL CdelDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 oldProc_PIC1 = (WNDPROC)SetWindowLong(GetDlgItem(IDC_PIC1)->m_hWnd, GWL_WNDPROC, (LONG)NewProc_PIC1); // 为控件设置新的消息处理函数 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE }
5. 处理对话框的WM_PAINT和子控件(IDC_PIC1)的WM_PAINT消息
5.1 主对话框的WM_PAINT消息处理
void CdelDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CString file[] = {"1.jpg", "2.jpg", "3.jpg", "11.jpg", "12.bmp", "Smiley.png", "11.gif"}; m_imgPath.Format("res\\%s", file[m_bgIndex]); imgPath = m_imgPath; DrawPic(this->m_hWnd); // 在控件上绘图 //CDialogEx::OnPaint(); } }
点滴经验:
1. 本来我想在对话框的PreTranslateMessage()中拦截子控件的WM_PAINT消息的,但是经过试验才知道,窗口在第一次启动时不会触发WM_PAINT消息的,只有失去焦点或重新获得焦点时才会触发WM_PAINT消息。所以放弃使用该方法。
2. CPaintDC的构造函数有个参数的,一般看到的代码中都是用dc(this)来初始化的,因为这个代码是在类的成员函数中,所以没问题。另外在进行绘图时,所指定的rect要与初始化dc的这个参数(如pWnd)对应的坐标(pWnd->GetClientRect(rect))相对应,否则可能看不到绘图结果或绘图的位置不对。
3. 在对话框的自绘处理中,要屏蔽掉其基类的OnPaint(),否则自绘上去的图形将被覆盖。但是,将基类的OnPaint()语句放在自绘语句的最后,是可以的,即我文中的代码可以取消对CDialogEx::OnPaint()的注释。