VC如何实现完全无闪烁的界面自绘,别说什么双缓存,也不要界面库 使用DirectUI的界面渲染效果非常好,比如QQ,飞信这种,但我发现比如酷我音乐盒和360安全卫士,仍然是采用ATL和win32实现的,因为用Spy++可以探测到窗体句柄,但是它们都没有丝毫闪烁。我在使用GDI自绘的时候总会有或多或少的闪烁,双缓存绘制也是。要完全无闪烁该怎么办才好? 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 用双缓冲完全可以无闪烁的实现类似酷我、360的这种界面,可能楼主的双缓冲实现方法有问题吧,或者遗漏了什么没处理,比如WM_ERASEBKGND。 你把自己的代码贴出来 看下就知道了,不要说双缓存不行,可能是你没用对或者理解错了要解决闪烁和拖动卡,OnPaint这个函数里面耗时不能超过100毫秒也就是说你不能在这个函数里执行太多的计算,基于此 你使用一个缓冲在OnPaint里直接拷贝到屏幕DC,这个速度是很快的其二、 不要随意的去刷背景,频繁刷背景可能会闪的 谢谢那你的指点,因为我还重绘了NC区域,代码很多,所以不好贴,我把代码上传了,如果可以的话你看看该怎么修改好,就是希望没有任何闪烁。另外如果你有别的经验的啊希望不吝赐教啊http://download.csdn.net/source/3565277 麻烦你看看我的绘图代码,有点多,不好在这里贴,谢谢!http://download.csdn.net/source/3565277 想看看,还要1个资源分..算了.双缓存也好单缓存也好,重要的分清Invalidate(true)跟Invalidate(false).数据改变需要重绘用false.窗口布局改变使你画的东西填不满了,用true有人说把窗口背景刷设成NULL,没必要. 闪烁最根本的原因来源于背景重绘,要尽量避免在设备DC上重绘背景,如果使用内存DC,将内存DC设置和窗口一样大,所有的绘图包括背景刷新,都在内存DC上完成,然后BitBlt到设备DC。(曾经试过,在内存DC上绘10000条的直线(随机取点,最大窗口)整个动作完成也都在100ms以内,不会感觉有闪烁(bitblt还是很快的)) 麻烦你看看我的绘图代码,有点多,不好在这里贴,谢谢!http://download.csdn.net/source/3565277 该怎么修改好? 少文件:fatal error C1083: 无法打开包括文件:“../Common/RichEditCtrlOpt.h”: No such file or directory 完全同意,尽量不要使整个窗口失效。例子:没有用双缓冲,但是不闪。http://download.csdn.net/source/3082195 闪烁的本质是屏幕上交替显示反差比较大的颜色造成的视觉感受原因可能是1、窗口在响应WM_PAINT消息时,(在调用BeginPaint()过程中)会send一个WM_ERASEBKGND消息,默认对WM_ERASEBKGND的处理是用窗口的背景刷子填充失效区域。如果默认窗口背景刷子的颜色和你要绘制的内容的颜色反差较大,就会产生闪烁的效果。解决的方法,设置背景刷子为空,或者自己响应WM_ERASEBKGND消息,直接返回TRUE2、你要在窗口上绘制的背景色和前景色反差较大,过程又比较长的话,也会产生闪烁效果。解决的方法就是使用双缓冲3、父窗口没有设置WS_CLIPCHILDREN属性的话,会先自己重绘子窗口的区域再让子窗口重绘,也可能造成闪烁 在一次开发过程中,因为自绘界面闪烁得厉害,后来网上说使用传说中的“双缓冲”,确实解决了一部分的闪烁。后来我一直怀疑到底是不是双缓冲不行,纠结了一段时间,发觉是自己错了。原因是在NCPaint那边再次刷新界面,导致了有窗体在界面上边移动就会不断地刷新两次,一次是客户区(OnPaint),一次就是非客户区(OnNcPaint).问题解决后,思考更深入的问题:很多时候,bug都是自己的功力不足导致的,我们可以本着怀疑真理的态度去学习,去研究,前提是自己真的已经无懈可击!所以,以后程序有问题,我都是不厌其烦地进入堆栈调试,最终确实也通过一点一点的累积解决了问题。 这只是客户端,完整是一个工作区,这个是工作区里面的一个工程,common的文件没有放进来,但是看代码够了。自绘NC区域和客户区的代码都在里面。谢谢。 这只是客户端,完整是一个工作区,这个是工作区里面的一个工程,common的文件没有放进来,但是看代码够了。自绘NC区域和客户区的代码都在里面。谢谢。 你提到了NCPaint重绘,我也恰好需要请教这个问题,我自绘了NC区域,但有比客户区明显一些的闪烁,请问你是怎么做的? 界面圆角处理。 CRect cRect = rtCli; static CRect rtOrg; if (rtCli == rtOrg) { return; } rtOrg = rtCli; UINT x, y; CRgn rgnTemp; //取得窗口大小 x = GetSystemMetrics(SM_CXFRAME); y = GetSystemMetrics(SM_CYFRAME); HRGN wndRgn = ::CreateRectRgn(x, y-1, cRect.Width()-x, cRect.Height()-y); int nRound = 10; int nRgs[4][4] = {{0, 0, nRound, nRound}, {nRound, 0, nRound*2, nRound}, {nRound, nRound, nRound*2, nRound*2}, {0, nRound, nRound, nRound*2}}; int nRgOff[4][2] ={{x,y-1},{cRect.Width()-x-nRound*2,y-1},{cRect.Width()-x-nRound*2, cRect.Height()-y-nRound*2},{x, cRect.Height()-y-nRound*2}}; HRGN hRel = CreateEllipticRgn(0,0,nRound*2,nRound*2); for (int i = 0; i < 4; ++i) { rgnTemp.CreateRectRgn(0,0,0,0); HRGN hR = ::CreateRectRgn(nRgs[i][0], nRgs[i][1], nRgs[i][2], nRgs[i][3]); ::CombineRgn(rgnTemp, hRel, hR, RGN_AND); ::CombineRgn(rgnTemp, hR, rgnTemp, RGN_DIFF); rgnTemp.OffsetRgn(nRgOff[i][0], nRgOff[i][1]); ::CombineRgn(wndRgn, wndRgn, rgnTemp, RGN_DIFF); ::DeleteObject(hR); rgnTemp.DeleteObject(); } ::DeleteObject(hRel); SetWindowRgn(wndRgn, TRUE); 用这么多代码?CreateRoundRectRgn() 没用过吗。 我可以肯定的说,正确使用双缓冲完全可以解决这些问题,至于NC区域存在闪烁,一定是你的代码在绘制上有问题,无问题也不会闪烁的,没时间看你代码,检查BUG吧。一定存在BUG。 这是非客户区自绘代码:if (m_hWnd) { CBrush* pOldBrush = pDC->SelectObject(&m_brush); CRect rtWnd, rtTitle, rtButtons; GetWindowRect(&rtWnd); //取得标题栏的位置 //SM_CXFRAME 窗口边框的边缘宽度 //SM_CYFRAME 窗口边框的边缘高度 //SM_CXSIZE 窗口标题栏宽度 //SM_CYSIZE 窗口标题栏高度 rtTitle.left = GetSystemMetrics(SM_CXFRAME); rtTitle.top = GetSystemMetrics(SM_CYFRAME); rtTitle.right = rtWnd.right - rtWnd.left - GetSystemMetrics(SM_CXFRAME); rtTitle.bottom = rtTitle.top + GetSystemMetrics(SM_CYSIZE); CPoint point; //填充顶部框架 point.x = rtWnd.Width(); point.y = GetSystemMetrics(SM_CYSIZE) + GetSystemMetrics(SM_CYFRAME) + 0; pDC->PatBlt(0, 0, point.x, point.y, PATCOPY); //填充左侧框架 point.x = GetSystemMetrics(SM_CXFRAME) -1; point.y = rtWnd.Height()- 1; pDC->PatBlt(0, 0, point.x, point.y, PATCOPY); //填充底部框架 point.x = rtWnd.Width(); point.y = GetSystemMetrics(SM_CYFRAME); pDC->PatBlt(0, rtWnd.Height()-point.y, point.x, point.y, PATCOPY); //填充右侧框架 point.x = GetSystemMetrics(SM_CXFRAME); point.y = rtWnd.Height(); pDC->PatBlt(rtWnd.Width()-point.x, 0, point.x, point.y, PATCOPY); CBitmap* pBitmap = new CBitmap; CBitmap* pOldBitmap; CDC* pDisplayMemDC = new CDC; pDisplayMemDC->CreateCompatibleDC(pDC); //重画关闭按钮 rtButtons.left = rtTitle.right - 39 + 3; rtButtons.top = rtTitle.top; rtButtons.right = rtButtons.left + 39; rtButtons.bottom = rtButtons.top + 19; pBitmap->LoadBitmap(IDB_CLOSE_NORMAL); pOldBitmap=(CBitmap*)pDisplayMemDC->SelectObject(pBitmap); pDC->BitBlt(rtButtons.left, rtButtons.top, rtButtons.Width(), rtButtons.Height(), pDisplayMemDC, 0, 0, SRCCOPY); pDisplayMemDC->SelectObject(pOldBitmap); m_rtBtnClose = rtButtons; m_rtBtnClose.OffsetRect(rtWnd.TopLeft()); pBitmap->DeleteObject(); //重画最小化按钮 rtButtons.OffsetRect(-28, 0); pBitmap->LoadBitmap(IDB_MIN_NORMAL); pOldBitmap=(CBitmap*)pDisplayMemDC->SelectObject(pBitmap); pDC->BitBlt(rtButtons.left, rtButtons.top, rtButtons.Width(), rtButtons.Height(), pDisplayMemDC, 0, 0, SRCCOPY); pDisplayMemDC->SelectObject(pOldBitmap); m_rtBtnMin = rtButtons; m_rtBtnMin.OffsetRect(rtWnd.TopLeft()); pBitmap->DeleteObject(); //重画caption int nOldMode = pDC->SetBkMode(TRANSPARENT); COLORREF clOldText = pDC->SetTextColor(RGB(255, 255, 255)); CFont* pOldFont = NULL; pOldFont = pDC->SelectObject(&m_fCaption); rtTitle.left += /*m_rtIcon.Width () +*/ 10; rtTitle.top = rtTitle.top = 10; rtTitle.bottom = rtTitle.top + 30; // GetWindowText(m_strTitle);// m_strTitle = "Hello"; pDC->DrawText(m_strTitle, &rtTitle, DT_LEFT); pDC->SetBkMode(nOldMode); pDC->SetTextColor(clOldText); ReleaseDC(pDisplayMemDC); delete pDisplayMemDC; delete pBitmap; } 你这也叫双缓冲?我没觉得你在内存绘图,虽然用到了bitblt,但是你还有很多的pDC->PatBlt,DrawText之类的,你试下全部放内存绘,显示只用一次BitBlt看看,应该不会闪烁,再说你的界面绘制频率应该不会太高,看起来就是个聊天窗口一样 主窗口用 WS_EX_COMPOSITED,系统给你双缓冲,不用自己费劲。 我那个例子就没有用客户区。加个WS_POPUP. 你这个效果蛮漂亮哎,VC什么版本?或者是WPF? 缩小区域, 必须要有数学基础, 其实也就只是简单的加减乘除, 得到重画区域要重画什么然后画上去而已.像一个无窗口句柄的按钮, 假如这个按键要改变状态, 调用整个窗口句柄invalidate的话, 那肯定会闪, 但假如只单纯调用个UpdateRect(&ButtonRect), 然后再由Button具体的状态画上相应的东西, 那肯定不会闪屏.滚动时, 其实整个界面要刷新的只单纯是窗口相应滚动方向的像素, 假如为了求简单, 滚动也只是个Invalidate的话, 肯定全屏在闪, 即使双缓冲也照样闪的, 但假如计算出滚动出来的内容, 只补画上这部分内容, 那就肯定不会感觉闪. 刷新滚动区域等等这类操作, VC下有个例子的, 教你怎样画表格的. 汇编,也就是显示了一张bmp位图而已。带alpha通道,效果跟玻璃差不多。 虚函数问题请教,先贴代码 如何自动检测是哪个串口 IHTMLAnchorElement中的get_href()是得到url,该如何得到url对应的链接文字呢? 父窗口与子窗口之间的通信问题? 请问"无法定位序数5076于动态链接库MFC42D.DLL"是什么错误? 哪位有MSDN中文版 请教:在EVC下如何实现象“金山快译”风格的界面效果 引入 ADO COM 接口的问题 CBN_SELCHANGE的问题,急啊! 我用DirectDraw 不想显示为全屏,比如显示在一个单文档的视中,该如何做? 关于MFC中Tree控件句柄的问题 treectrl前面的加号问题
或者理解错了要解决闪烁和拖动卡,OnPaint这个函数里面耗时不能超过100毫秒
也就是说你不能在这个函数里执行太多的计算,基于此 你使用一个缓冲
在OnPaint里直接拷贝到屏幕DC,这个速度是很快的
其二、 不要随意的去刷背景,频繁刷背景可能会闪的
谢谢那你的指点,因为我还重绘了NC区域,代码很多,所以不好贴,我把代码上传了,如果可以的话你看看该怎么修改好,就是希望没有任何闪烁。另外如果你有别的经验的啊希望不吝赐教啊
http://download.csdn.net/source/3565277
麻烦你看看我的绘图代码,有点多,不好在这里贴,谢谢!
http://download.csdn.net/source/3565277
双缓存也好单缓存也好,重要的分清Invalidate(true)跟Invalidate(false).
数据改变需要重绘用false.窗口布局改变使你画的东西填不满了,用true
有人说把窗口背景刷设成NULL,没必要.
闪烁最根本的原因来源于背景重绘,要尽量避免在设备DC上重绘背景,如果使用内存DC,将内存DC设置和窗口一样大,所有的绘图包括背景刷新,都在内存DC上完成,然后BitBlt到设备DC。(曾经试过,在内存DC上绘10000条的直线(随机取点,最大窗口)整个动作完成也都在100ms以内,不会感觉有闪烁(bitblt还是很快的))
麻烦你看看我的绘图代码,有点多,不好在这里贴,谢谢!
http://download.csdn.net/source/3565277 该怎么修改好?
fatal error C1083: 无法打开包括文件:“../Common/RichEditCtrlOpt.h”: No such file or directory
完全同意,尽量不要使整个窗口失效。例子:没有用双缓冲,但是不闪。
http://download.csdn.net/source/3082195
1、窗口在响应WM_PAINT消息时,(在调用BeginPaint()过程中)会send一个WM_ERASEBKGND消息,默认对WM_ERASEBKGND的处理是用窗口的背景刷子填充失效区域。
如果默认窗口背景刷子的颜色和你要绘制的内容的颜色反差较大,就会产生闪烁的效果。解决的方法,设置背景刷子为空,或者自己响应WM_ERASEBKGND消息,直接返回TRUE2、你要在窗口上绘制的背景色和前景色反差较大,过程又比较长的话,也会产生闪烁效果。解决的方法就是使用双缓冲3、父窗口没有设置WS_CLIPCHILDREN属性的话,会先自己重绘子窗口的区域再让子窗口重绘,也可能造成闪烁
问题解决后,思考更深入的问题:很多时候,bug都是自己的功力不足导致的,我们可以本着怀疑真理的态度去学习,去研究,前提是自己真的已经无懈可击!所以,以后程序有问题,我都是不厌其烦地进入堆栈调试,最终确实也通过一点一点的累积解决了问题。
这只是客户端,完整是一个工作区,这个是工作区里面的一个工程,common的文件没有放进来,但是看代码够了。自绘NC区域和客户区的代码都在里面。谢谢。
这只是客户端,完整是一个工作区,这个是工作区里面的一个工程,common的文件没有放进来,但是看代码够了。自绘NC区域和客户区的代码都在里面。谢谢。
你提到了NCPaint重绘,我也恰好需要请教这个问题,我自绘了NC区域,但有比客户区明显一些的闪烁,请问你是怎么做的?
static CRect rtOrg; if (rtCli == rtOrg)
{
return;
} rtOrg = rtCli; UINT x, y;
CRgn rgnTemp; //取得窗口大小
x = GetSystemMetrics(SM_CXFRAME);
y = GetSystemMetrics(SM_CYFRAME);
HRGN wndRgn = ::CreateRectRgn(x, y-1, cRect.Width()-x, cRect.Height()-y);
int nRound = 10;
int nRgs[4][4] = {{0, 0, nRound, nRound}, {nRound, 0, nRound*2, nRound}, {nRound, nRound,
nRound*2, nRound*2}, {0, nRound, nRound, nRound*2}};
int nRgOff[4][2] ={{x,y-1},{cRect.Width()-x-nRound*2,y-1},{cRect.Width()-x-nRound*2,
cRect.Height()-y-nRound*2},{x, cRect.Height()-y-nRound*2}};
HRGN hRel = CreateEllipticRgn(0,0,nRound*2,nRound*2);
for (int i = 0; i < 4; ++i)
{
rgnTemp.CreateRectRgn(0,0,0,0);
HRGN hR = ::CreateRectRgn(nRgs[i][0], nRgs[i][1], nRgs[i][2], nRgs[i][3]);
::CombineRgn(rgnTemp, hRel, hR, RGN_AND);
::CombineRgn(rgnTemp, hR, rgnTemp, RGN_DIFF);
rgnTemp.OffsetRgn(nRgOff[i][0], nRgOff[i][1]);
::CombineRgn(wndRgn, wndRgn, rgnTemp, RGN_DIFF);
::DeleteObject(hR);
rgnTemp.DeleteObject();
} ::DeleteObject(hRel);
SetWindowRgn(wndRgn, TRUE);
CreateRoundRectRgn() 没用过吗。
至于NC区域存在闪烁,一定是你的代码在绘制上有问题,
无问题也不会闪烁的,没时间看你代码,检查BUG吧。一定存在BUG。
这是非客户区自绘代码:
if (m_hWnd)
{
CBrush* pOldBrush = pDC->SelectObject(&m_brush); CRect rtWnd, rtTitle, rtButtons;
GetWindowRect(&rtWnd);
//取得标题栏的位置
//SM_CXFRAME 窗口边框的边缘宽度
//SM_CYFRAME 窗口边框的边缘高度
//SM_CXSIZE 窗口标题栏宽度
//SM_CYSIZE 窗口标题栏高度
rtTitle.left = GetSystemMetrics(SM_CXFRAME);
rtTitle.top = GetSystemMetrics(SM_CYFRAME);
rtTitle.right = rtWnd.right - rtWnd.left - GetSystemMetrics(SM_CXFRAME);
rtTitle.bottom = rtTitle.top + GetSystemMetrics(SM_CYSIZE); CPoint point; //填充顶部框架
point.x = rtWnd.Width();
point.y = GetSystemMetrics(SM_CYSIZE) + GetSystemMetrics(SM_CYFRAME) + 0;
pDC->PatBlt(0, 0, point.x, point.y, PATCOPY);
//填充左侧框架
point.x = GetSystemMetrics(SM_CXFRAME) -1;
point.y = rtWnd.Height()- 1;
pDC->PatBlt(0, 0, point.x, point.y, PATCOPY);
//填充底部框架
point.x = rtWnd.Width();
point.y = GetSystemMetrics(SM_CYFRAME);
pDC->PatBlt(0, rtWnd.Height()-point.y, point.x, point.y, PATCOPY);
//填充右侧框架
point.x = GetSystemMetrics(SM_CXFRAME);
point.y = rtWnd.Height();
pDC->PatBlt(rtWnd.Width()-point.x, 0, point.x, point.y, PATCOPY);
CBitmap* pBitmap = new CBitmap;
CBitmap* pOldBitmap;
CDC* pDisplayMemDC = new CDC;
pDisplayMemDC->CreateCompatibleDC(pDC);
//重画关闭按钮
rtButtons.left = rtTitle.right - 39 + 3;
rtButtons.top = rtTitle.top;
rtButtons.right = rtButtons.left + 39;
rtButtons.bottom = rtButtons.top + 19;
pBitmap->LoadBitmap(IDB_CLOSE_NORMAL);
pOldBitmap=(CBitmap*)pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(rtButtons.left, rtButtons.top,
rtButtons.Width(), rtButtons.Height(), pDisplayMemDC, 0, 0, SRCCOPY);
pDisplayMemDC->SelectObject(pOldBitmap);
m_rtBtnClose = rtButtons;
m_rtBtnClose.OffsetRect(rtWnd.TopLeft());
pBitmap->DeleteObject();
//重画最小化按钮
rtButtons.OffsetRect(-28, 0);
pBitmap->LoadBitmap(IDB_MIN_NORMAL);
pOldBitmap=(CBitmap*)pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(rtButtons.left, rtButtons.top, rtButtons.Width(), rtButtons.Height(), pDisplayMemDC, 0, 0, SRCCOPY);
pDisplayMemDC->SelectObject(pOldBitmap);
m_rtBtnMin = rtButtons;
m_rtBtnMin.OffsetRect(rtWnd.TopLeft());
pBitmap->DeleteObject();
//重画caption
int nOldMode = pDC->SetBkMode(TRANSPARENT);
COLORREF clOldText = pDC->SetTextColor(RGB(255, 255, 255));
CFont* pOldFont = NULL;
pOldFont = pDC->SelectObject(&m_fCaption);
rtTitle.left += /*m_rtIcon.Width () +*/ 10;
rtTitle.top = rtTitle.top = 10;
rtTitle.bottom = rtTitle.top + 30;
// GetWindowText(m_strTitle);
// m_strTitle = "Hello";
pDC->DrawText(m_strTitle, &rtTitle, DT_LEFT);
pDC->SetBkMode(nOldMode);
pDC->SetTextColor(clOldText);
ReleaseDC(pDisplayMemDC);
delete pDisplayMemDC;
delete pBitmap;
}
你这个效果蛮漂亮哎,VC什么版本?或者是WPF?
像一个无窗口句柄的按钮, 假如这个按键要改变状态, 调用整个窗口句柄invalidate的话, 那肯定会闪, 但假如只单纯调用个UpdateRect(&ButtonRect), 然后再由Button具体的状态画上相应的东西, 那肯定不会闪屏.
滚动时, 其实整个界面要刷新的只单纯是窗口相应滚动方向的像素, 假如为了求简单, 滚动也只是个Invalidate的话, 肯定全屏在闪, 即使双缓冲也照样闪的, 但假如计算出滚动出来的内容, 只补画上这部分内容, 那就肯定不会感觉闪. 刷新滚动区域等等这类操作, VC下有个例子的, 教你怎样画表格的.