NOTE: This is an older article, so the information provided may no longer be accurate.

Window scrolling example - Windows API (Win32/64) [update 3]

window-scroll-example3 This Windows API C / C++ scrollbar example was created out of a need to scroll a child window from a parent window. Just like with my earlier tab control project , I looked all over the place, but didn't find anything that really helped. I just mashed various bits and pieces from around the web until I got exactly what I wanted.

Example summary
This example has a parent window with both horizontal and vertical scrollbars. Within the parent window is a child window which has a single static control added to it. When you change the position of the scrollbars, it scrolls the child window within.

While the summary makes it sound like a pretty easy and simple project, it really is not. In my opinion, dealing with scrollbars is NOT fun at all! There is checking for many different messages within both horizontal and vertical scrollbar messages. There is a lot of computation and updating of the scrollbar info structure. It's not impossibly complex, but it's not nearly as simple as my earlier tab example.

As mentioned elsewhere on my blog, I only use Code::Blocks with MinGW for writing C / C++ programs, so you'll find the code is tailored to that, and the example file only contains Code::Blocks project files. Visual Studio users can still grab the code out of the source file, but you will probably need to make minor adjustments.

 

God loves geeks too!
Why Jesus?

If you have any questions, just leave a comment.

 

Window Scroll Example file:
Code::Blocks example (5 KB - zip)

 

Code:

#define _WIN32_WINNT 0x0501

#include <windows.h>

/*
    global variables
*/
HINSTANCE hInst = NULL;

HWND hParent = NULL;  // parent window handle
HWND hChild = NULL;  // child window handle

SCROLLINFO si = {0};  // scroll info structure

INT iXCurrentScroll = 0;  // current horizontal scroll value
INT iYCurrentScroll = 0;   // current vertical scroll value

BOOL blManualMove = FALSE;  // manual move flag
BOOL blShutDown = FALSE;  // program shut-down flag

/*
    declare functions
*/
VOID CreateChildWindow ();
VOID ResizeScrollbars ();
VOID ScrollChild ();
VOID SetDefaultFont (HWND);
LRESULT CALLBACK WindowProcedureParent (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WindowProcedureChild (HWND, UINT, WPARAM, LPARAM);

/*
    start of main program
*/
INT WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, INT nCmdShow)
{
    MSG messages = {0};  // message structure
    WNDCLASSEX wincl = {0};  // window class structure

    hInst = hThisInstance;

    // main window structure
    wincl.cbSize = sizeof(WNDCLASSEX);
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = "WindowScrollParent";
    wincl.lpfnWndProc = WindowProcedureParent;  // main window procedure
    wincl.style = 0;  // no special window class styles

    // use default icons and set background color
    wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);

    // Register the window class, and if it fails quit the program
    if (!RegisterClassEx(&wincl))
    {
        MessageBox(NULL, "Could not register main window class", "Error", MB_OK | MB_ICONERROR);
        return GetLastError();
    }

    // create parent window
    hParent = CreateWindowEx (
                  0,            // no extended styles
                  "WindowScrollParent",  // window class name
                  "Windows API Win32/64 Scrolling Example",  // window text
                  WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,  // window styles
                  CW_USEDEFAULT,  // window x position
                  CW_USEDEFAULT,  // window y position
                  375,          // window width
                  375,          // window height
                  HWND_DESKTOP, // parent
                  NULL,         // no menu/ID
                  hInst,        // instance handler
                  NULL          // no extra creation data
              );

    // if the main window couldn't be created, exit with error message
    if (hParent == NULL)
    {
        MessageBox(NULL, "Could not create Parent window", "Error", MB_OK | MB_ICONERROR);
        return GetLastError();
    }

    // create the child window
    CreateChildWindow();

    // show the main window, along with children
    ShowWindow(hParent, nCmdShow);
    ShowWindow(hChild, SW_SHOW);

    // set initial child window position
    iXCurrentScroll = -5;
    iYCurrentScroll = -5;

    // scroll child window to initial position
    ScrollChild();

    // Run the message loop
    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }

    // exit program with exit code
    return messages.wParam;
}


/*
    create the child window that will be scrolled
*/
VOID CreateChildWindow ()
{
    WNDCLASSEX wincl = {0};  // window class structure

    // child window structure
    wincl.cbSize = sizeof(WNDCLASSEX);
    wincl.lpszClassName = "WindowScrollChild";

    // this child window uses a different message loop
    // than the parent window
    wincl.lpfnWndProc = WindowProcedureChild;
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.hbrBackground = CreateSolidBrush(RGB(245,245,255));

    // Register the window class
    if (!RegisterClassEx(&wincl))
    {
        MessageBox(NULL, "Could not register child window class", "Error", MB_OK | MB_ICONERROR);
        return;
    }

    // create child window
    hChild = CreateWindowEx (
                 0,         // no extended styles
                 "WindowScrollChild",  // child window class name
                 "Child window",        // window text
                 WS_OVERLAPPEDWINDOW | WS_CHILD,
                 0,         // window x position
                 0,         // window y position
                 400,       // window width
                 400,       // window height
                 hParent,   // parent
                 NULL,      // no menu/ID
                 hInst,     // instance handler
                 NULL       // no extra creation data
             );

    // if child window couldn't be created, display error
    if (hChild == NULL)
    {
        MessageBox(hParent, "Could not create Child window", "Error", MB_OK | MB_ICONEXCLAMATION);
    }

    // create static text
    HWND hStatic1 = CreateWindowEx (
                        0,               // no extended styles
                        "STATIC",        // control class name
                        "This is a Static text control",    // text
                        WS_CHILD | WS_VISIBLE,  // styles
                        120,               // x position
                        100,             // y position
                        150,             // width
                        18,              // height
                        hChild,          // parent
                        NULL,            // no menu/ID
                        hInst,           // instance handler
                        NULL             // no extra creation data
                    );

    // if control can't be created, display error
    if (hStatic1 == NULL)
    {
        MessageBox(hParent, "Could not create static text control.", "Error", MB_OK | MB_ICONEXCLAMATION);
    }

    // make font nice
    SetDefaultFont(hStatic1);
}


/*
    update scroll bar info
*/
VOID ResizeScrollbars ()
{
    INT iXMinScroll = -5;
    INT iYMinScroll = -5;

    RECT rParent = {0};
    RECT rChild = {0};
    POINT pChild = {0};

    // get parent's client rect
    GetClientRect(hParent, &rParent);

    // get child's window rect
    GetWindowRect(hChild, &rChild);

    // current child position
    pChild.x = rChild.left;
    pChild.y = rChild.top;
    ScreenToClient(hParent, &pChild);

    // set current scroll
    iXCurrentScroll = -pChild.x;
    iYCurrentScroll = -pChild.y;

    // set horizontal scrollbar info
    memset(&si, 0, sizeof(si));
    si.cbSize = sizeof(si);
    si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
    si.nMin = iXMinScroll;
    si.nMax = (rChild.right - rChild.left);
    si.nPage = (rParent.right - rParent.left);
    si.nPos = iXCurrentScroll;

    SetScrollInfo(hParent, SB_HORZ, &si, TRUE);

    // set vertical scrollbar info
    memset(&si, 0, sizeof(si));
    si.cbSize = sizeof(si);
    si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
    si.nMin = iYMinScroll;
    si.nMax = (rChild.bottom - rChild.top);
    si.nPage = (rParent.bottom - rParent.top);
    si.nPos = iYCurrentScroll;

    SetScrollInfo(hParent, SB_VERT, &si, TRUE);
}

/*
    scroll child window
*/
VOID ScrollChild()
{
    RECT rChild = {0};

    // get child window rect
    GetWindowRect(hChild, &rChild);

    // scroll child window
    MoveWindow(hChild, -iXCurrentScroll, -iYCurrentScroll, rChild.right - rChild.left, rChild.bottom - rChild.top, TRUE);
}

/*
    set control to default GUI font
*/
VOID SetDefaultFont (HWND hwnd)
{
    SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)TRUE);
}

/*
    main window procedure called by DispatchMessage
*/
LRESULT CALLBACK WindowProcedureParent (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // process messages by ID
    switch (message)
    {
    // parent window was resized
    case WM_SIZE:
    {
        ResizeScrollbars();
        SendMessage(hParent, WM_HSCROLL, SB_THUMBTRACK, 0);
        SendMessage(hParent, WM_VSCROLL, SB_THUMBTRACK, 0);
        return 0;
    }
    break;

    // user changed horizontal scrollbar position
    case WM_HSCROLL:
    {
        INT iXNewPos = 0;  // new x scroll position value

        // figure out which method user used to change position
        switch (LOWORD(wParam))
        {
        // User clicked the scroll bar shaft left of the scroll box
        case SB_PAGEUP:
        {
            iXNewPos = iXCurrentScroll - 50;
        }
        break;

        // User clicked the scroll bar shaft right of the scroll box
        case SB_PAGEDOWN:
        {
            iXNewPos = iXCurrentScroll + 50;
        }
        break;

        // User clicked the left arrow
        case SB_LINEUP:
        {
            iXNewPos = iXCurrentScroll - 10;
        }
        break;

        // User clicked the right arrow
        case SB_LINEDOWN:
        {
            iXNewPos = iXCurrentScroll + 10;
        }
        break;

        // User dragged the scroll box
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
        {
            memset(&si, 0, sizeof(si));
            si.cbSize = sizeof(si);
            si.fMask = SIF_TRACKPOS;

            GetScrollInfo(hParent, SB_HORZ, &si);

            iXNewPos = si.nTrackPos;
        }
        break;

        default:
            iXNewPos = iXCurrentScroll;
        }

        // reset the current scroll position value
        iXCurrentScroll = iXNewPos;

        // scroll child window with new position
        ScrollChild();

        return 0;
    }
    break;

    // user changed vertical scrollbar position
    case WM_VSCROLL:
    {
        INT iYNewPos = 0;    // new Y scroll position value

        // figure out which method user used to change position
        switch (LOWORD(wParam))
        {
        // User clicked the scroll bar shaft above the scroll box.
        case SB_PAGEUP:
        {
            iYNewPos = iYCurrentScroll - 50;
        }
        break;

        // User clicked the scroll bar shaft below the scroll box.
        case SB_PAGEDOWN:
        {
            iYNewPos = iYCurrentScroll + 50;
        }
        break;

        // User clicked the top arrow.
        case SB_LINEUP:
        {
            iYNewPos = iYCurrentScroll - 10;
        }
        break;

        // User clicked the bottom arrow.
        case SB_LINEDOWN:
        {
            iYNewPos = iYCurrentScroll + 10;
        }
        break;

        // User dragged the scroll box.
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
        {
            memset(&si, 0, sizeof(si));
            si.cbSize = sizeof(si);
            si.fMask = SIF_TRACKPOS;

            GetScrollInfo(hParent, SB_VERT, &si);

            iYNewPos = si.nTrackPos;
        }
        break;

        default:
            iYNewPos = iYCurrentScroll;
        }

        iYCurrentScroll = iYNewPos;

        // scroll child window with new position
        ScrollChild();

        return 0;
    }
    break;

    // user closed window
    case WM_DESTROY:
    {
        // set program shut-down flag
        blShutDown = TRUE;

        // tell program to terminate
        PostQuitMessage(0);
    }
    break;

    default:
        // process messages we don't deal with
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

/*
    child window procedure called by DispatchMessage
*/
LRESULT CALLBACK WindowProcedureChild (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // process messages by ID
    switch (message)
    {
    // set our manual move flag if user tries
    // to move the child window manually
    case WM_ENTERSIZEMOVE:
    {
        blManualMove = TRUE;
        return 0;
    }
    break;

    // un-set our manual move flag when user
    // stops trying to move window manually
    case WM_EXITSIZEMOVE:
    {
        blManualMove = FALSE;
        return 0;
    }
    break;

    // child window has been moved
    // programmatically or manually
    case WM_MOVE:
    {
        // if user is moving child window manually, this
        // will make it 'snap back' to where it belongs
        if (blManualMove)
        {
            ScrollChild();
            return 0;
        }

        // update our scrolling info
        ResizeScrollbars();
        return 0;
    }
    break;

    // child window has been sized
    // programmatically or manually
    case WM_SIZE:
    {
        // update our scrolling info
        ResizeScrollbars();
        return 0;
    }
    break;

    // end example if child is closed
    case WM_DESTROY:
    {
        // if the program isn't shutting down and user
        // closes child window, display message
        if (blShutDown == FALSE)
        {
            MessageBox(
                hParent,
                "The child window has been closed.\n\nThis example program will now end.",
                "Child window closed",
                MB_OK | MB_ICONEXCLAMATION
            );

            // tell program to terminate
            PostQuitMessage(0);

            return 0;
        }
    }
    break;

    default:
        // process messages we don't deal with
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

Update 1 [2010-05-28]:
I reworked my previous project to make this a little easier to understand, plus the previous glitches are gone. Now the compiled example is included in the file.

Update 2 [2010-12-06]:
Performed further code clean-up and made some comments more descriptive.

Update 3 [2012-06-14]:
Did a near complete overhaul on the code example above so that child window scrolls correctly when parent window is resized. Also trimmed a lot of 'fat' from the example so it's a little smaller...[LATER]...added code syntax coloring manually, so if anything looks weird, let me know.

 

Post A Comment

Your name:

Your e-mail address: (Will not be seen or used by anyone else but me)

To help cut down on spam, what do you get when you add two and four?:

Please type your message below: (Please limit message to less than 1,000 characters)

 

My Story   |   Business site   |   TGIF2   |   JWM   |   Openbox   |   Autism