1
votes

I encountered a problem scrolling large areas in a CScrollView. When slowly moving the scrollbar from top to bottom the behaviour is the following: At first the scrolling is working fine. At some point scrolling further does not do anything, instead the top of the area is shown. At some point scrolling starts again from the top.

Here is a small example. I created a new MFC project using the document-view-model and used a CScrollView as the view class. I added the following code to create a large area and add some text to show, which part is currently shown:

void CScrollViewTest2View::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate();

    CSize sizeTotal;
    // TODO: calculate the total size of this view
    sizeTotal.cx = sizeTotal.cy = 100*1000;
    SetScrollSizes(MM_TEXT, sizeTotal);

    for(int i = 0; i < 1000; i++)
    {
        CStatic* label = new CStatic();
        label->Create(NULL, WS_CHILD | WS_VISIBLE, CRect(10,10 + i*100,100,30 + i*100), this);
        CString text;
        text.Format(L"%d",i);
        label->SetWindowText(text);
    }
}

If I add the following code I see that during the scrolling the 'nPos' value seems to wrap around. This would explain the behaviour. But I don't know how to work around that.

BOOL CScrollViewTest2View::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll )
{
    CString msg;
    msg.Format(L"nPos = %u\n",nPos);
    TRACE(msg);
    return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll);
}

and here is the output when it stops scrolling:

nPos = 27826
nPos = 29190
nPos = 30281
nPos = 31372
nPos = 31645
nPos = 32464
nPos = 4294938588
nPos = 4294939134
nPos = 4294939407
nPos = 4294939680
nPos = 4294940225
nPos = 4294940771

So is there a way to use a CScrollView to completely scroll down a large area?

The code of the sample project can be found here.

1
CScrollView uses SetScrollRange and other old functions which have 16bit limit, it only goes up to ‭32767‬. It's supposed to use SetScrollInfo/GetScrollInfo but they didn't bother to update. You have an additional problem with creating static controls whose coordinates are out of 16bit range, I don't think there is a solution for that.Barmak Shemirani
So if I would write my own version of 'CScrollView' using 'SetScrollInfo/GetScrollInfo' I would get around this problem? Do you know if someone already did this and made it available? And what exactly is the problem with static controls and coordinates out of the 16 bit range? Thanks for your help.sietschie
I don't think anything can be done for placing window controls outside of 32767 range. I added a fix for CScrollView, it's for regular drawing, otherwise you might want to consider HTML dialog or another solution.Barmak Shemirani

1 Answers

1
votes

The static controls are not placed where you expect them to be. To see the problem, run the following code:

static CStatic test;
CRect r(0, 0, 100, 30);
r.MoveToY(40000);
test.Create(0, WS_CHILD | WS_VISIBLE, r, this);

test.GetWindowRect(r);

TRACE("%d\n", r.top);

r.top should be 32767. This is because of 16bit limits in Windows. All of the controls whose x/y position exceed this limit, are pushed back to this position.

OnScroll function suffers from a similar problem, however this can be fixed by using GetScrollInfo

Add ON_WM_VSCROLL to message map and the following OnVScroll override to your class

void CScrollViewTest2View::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    CView::OnVScroll(nSBCode, nPos, pScrollBar);
    SCROLLINFO info;
    GetScrollInfo(SB_VERT, &info, SIF_ALL);

    int pos = info.nPos;
    int save = pos;
    switch (nSBCode)
    {
    case SB_LEFT: pos = info.nMin; break;
    case SB_RIGHT: pos = info.nMax; break;
    case SB_LINELEFT: pos--; break;
    case SB_LINERIGHT: pos++;  break;
    case SB_PAGELEFT: pos -= info.nPage; break;
    case SB_PAGERIGHT: pos += info.nPage; break;
    case SB_THUMBPOSITION: pos = info.nTrackPos; break;
    case SB_THUMBTRACK: pos = info.nTrackPos; break;
    }

    //make sure the new position is within range
    if (pos < info.nMin) pos = info.nMin;
    int max = info.nMax - info.nPage + 1;
    if (pos > max) pos = max;

    OnScrollBy(CSize(0, pos - save), 1);

    //EDIT: moved this line after OnScrollBy and added condition
    if (info.nPos != pos)
    {
        info.nPos = pos;
        SetScrollInfo(SB_VERT, &info, FALSE);
    }
    UpdateWindow();
} 

This will not solve the problem with windows controls whose x/y position is greater than 32,000, but at least it will scroll the DC as expected.

For testing, remove the static controls. You can use the draw function below to test the painting:

void CScrollViewTest2View::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate();
    CSize sizeTotal(0, 100 * 1000);
    SetScrollSizes(MM_TEXT, sizeTotal);
}

void CScrollViewTest2View::OnDraw(CDC* pDC)
{
    for (int i = 0; i < 1000; i++)
    {
        int y = i * 100;
        CString s;
        s.Format(L"%d                ", y);
        pDC->TextOutW(200, y, s);
    }
}