在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;elsebreak;default:break;}return CallWindowProc(oldProc_PIC1, hWnd, message, wParam, lParam); // 调用默认的消息函数
}
4. 在对话框的OnInitDialog()中为子控件IDC_PIC1 指定新的消息循环函数
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();}
}
5.2 子控件(IDC_PIC1)的WM_PAINT消息处理,即步骤3中的NewProc_PIC1()
点滴经验:
1. 本来我想在对话框的PreTranslateMessage()中拦截子控件的WM_PAINT消息的,但是经过试验才知道,窗口在第一次启动时不会触发WM_PAINT消息的,只有失去焦点或重新获得焦点时才会触发WM_PAINT消息。所以放弃使用该方法。
2. CPaintDC的构造函数有个参数的,一般看到的代码中都是用dc(this)来初始化的,因为这个代码是在类的成员函数中,所以没问题。另外在进行绘图时,所指定的rect要与初始化dc的这个参数(如pWnd)对应的坐标(pWnd->GetClientRect(rect))相对应,否则可能看不到绘图结果或绘图的位置不对。
3. 在对话框的自绘处理中,要屏蔽掉其基类的OnPaint(),否则自绘上去的图形将被覆盖。但是,将基类的OnPaint()语句放在自绘语句的最后,是可以的,即我文中的代码可以取消对CDialogEx::OnPaint()的注释。