MFC中CScrollView滚动时BitBlt异常:新可见区域未绘制
CScrollView双缓冲滚动重绘问题解析
问题根源:内存DC视口与Bitmap尺寸不匹配
- 你创建的内存Bitmap尺寸是客户区大小,但设置
memDC.SetViewportOrg(-scrollPos.x, -scrollPos.y)后,myDrawingFunction绘制的内容会有部分超出Bitmap范围(视口偏移后,绘制坐标对应Bitmap的负区域,这部分无法被存储)。 - 滚动后新可见区域的内容根本没被绘制到内存Bitmap中,
BitBlt时拿不出正确像素导致空白;原区域错位是因为内存Bitmap里的内容坐标和实际显示坐标不匹配。
临时解决方法的“反常识”原因
你用的pDC->BitBlt(0, 0, rect.Width()+scrollPos.x, rect.Height()+scrollPos.y, &memDC, 0, 0, SRCCOPY)本质是利用了BitBlt对超出源Bitmap范围的区域返回黑色(或透明,取决于设备)的未定义行为——通过扩大目标区域,让BitBlt把内存Bitmap里偏移后的内容“蹭”到正确显示位置,完全不符合参数规范,属于巧合凑效的hack。
CScrollView双缓冲的核心遗漏点
1. 内存Bitmap需适配滚动总范围,而非仅客户区
CScrollView的滚动范围是整个文档内容,内存Bitmap应创建为滚动总范围的尺寸,确保视口偏移后所有绘制内容都能被存储。
2. 正确匹配BitBlt的源坐标与滚动位置
内存DC存储的是整个文档的绘制结果,客户区显示的是其中一块,因此BitBlt的源坐标应设为当前滚动位置。
3. 无需手动重置内存DC视口原点,通过源坐标控制BitBlt区域即可
修正后的核心代码(适配大滚动范围)
void CMyScrollView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // 获取客户区与滚动总范围 CRect clientRect; GetClientRect(&clientRect); CSize totalScrollSize = GetTotalScrollSize(); // 创建适配滚动范围的内存DC与Bitmap CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap bitmap; bitmap.CreateCompatibleBitmap(pDC, totalScrollSize.cx, totalScrollSize.cy); CBitmap* pOldBitmap = memDC.SelectObject(&bitmap); // 直接在逻辑坐标下绘制整个文档内容 myDrawingFunction(&memDC); // 按滚动位置截取对应区域绘制到客户区 CPoint scrollPos = GetDeviceScrollPosition(); pDC->BitBlt(0, 0, clientRect.Width(), clientRect.Height(), &memDC, scrollPos.x, scrollPos.y, SRCCOPY); memDC.SelectObject(pOldBitmap); }
内存优化版(仅创建客户区大小Bitmap)
如果滚动内容极大,可只创建客户区尺寸的Bitmap,通过调整内存DC的窗口原点来捕获可见区域:
void CMyScrollView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CRect clientRect; GetClientRect(&clientRect); CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap bitmap; bitmap.CreateCompatibleBitmap(pDC, clientRect.Width(), clientRect.Height()); CBitmap* pOldBitmap = memDC.SelectObject(&bitmap); // 设置内存DC窗口原点,让绘制内容对应滚动后的可见区域 CPoint scrollPos = GetDeviceScrollPosition(); memDC.SetWindowOrg(scrollPos.x, scrollPos.y); // 绘制整个文档,内存DC仅捕获可见区域内容 myDrawingFunction(&memDC); // 直接将内存Bitmap绘制到客户区 pDC->BitBlt(0, 0, clientRect.Width(), clientRect.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBitmap); }
内容的提问来源于stack exchange,提问作者starriet 차주녕
相关产品推荐
相关产品推荐

