ScrollCall is a demo program that takes a sample of Windows standard controls, along with a standard GDI image, and arranges them on a Device Context (or DC), in a window. Depending on the dimensions of the image, and the size of the containing window, horizontal and/or system scrollbars become visible, to enable scrolling for the image and controls. Thus ScrollCall is as at least as much focused on sizing as it is scrolling, and both offer unique challenges for the programmer, as will be seen in the following sections.

Introduction
ScrollCall
features:
- System scroll bars
- Optional groupbox
- Button to open images on the Device Context (DC)
- Radio options for choice of window scroll function
- Checkbox to stretch rather than scroll the image
- Label Paint Mult with UpDown and Buddy to increase the wait times of
WM_SIZE
during sizing, thus reduced WM_PAINT
processing - Right click for system snapshot of view in default or monitor attached to desktop
- Double-click to print the visible part of the (mostly empty) client window to the DC, and back to the client window (experimental)
ScrollCall
temporarily turns on SPI_SETDRAGFULLWINDOWS
for the testing of the visual effects of dragging, if ever it was toggled off - Compatibility with
AeroSnap
sizing
Background
The objective of a Graphical User Interface (GUI) based app is to promote a clear, uncluttered, and sizable form for ease of use.
ScrollCall
considers the scenario of sizing, scaling and scrolling controls adjacent to an image background which can also be scrolled with the same scrollbar. A handy feature if any controls containing text are too small to read.
ScrollCall
is intended to display the capabilities of the scrolling and sizing functions under a variety of conditions, but it has only been tested on (by today's standards at least), slow rigs.
In the next section, the purpose and utility of the main functions in ScrollCall
will have the focus, leaving the finer details to the reader.
Using the Code
Although ScrollCall
is tagged as C++, every attempt has been made to make it as "C-friendly" as possible, so that a future conversion to C may be possible. For best results, build ScrollCall
as a debug in Visual Studio. Alternatively, a release executable is included in the attachment, or made available at Github.
Painting
In ScrollCall
, as per requirement of ScrollWindow(Ex)
, we are obliged to use the WM_PAINT
message to paint the output of the scrolling function. With form sizing, we choose to perform painting for the loaded images and system screenshot with the function AdjustImage()
.
BOOL AdjustImage(HWND hWnd, HBITMAP hBitmap, HBITMAP &hBitmapScroll,
HGDIOBJ &hdefBitmap, HGDIOBJ &hdefBitmapScroll, BITMAP bmp, HDC& hdcMem,
HDC& hdcMemIn, HDC& hdcMemScroll, HDC hdcScreen, HDC hdcScreenCompat,
HDC hdcWinCl, UINT& bmpWidth, UINT& bmpHeight, int curFmWd, int curFmHt,
int resizePic, int minMaxRestore, BOOL newPic)
This function deals with the handling of the system screenshot as well as any images loaded from the disk, so the parameter list is a bit "overloaded". Most notable are the memory DCs: hdcMem
, hdcMemIn
, hdcMemScroll
, the handle to the system screenshot object, hdcScreen
, our compatible version of it, hdcScreenCompat
, and the window DC hdcWinCl
for all image output to our client window. These constitute a fundamental part of our image sandbox.
AdjustImage()
is divided into two sections each for both image and screenshot processing. The first section focuses more on manipulation of the data on the memory DCs, the second section is for faster Blting
during sizing moves. If Stretch
is toggled on, the image is painted to the limits of the window rectangle, so additional bitmap analysis is required to establish stretch cutoff co-ordinates to retain a reasonable image quality.
The painting for the printed window is performed by PaintScrolledorPrinted()
(gotten by double-click) called from a timer. The timer is used to avoid possible complications with window (in)validation by scrollbar changes after sizing.
PaintScrolledorPrinted(PAINTSTRUCT *ps, HDC hdcWinCl, HDC hdcMem,
int xNewSize, int yNewSize, UINT bmpWidth, UINT bmpHeight, BOOL printCl, BOOL noFill)
The printed image is not updated during sizing as can happen with the system screenshot.
Here's a typical implementation of BitBlt()
in ScrollCall
, where the action goes from right to left. The data from the rectangle in hdcMem
is blt'd into hdcWinCl
, the DC for our output client window, thus the visible effect is immediate.
BitBlt(hdcWinCl, wd - xCurrentScroll, -yCurrentScroll, bmpWidth + newPicWd,
bmpHeight, hdcMem, newPicWd, 0, SRCCOPY);
Scrolling
What scrolling function to choose? ScrollDC()
was the function of choice in scrolling the printed window (got by double-click). This is partly because the generation of a complete printed window of a client window is performed in a different way to default image handling.
ScrollDC()
works in a different way to the other functions. It will not populate PAINTSTRUCT's rcPaint
with co-ordinates for convenient painting. ScrollDC()
will also not scroll owned/child/parent/sibling windows which may be visible in the DC, so if there are controls not clipped out by the DC, the scrolling image will be visible under them. ScrollCall
can also use the groupbox
around the controls to hide the scrolling around them.
PaintScrolledorPrinted(PAINTSTRUCT *ps, HDC hdcWinCl, HDC hdcMem, int xNewSize,
int yNewSize, UINT bmpWidth, UINT bmpHeight, BOOL printCl, BOOL noFill)
Thus PaintScrolledorPrinted()
, mentioned in the last section, effectively complements AdjustImage()
for all the painting in ScrollCall
. The printed window scrolled by ScrollDC()
never gets any data from *ps
, but the window scrolling functions ScrollWindow(Ex())
do.
In comparing the two functions below, ScrollDC()
does not scroll the rectangle containing the controls, whereas the magic required for ScrollWindowEx()
is promulgated by the SW_SCROLLCHILDREN
and SW_INVALIDATE
flags.
ScrollDC(hdcWinCl, -xDelta, 0, (CONST RECT*) &rectControls, (CONST RECT*) &rectControls,
(HRGN)NULL, (RECT*) &rectControls)
ScrollWindowEx(hWnd, -xDelta, 0, (CONST RECT*) NULL, (CONST RECT*) NULL, (HRGN)NULL,
(PRECT)NULL, SW_SCROLLCHILDREN | SW_INVALIDATE);
To test & compare usage with loaded images or system screenshot, we can choose either ScrollWindow()
or ScrollWindowEx()
within the program's GUI interface. Albeit little noticeable difference in the performance of the two, even on lightweight systems or power saver plans, the function recommended for general use today is ScrollWindowEx()
. In most situations, ScrollWindow()
operates the same way as ScrollWindowEx()
, but it requires Invalidaterect()
on the scrolled rectangle for WM_PAINT
processing.
The data required for scrolling is managed by ScrollInfo()
, which processes the various scrolling parameters determined from scrollXorY
.
ScrollInfo(HWND hWnd, int scrollXorY, int scrollType, int scrollDrag,
int xNewSize, int yNewSize, int bmpWidth, int bmpHeight, BOOL newPic)
Of most interest is when scrollXorY
is zero, which is when a scrollbar is created, changed in some way, or destroyed. The scrollbar
attributes are stored in the SCROLLINFO structure, where notably, the nPos
values correspond with the well used global variables xCurrentScroll
and yCurrentScroll
. For example, the vertical height (nMax
) of the system screenshot determines the range of values yCurrentScroll
assumes.
Sizing
For convenience, the default window size after load is based on a multiple of control width (wd
) and height (ht
), which are set as wd
and ht
for the Open Image button, half wd
and full ht
for the UpDown
& Buddy
, and wd
and half ht
for the rest of the controls. The function SizeControls()
is able to modify the size of the controls, as well as their vertical separation.
SizeControls(int bmpHeight, HWND hWnd, int &yOldScroll, int resizeType,
int curFmWd, int curFmHt, BOOL newPic)
Variables like yOldScroll
are required when it is desired for a replacement image to be positioned at the last scroll point rather than the zero default. As mouse speed is not tracked during sizing, ScrollCall
generates its own timed sizing with the variables newCtrlSizeTriggerWd
and newCtrlSizeTriggerHt
. Because the Open Image button is always set at the top of the client window, the vertical sizing and separation of the other controls require the extra management of a function like delegateSizeControl()
.
delegateSizeControl(RECT rectOpt, HWND hWndOpt, int oldOptTop,
int resizeType, int oldResizeType, int defOptTop, int yScrollBefNew,
int newCtrlSizeTriggerHt, int newWd, int newHt, int minHt, BOOL newPic, HWND buddyHWnd)
ScrollCall
uses the scaling factors scaleX
and scaleY
modified by GetDims()
to meet the various conditions passed from SizeControls()
.
GetDims(HWND hWnd, int resizeType, int oldResizeType)
Of interest is during sizing, when firstSizeAfterSTART_SIZE_MOVE
is required to set the scale based on the window's previous state. Further, for each image load, the precise dimensions of the client window with any combination of scrollbars flagged by scrollStat
is calculated in:
InitWindowDims(HWND hWnd, int scrollStat, int& xNewSize, int& yNewSize,
UINT bmpWidth, UINT bmpHeight)
Finally, here's a typical implementation of a sizing function used in ScrollCall
:
SetWindowPos(hWndButton, NULL, -xCurrentScroll,
-yCurrentScroll, newWd, newHt, SWP_NOSENDCHANGING);
The flag SWP_NOSENDCHANGING
is added to avoid an extra WM_SIZE
message sent after this statement is executed.
Miscellany
If running ScrollCall
through a debugger, the debug log shows extra info when non zero values of Paint Mult are set. Can be of use when considering the need for less graphic output on high end systems. The format for this output is set out in ReportErr()
.
ReportErr(const wchar_t* szTypes, ...)
For zero values of Paint Mult, we know there is just about one WM_PAINT
processed for every WM_SIZE
. For non zero values of Paint Mult, the debug program log outputs the frequency of each event, noting that as Paint Mult increases, the WM_SIZE
hits decrease.
As the functionality of ScrollCall
applies to within the dimensions of the current monitor, any painting from moving/sizing outside the screen cannot be relied upon. Consequently, we have:
IsAllFormInWindow(HWND hWnd, BOOL toolTipOn, BOOL isMaximized)
If a "monitor breach" has occurred, IsAllFormInWindow()
calls CreateToolTipForRect()
for the tooltip notification.
When the setting in Control Panel: System Properties > Advanced > Performance > Settings is turned off, SetDragFullWindow()
turns it on temporarily to show the paint while sizing.
SetDragFullWindow(BOOL dragFullWindow, BOOL restoreDef)
The variable defDragFullWindow
holds the setting until it is required to be restored in the function outlined next:
Last, but not least, is the return of ScrollCall
memory allocations to the shared memory pool, by releasing, deleting, freeing, or just zeroing them. Kleenup()
is also required before each loading or reloading of an image.
Kleenup(HWND hWnd, HBITMAP& hBitmap, HBITMAP& hBitmapScroll, HGDIOBJ hdefBitmap,
HGDIOBJ hdefBitmapScroll, HBITMAP& hbmpCompat, GpBitmap*& pgpbm, HDC& hdcMem,
HDC& hdcMemIn, HDC& hdcMemScroll, HDC& hdcWinCl, int typeOfDC, BOOL noExit)
Points of Interest
For further reading, this article on painting and this article on scrolling complement the above subject matter rather well.
For more information and ideas for expansion on the project, please refer the Github wiki, as well as ScrollCall's known issues, logged there.
Scroll up! Scroll up! for the last bit.
We all know something is unscaled because no-one has ever attempt to scale it, yet something that is unscrolled? We are all sure to have scrolled it!
History
- 30th January, 2021: Version 1.0
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.