MFC CScrollView 不清除背景

问题描述

我的英语并不完美。我正在使用 Visual C++ 2019 和 MFC。示例程序:一个SDI程序,视图的基数为CScrollView,在一个矩阵中绘制128*128个0。但是 MFC 在使用滚动条滚动时不会清除背景。你有什么想法吗?谢谢。

在 Windows 设置中,我使用的是 96 dpi * 3 = 288 dpi。 我试过:96 dpi 模式受影响。

如何将示例程序上传到此?

void CsdView::OnDraw(CDC *pDC) {
    CsdDoc *pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if  (!pDoc)
        return;
    CRect rect;
    GetClientRect(&rect);
    pDC->FillSolidRect(rect,0xFFFFFF);
    CPoint  pos = GetDeviceScrollPosition();
    TRACE(L"OnDraw: %4u.%4u - %4u.%4u,%4u.%4u\n",rect.right,rect.bottom,pos.x,pos.y,rect.right + pos.x,rect.bottom+pos.y);
    for (int i = 0; i < 128; ++ i)
        for (int j = 0; j < 128; ++ j)
            pDC->textoutW(j*20 - pos.x,i*54 - pos.y,L"0",1);
}

void CsdView::OnInitialUpdate() {
    CScrollView::OnInitialUpdate();
    CSize sizetotal;
    sizetotal.cx = 20*128;
    sizetotal.cy = 54*128;
    SetScrollSizes(MM_TEXT,sizetotal);
}

BOOL CsdView::OnEraseBkgnd(CDC *pDC) {
    CBrush  brush(0xFFFFFF);
    FillOutsideRect(pDC,&brush);
    return  TRUE;
//  return CScrollView::OnEraseBkgnd(pDC);
}

我无法上传图片代码作为评论,所以我必须编辑原始问题。

enter image description here

还有一个小bug。原始代码(MDI MFC):

void CIDEView::OnDraw(CDC *pDC) {
    CIDEDoc *const d = GetDocument();
    ASSERT_VALID(d);
    if  (! d)
        return;
    CPoint  const pos = GetDeviceScrollPosition();
    CRect rect;
    GetClientRect(&rect);
    OffsetRect(&rect,pos.y);
    pDC->FillSolidRect(rect,bkcolor);
    auto    oldfont = pDC->SelectObject(&font);
    pDC->SetBkColor(bkcolor);
    pDC->SetTextColor(textcolor);
    pDC->SetBkMode(TRANSPARENT);
    const int   cxs = pos.x / mincw,cys = pos.y / lineheight;
    const int   cxe = (rect.right + mincw-1) / mincw,cye = (rect.bottom + 41) / lineheight;
    for (int i = cys; i <= cye; ++ i)
        for (int j = cxs; j <= cxe; ++ j)
            pDC->textoutW(textmargin+j*mincw,i*lineheight,1);
    pDC->SelectObject(oldfont);
}

void CIDEView::OnInitialUpdate() {
    CScrollView::OnInitialUpdate();
    SetScrollSizes(MM_TEXT,{linewidth,128*lineheight});
}

BOOL CIDEView::OnEraseBkgnd(CDC *pDC) {
    return  TRUE;
}

解决方法

CScrollView 类是具有滚动功能的视图。您几乎可以像使用 CView 一样使用它(即在 OnDraw() 成员中绘图),只是您必须考虑可能的滚动。

GetClientRect() 函数返回可见客户区,返回的坐标不是相对于视图原点,而是相对于窗口原点,即 lefttop 成员总是0. CDC 函数中的 OnDraw() 参数虽然是相对于逻辑视图原点,所以需要调整。

至于您的代码,您不需要使用 OnEraseBkgnd() 函数,因为您是在 OnDraw() 中这样做的。您只填充窗口的可见部分,但这非常好。所以最好把它去掉。此外,传递给 TextOutW() 函数的坐标必须相对于视图原点,因此 -pos.x-pos.y 调整是错误的。相反,需要调整的是传递给 FillSolidRect() 函数的矩形。所以,你的代码会变成:

void CsdView::OnDraw(CDC *pDC)
{
    CsdDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
    CPoint  pos = GetScrollPosition();
    CRect rect;
    GetClientRect(&rect);
    // Adjust client rect to device coordinates
    OffsetRect(&rect,pos.x,pos.y);
    pDC->FillSolidRect(rect,0xFFFFFF);
    for (int i = 0; i < 128; ++i)
        for (int j = 0; j < 128; ++j)
            pDC->TextOutW(j * 20,i * 54,L"0",1);
}

然而,这段代码是“浪费的”,因为它绘制了整个视图。它可以优化为仅绘制可见部分。它只会绘制 0,即使是一个像素位于可见部分(没有 #define 任何东西,只是使用了您的硬编码 20 和 54 值)。也把颜色改成黄色,这样可以更好的测试一下(白色是默认背景色)。

void CsdView::OnDraw(CDC *pDC)
{
    CsdDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
    CPoint  pos = GetScrollPosition();
    CRect rect;
    GetClientRect(&rect);
    // Adjust client rect to device coordinates
    OffsetRect(&rect,0x00FFFF);
    // Paint only the items in the visible part
    int xL = rect.left / 20,xR = (rect.right + 19) / 20,yT = rect.top / 54,yB = (rect.bottom + 53) / 54;
    for (int i = yT; i < yB; ++i)
        for (int j = xL; j < xR; ++j)
            pDC->TextOutW(j * 20,1);
}

编辑:

修改后的代码中,为什么doc、pos、cxs等变量是const?你的逻辑也有不少错误:

  • 您将视图大小设置为 OnInitialUpdate(),而不是假设 linewidth 等于 textmargin + 128*mincw。如果没有,请再次修改您的代码。
  • rect 已调整(使用 OffsetRect()),因此再次添加 pos.x 是错误的。
  • 因为您在变量中有单元格大小,所以不要使用硬编码数字。例如,cxe 的代码应该变成 cxe = (rect.right + mincw - 1) / mincw;类似地更新 cye 代码。
  • 您还以 textmargin 的偏移量进行绘制。然后代码应该变成 cxe = (rect.right - textmargin + mincw - 1) / mincw;
  • 我发布的代码适用于循环中的 < 条件,您不需要 <=。算一算,你会发现这是正确的。