今天还是继续昨天的事情:自己堆砌MFC。我在WM_PAINT的消息响应函数OnPaint()里面调用DC去drawtext,可是发现他老是重绘。于是找原因,原来是这样的:我用的是:
CRect rect;
GetClientRect(&rect);
CPaintDC myDC(this);// 这里不能用CClientDC,只有CPaintDC才能把WM_PAINT消息从消息联里面删除,否则用CClientDC会不断重绘
myDC.DrawText(_T("nice to meet you"),&rect,DT_SINGLELINE| DT_CENTER|DT_VCENTER);
//MessageBoxW(_T("hao la"),_T("my lover"),MB_OK|MB_YESNOCANCEL|MB_DEFBUTTON3);// MB_DEFBUTTON3用来指定第几个按钮为默认按钮
GetClientRect(&rect);
CPaintDC myDC(this);// 这里不能用CClientDC,只有CPaintDC才能把WM_PAINT消息从消息联里面删除,否则用CClientDC会不断重绘
myDC.DrawText(_T("nice to meet you"),&rect,DT_SINGLELINE| DT_CENTER|DT_VCENTER);
//MessageBoxW(_T("hao la"),_T("my lover"),MB_OK|MB_YESNOCANCEL|MB_DEFBUTTON3);// MB_DEFBUTTON3用来指定第几个按钮为默认按钮
而这里要用CPaintDC,原因上面代码的注释里面已经说了,但是为什么只有
CPaintDC才能把消息从消息队列里面删除呢?其实准确的来说,从消息队列里面删除该消息的说法是不正确的,真正的原因在下面:
CPaintDC的构造函数中调用BeginPaint获得客户区重绘的DC,析构函数中调用EndPaint。这样就明白了吧,呵呵。在响应WM_PAINT消息时应调用或间接调用BeginPaint、EndPaint函数,使窗口变为“有效”。因为一旦客户区无效时,windows就会给该窗口发送WM_PAINT消息,而BeginPaint的作用就是使得该区域有效,并且一般在BeginPaint和EndPaint函数对中间调用GetDC来获取设备描述表。否则像我上面写的代码,DrawText完了改区域还是无效的,于是继续发送消息,继续调用重绘函数(若是还是有疑问,请参见《Windows 程序设计》,原版英文名为(《Programming Windows》)),程序不能正常运行。这就是所谓“只有
CPaintDC才能把消息从消息联里面删除 ”的意思。
那么为什么在MFC里面非WM_PAINT的消息响应函数里面就可以用CClientDC呢?废话嘛,你CClientDC没有调用BeginPaint和EndPaint函数对,那就让程序继续发送WM_PAINT消息好了,但是WM_PAINT的消息响应函数里面没有什么实质的内容,那就意味着没什么变化了。哎,好像有点不对劲,这个解释貌似不是很合理,这样的话程序不是很耗时间吗?因为线程老是要过来给它发送消息,调用函数啊,这里是怎么解决的呢?
暂时还不太理解,以后再回来修改。
进一步说明:
当window接收到WM_PAINT消息后,开始调用BeginPaint时就发出WM_ERASEBKGND消息,并调用相关的消息响应函数去搜索类的背景色然后去填充客户区。这里如果我自己去调用该消息响应函数,那么我就可以指定该背景色,甚至是一个图片。(If you don't process WM_ERASEBKGND messages yourself, Windows processes them for you by retrieving the class background brush and using it to fill the window's client area. (You can create custom window backgrounds—for example, backgrounds formed from bitmap images—by processing WM_ERASEBKGND messages yourself and returning a nonzero value. The nonzero return prevents Windows from painting the background and overwriting what you wrote.) )
CClientDC的构造函数调用GetDC获取窗口客户区DC,析构函数中调用ReleaseDC,当需要对窗口客户区操作时使用。
CWindowDC的构造函数调用GetWindowDC获取窗口DC(包含非客户区),析构函数中调用ReleaseDC,当需要对窗口非客户区操作时使用。
这几个CDC派生类的构造函数中的参数指定要获取哪个窗口的DC,this就是当前对象关联的窗口,在CMyView类中就是获取该视图的DC, 如果指定一个CWnd派生类对象的指针,则是获取该对象关联的窗口的DC,对于 框架类,其客户区被子窗口全部覆盖,在其上面输出是看不到的。
CClientDC的构造函数调用GetDC获取窗口客户区DC,析构函数中调用ReleaseDC,当需要对窗口客户区操作时使用。
CWindowDC的构造函数调用GetWindowDC获取窗口DC(包含非客户区),析构函数中调用ReleaseDC,当需要对窗口非客户区操作时使用。
这几个CDC派生类的构造函数中的参数指定要获取哪个窗口的DC,this就是当前对象关联的窗口,在CMyView类中就是获取该视图的DC, 如果指定一个CWnd派生类对象的指针,则是获取该对象关联的窗口的DC,对于 框架类,其客户区被子窗口全部覆盖,在其上面输出是看不到的。
PS:关于OnPaint和OnEraseBkGnd之间的联系参见如下:(来自Tr0j4n在CSDN里面的回复)
问题是这样产生的.在OnEraseBkGnd中,如果你不调用原来缺省
的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd
函数,这时就和窗口缺省的背景刷相关了.缺省的
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况
下是白刷),而随后你又自己重画背景造成屏幕闪动.
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数
是FALSE,则不会重刷背景.
所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回.
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一
下,所以不是彻底的解决方法)
都挺简单的.
------------------------------------------------------
在MFC中 任何一个window组件的绘图 都是放在这两个member function中
在设定上 OnEraseBkgnd()是用来画底图的 而OnPaint()是用来画主要对象的
举例说明 一个按钮是灰色的 上面还有文字
则OnEraseBkgnd()所做的事就是把按钮画成灰色
而OnPaint()所做的事 就是画上文字
既然这两个member function都是用来画出组件的
那为何还要分OnPaint() 与 OnEraseBkgnd() 呢
其实OnPaint() 与 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间
因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程序有空闲的时候才会被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次
如果我们是一个在做图形化使用者接口的人
常会需要把一张美美的图片设为我们dialog的底图
把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题
比方说拖曳一个窗口在我们做的dialog上面一直移动
则dialog会变成灰色 直到动作停止才恢复
这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd()
OnEraseBkgnd()就把dialog画成灰色
而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去
这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function
如下所示
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话
程序便不会画上灰色的底色了
比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做
如下所示
// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetUpdateRect(&rc);
CDC srcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);
pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
return TRUE;
}
特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 会把不该重画的地方重画
的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd
函数,这时就和窗口缺省的背景刷相关了.缺省的
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况
下是白刷),而随后你又自己重画背景造成屏幕闪动.
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数
是FALSE,则不会重刷背景.
所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回.
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一
下,所以不是彻底的解决方法)
都挺简单的.
------------------------------------------------------
在MFC中 任何一个window组件的绘图 都是放在这两个member function中
在设定上 OnEraseBkgnd()是用来画底图的 而OnPaint()是用来画主要对象的
举例说明 一个按钮是灰色的 上面还有文字
则OnEraseBkgnd()所做的事就是把按钮画成灰色
而OnPaint()所做的事 就是画上文字
既然这两个member function都是用来画出组件的
那为何还要分OnPaint() 与 OnEraseBkgnd() 呢
其实OnPaint() 与 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间
因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程序有空闲的时候才会被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次
如果我们是一个在做图形化使用者接口的人
常会需要把一张美美的图片设为我们dialog的底图
把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题
比方说拖曳一个窗口在我们做的dialog上面一直移动
则dialog会变成灰色 直到动作停止才恢复
这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd()
OnEraseBkgnd()就把dialog画成灰色
而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去
这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function
如下所示
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话
程序便不会画上灰色的底色了
比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做
如下所示
// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetUpdateRect(&rc);
CDC srcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);
pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
return TRUE;
}
特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 会把不该重画的地方重画