BrainOut!
The mumblings of a Christian autistic husband, dad, IT guy and amateur radio operator - Will Brokenbourgh / AF7EC
Window scrolling example - Windows API (Win32/64) [update 3]
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.
If you have any questions, just leave a comment.
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.