Click here to Skip to main content
15,886,963 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
In the program below, everything works fine until I try to scroll with the thumbtrack. The thumtrack simply snaps back to its initial position after I release it. What could be wrong?


C++
case WM_VSCROLL:
    {
        SCROLLINFO scrollInfo;
        scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE | SIF_TRACKPOS;
        
        GetScrollInfo(hWnd, SB_VERT, &scrollInfo);
        int yScrollPos = scrollInfo.nPos;
        switch (wParam)
        {
        case SB_TOP:
            yScrollPos = 0;
            break;
        case SB_BOTTOM:
            yScrollPos = scrollInfo.nMax - scrollInfo.nPage + 1;
            break;
        case SB_LINEUP:
            yScrollPos -= LINE_HEIGHT;
            break;
        case SB_LINEDOWN:
            yScrollPos += LINE_HEIGHT;
            break;
        case SB_PAGEUP:
            yScrollPos -= scrollInfo.nPage;
            break;
        case SB_PAGEDOWN:
            yScrollPos += scrollInfo.nPage;
            break;
        case SB_THUMBPOSITION:
            yScrollPos = scrollInfo.nTrackPos;
            break;

        }

        yScrollPos = max(yScrollPos, 0);
        yScrollPos = min(yScrollPos, static_cast<int>(scrollInfo.nMax) - static_cast<int>(scrollInfo.nPage) + 1);

        if (yScrollPos != scrollInfo.nPos)
        {
            SIZE szDistance(0, scrollInfo.nPos - yScrollPos);

            scrollInfo.fMask = SIF_POS | SIF_TRACKPOS;

            scrollInfo.nPos = yScrollPos;
            scrollInfo.nTrackPos = yScrollPos;
            SetScrollInfo(hWnd, SB_VERT, &scrollInfo, TRUE);


            ScrollWindow(hWnd, 0, szDistance.cy, nullptr, nullptr);
            UpdateWindow(hWnd);
        }
    }
    break;
    case WM_HSCROLL:
    {
        SCROLLINFO scrollInfo;
        scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE | SIF_TRACKPOS;

        GetScrollInfo(hWnd, SB_HORZ, &scrollInfo);
        int xScrollPos = scrollInfo.nPos;
        switch (wParam)
        {
        case SB_LEFT:
            xScrollPos = 0;
            break;
        case SB_RIGHT:
            xScrollPos = scrollInfo.nMax + 1;
            break;
        case SB_LINELEFT:
            xScrollPos -= LINE_WIDTH;
            break;
        case SB_LINERIGHT:
            xScrollPos += LINE_WIDTH;
            break;
        case SB_PAGELEFT:
            xScrollPos -= scrollInfo.nPage;
            break;
        case SB_PAGERIGHT:
            xScrollPos += scrollInfo.nPage;
            break;
        case SB_THUMBPOSITION:
            xScrollPos = scrollInfo.nTrackPos;
            break;

        }

        xScrollPos = max(xScrollPos, 0);
        xScrollPos = min(xScrollPos, static_cast<int>(scrollInfo.nMax) - static_cast<int>(scrollInfo.nPage) + 1);

        if (xScrollPos != scrollInfo.nPos)
        {
            SIZE szDistance(scrollInfo.nPos - xScrollPos, 0);

            scrollInfo.fMask = SIF_POS | SIF_TRACKPOS;
            scrollInfo.nPos = xScrollPos;
            scrollInfo.nTrackPos = xScrollPos;
            SetScrollInfo(hWnd, SB_HORZ, &scrollInfo, TRUE);


            ScrollWindow(hWnd, szDistance.cx, 0, nullptr, nullptr);
            UpdateWindow(hWnd);
        }
    }


What I have tried:

I have been on this for several months. I kept abandoning it and attending to other codes and going back to it.
Posted
Comments
Richard MacCutchan 28-Mar-24 4:46am    
Your scrolling code should be happening in response to WM_PAINT, not in response to scroll messages.
Gbenbam 28-Mar-24 8:14am    
Can you expatiate on the this?
Richard MacCutchan 28-Mar-24 10:22am    
See my solution below.
Gbenbam 28-Mar-24 11:26am    
Although I already have an excellent answer below, I will appreciate other perspectives or solutions for deeper understanding. Others should not simply back off because of the excellent solution1 below. Besides, it uses a paradigm that is completely new to me .You own solution could have the same paradigm as mine present paradigm.

1 solution

Here is the essential parts of the code I use to allow scrolling in a self painted Window. If you want an easier life then you can use one of the standard Windows controls (e.g. Edit Control (Windows Controls) - Win32 apps | Microsoft Learn[^] ) which does all the work for you.

This handler can be called for both Vertical and Horizontal scroll messages
C++
/// <summary>
/// Handle the <c>WM_VSCROLL</c> and <c>WM_HSCROLL</c> messages. Adjust the scroll information
/// block to re-display the application's view in an adjusted
/// vertical, or horizontal, position according to user selection.
/// Called in response to UP, DOWN, LEFT and RIGHT key or mouse commands
/// </summary>
///
/// <param name="hWnd">handle of the window</param>
/// <param name="wParam">scroll code and position value</param>
/// <param name="sbType">indicates vertical or horizontal movement</param>
///
/// <returns>No return value</returns>
///
void OnScrollMsg(
	HWND		hWnd,
	WPARAM		wParam,
	int			sbType
)
{
	UINT		uCode = LOWORD(wParam);
	int			nPosition = HIWORD(wParam); // if uCode is SB_THUMBPOSITION or SB_THUMBTRACK
	SCROLLINFO	scrollInfo;
	int			nOldPos;

	// get the current scroll position data and adjust as necessary
	ZeroMemory(&scrollInfo, sizeof scrollInfo);
	scrollInfo.cbSize = sizeof scrollInfo;
	scrollInfo.fMask = SIF_ALL;
	if (::GetScrollInfo(hWnd, sbType, &scrollInfo) == 0)
		scrollInfo.nPos = -1;	// first time
	nOldPos = scrollInfo.nPos;
	switch (uCode)
	{
	case SB_LINEUP:		// Scrolls up or
		//	case SB_LINELEFT:	// left by one unit.
		scrollInfo.nPos -= nPosition == 0 ? 1 : nPosition;
		break;
	case SB_LINEDOWN:	// Scrolls down or
		//	case SB_LINERIGHT:	// right by one unit.
		scrollInfo.nPos += nPosition == 0 ? 1 : nPosition;
		break;
	case SB_PAGEUP:		// Scrolls up by the height or
		//	case SB_PAGELEFT:	// left by the width of the window.
		scrollInfo.nPos -= scrollInfo.nPage;
		break;
	case SB_PAGEDOWN:	// Scrolls down by the height or
		//	case SB_PAGERIGHT:	// right by the width of the window.
		scrollInfo.nPos += scrollInfo.nPage;
		break;
	case SB_THUMBPOSITION:
		// actual position is stored in the info structure
		//scrollInfo.nPos = scrollInfo.nTrackPos;
		//break;
	case SB_THUMBTRACK:
		// actual position is stored in the info structure
		scrollInfo.nPos = nPosition;
		break;
	case SB_TOP:		// Scrolls to the top
		//	case SB_LEFT:		// or left of the window.
		scrollInfo.nPos = 0;
		break;
	case SB_BOTTOM:		// Scrolls to the bottom
		//	case SB_RIGHT:		// or right of the window.
		scrollInfo.nPos = scrollInfo.nMax + 1;
		break;
	default:
		break;
	}
	// ensure the position is not out of range
	if (scrollInfo.nPos < 0)
		scrollInfo.nPos = 0;
	if (scrollInfo.nPos >= scrollInfo.nMax - (int)scrollInfo.nPage)
		scrollInfo.nPos = scrollInfo.nMax - scrollInfo.nPage;

	if (nOldPos != scrollInfo.nPos)
	{
		// set the new position and force repainting of the screen
		::SetScrollInfo(hWnd, sbType, &scrollInfo, TRUE);
		::InvalidateRect(hWnd, nullptr, TRUE);
	}
}


This function should be called from the WM_PAINT handler,
to adjust the settings according to the size of the display Window
as described in the section below this.
C++
/// <summary>
/// Set ranges for the scrollbar - vertical or horizontal.
/// Called from WM_PAINT handler for vertical and horizontal scrolling
/// </summary>
///
/// <param name="nBar"><c>SB_xx</c> value indicating whether vertical or horizontal bar</param>
/// <param name="nPageSize">height or width of a visual page</param>
/// <param name="nMaxRange">height or width of total data to view</param>
///
/// <returns>Current position of the scroll box</returns>
///
int SetScrollRange(
	HWND		hWnd,
	int			nBar,
	int			nPageSize,
	int			nMaxRange
)
{
	SCROLLINFO	scrollInfo;

	// Add/adjust vertical scroll bars...
	ZeroMemory(&scrollInfo, sizeof scrollInfo);
	scrollInfo.cbSize = sizeof scrollInfo;
	scrollInfo.fMask = SIF_ALL;

	// get the current information
	::GetScrollInfo(hWnd, nBar, &scrollInfo);

	// set the new values
	scrollInfo.nPage = nPageSize;	// lines/characters in the page
	scrollInfo.nMax = nMaxRange;	// total number of lines/characters
	::SetScrollInfo(hWnd, nBar, &scrollInfo, TRUE);

	return scrollInfo.nPos;
}


In your WM_PAINT handler before you start paining the window you need to adjust the start points based on the scroll ranges. You need to make
these calls once you have made any adjustments to the size of your
display window.
C++
// Use the view and character sizes to set the scrolling sizes
int viewHeight = paintStruct.rcPaint.bottom - paintStruct.rcPaint.top;
// Set the starting line of dats to be painted
int nLine = SetScrollRange(hWnd, SB_VERT, viewHeight / static_cast<int>(charHeight), pVpstr->size());

int viewWidth = paintStruct.rcPaint.right - paintStruct.rcPaint.left;
// set the rightmost character position: maxWidth is the width of the widest line to paint
int nChar = SetScrollRange(hWnd, SB_HORZ, viewWidth / static_cast<int>(charWidth), maxWidth);

You then use nLine and nChar to set the starting points for displaying the data. When I say "starting" points, I mean the first line or character of your data. You still paint into
the full viewport of your Window. So the first line displays at Y-offset 0, and the first character at
X-offset 0.

I hope that makes sense.
 
Share this answer
 
v3
Comments
Pete O'Hanlon 28-Mar-24 10:42am    
This is a truly beautiful answer.
Richard MacCutchan 28-Mar-24 11:00am    
Thanks Pete, your comments are always a boost to the ego.
Gbenbam 28-Mar-24 11:15am    
Wow! This is amazing! I am overwhelmed. This is a completely different approach to what I have always known. But I have two questions , one concern and two request.

1. Why is it your Onscroll program does not use ScrollWindow Function. Is it that it doesn't need it or it is an oversight on your part.

2 . I don't understand the line:

pVpstr->size())

I could not find where that variable was defined in the code .


My concern is this: my program does not really write characters. It renders graphics which can be positioned anywhere arbitrarily.

My first request is this: can you kindly help with the accompanying WM_SIZE and WM-SIZING codes.

My second request is this: could you be kind enough to show the corresponding WM_PAINT program. It will help me understand well enough how to adapt the code to my need. Besides, I finally came to term with your assertion that all primary device context message should be done within WM_PAINT. I am still trying to resolve the handling of WM_PAINT message for my application. I will appreciate it if you can illustrate your perspective with a code especially one that would represent the accompanying messages to code to this OnScroll program.


Do pardon me for being such a border
Richard MacCutchan 28-Mar-24 11:35am    
I will answer the first two questions as they are fairly easy:
1. I don't use ScrollWindow as in this sample i am displaying simple text. So each time WM_PAINT is called I repaint the entire Window starting at the point in the data that needs displaying. So if the page size is calculate as 20 lines and the scroll was caused by PAGE_DOWN I need to skip lines 1 to 20 and start by displaying line 21 of the text and beyond, always stopping when I hit the bottom of the display area.

2. The pointer pVpstr is a pointer to a vector that contains the lines of text to be displayed. Its size method returns the number of lines. So passing that value and the height of the display area to SetScrollRange returns the position in the data at which to start.

I need to think a bit more about the other queries.
Gbenbam 28-Mar-24 11:38am    
Thank you. I appreciate your assistance.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900