How do I calculate the value to set into SCROLLINFO.nMax
to my window? currently I'm looping over all the controls in the window, keeping track of the smallest top
and highest bottom
values, so that I could get the size of the whole window, including those out of display area, not currently being show but still there. But something is wrong, I'm getting a big empty area in the end of scrollbar, it seems nMax
is too large. what am I missing? once you scroll down entirely, it's supposed the window until the edit box show up. What the gab looks like now:
I'm getting this size like this:
int scrollHeight(void)
{
int mTop = 0;
int mBottom = 0;
RECT rt = {0};
for(int i = 0; i < MAX_CONTROLS; i++)
{
HWND h = allControls[i];
if(h == NULL) break;
memset(&rt, 0, sizeof(RECT));
if(!GetWindowRect(h, &rt))
{
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
mBottom = my_max(mBottom, rt.bottom);
mTop = my_min(mTop, rt.top);
}
return mBottom - mTop;
}
setting the scrollbar like this:
void setUpScrollBar(HWND hwnd)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = scrollHeight();
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>
#ifdef UNICODE
#define STRSPLIT wcsrchr
#else
#define STRSPLIT strrchr
#endif
#define __FILENAME__ (STRSPLIT(TEXT(__FILE__), '/') ? STRSPLIT(TEXT(__FILE__), '/') + 1 : TEXT(__FILE__))
#define NAMEOF(s) TEXT(#s)
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK CreateTabProc(HWND, UINT, WPARAM, LPARAM);
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void CreateTab(HWND hwnd);
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text);
void CreateButtons(HWND hwnd);
RECT GetLocalCoordinates(HWND hWnd);
int scrollHeight(void);
int getHeight(HWND control);
void setUpScrollBar(HWND hwnd);
void pushControl(HWND);
inline int my_max(int a, int b);
inline int my_min(int a, int b);
void showText(HWND hwnd);
HINSTANCE ghInstance;
HWND hTab;
HWND hLabel1, hLabel2;
HWND hEdit1;
#define MAX_CONTROLS 8
static const wchar_t *title[] = { L"Button A1", L"Button B2", L"Button C3",
L"Button D4", L"Button E5", L"Button F6",
L"Button G" , L"Button 001", L"Button 002",
L"Button 003", L"Button 004",
L"Button 005", L"Button 006" };
HWND hButton[sizeof(title)/sizeof(title[0])] = {0};
HWND allControls[MAX_CONTROLS];
int allControls_indx = 0;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow)
{
MSG msg = {0};
HWND hwnd;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
InitComControls();
if(!RegisterClass(&wc)) {
ErrorExit(NAMEOF(RegisterClass), __LINE__, __FILENAME__);
}
int width = 500;
int height = 350/2; // half than the usual size, so that the scrollbar show up and we can test it
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int cx = (screenWidth - width) / 2;
int cy = (screenHeight - height) / 2;
hwnd = CreateWindowW(wc.lpszClassName, L"Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
cx, cy, width, height, NULL, NULL,
hInstance, NULL);
pushControl(hwnd); // push main window too
ghInstance = hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static int g_scrollY;
switch(msg)
{
case WM_CREATE:
hLabel1 = CreateWindowW(L"Static", L"This is label 1...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 10, 130, 25, hwnd, (HMENU) 18, NULL, NULL);
pushControl(hLabel1);
hLabel2 = CreateWindowW(L"Static", L"This is label 2...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 40, 130, 25, hwnd, (HMENU) 19, NULL, NULL);
pushControl(hLabel2);
CreateTab(hwnd);
CreateButtons(hwnd);
setUpScrollBar(hwnd);
//ChangeToDefaultFont(hwnd);
break;
case WM_VSCROLL:
{
int action = LOWORD(wParam);
//HWND hScroll = (HWND)lParam;
int pos = -1;
if (action == SB_THUMBPOSITION || action == SB_THUMBTRACK) {
pos = HIWORD(wParam);
} else if (action == SB_LINEDOWN) {
pos = g_scrollY + 30;
} else if (action == SB_LINEUP) {
pos = g_scrollY - 30;
}
if (pos == -1)
break;
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
si.nPos = pos;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
pos = si.nPos;
POINT pt;
pt.x = 0;
pt.y = pos - g_scrollY;
HDC hdc = GetDC(hwnd);
LPtoDP(hdc, &pt, 1);
ReleaseDC(hwnd, hdc);
ScrollWindow(hwnd, 0, -pt.y, NULL, NULL);
g_scrollY = pos;
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void pushControl(HWND hwnd)
{
if(allControls_indx > MAX_CONTROLS) {
assert(!"no room for extra controls");
}
allControls[allControls_indx++] = hwnd;
}
void CreateTab(HWND hwnd)
{
hTab =
CreateWindow(WC_TABCONTROLW, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
100, 80, 400, 250,
hwnd,
(HMENU) 1,
NULL,
NULL);
InsertTabItem(hTab, 2, L"Tab 1");
InsertTabItem(hTab, 3, L"Tab b");
pushControl(hTab);
}
void CreateButtons(HWND hwnd)
{
RECT rt = GetLocalCoordinates(hTab);
TabCtrl_AdjustRect(hTab, FALSE, &rt);
RECT rt2 = {0};
GetWindowRect(hTab, &rt2);
int tab_width = rt2.right - rt2.left;
int tab_height = rt2.bottom - rt2.top;
int id = 4;
const int cy_breakSize = 25;
int cx_initPos = rt.left;
int cy_initPos = rt.top;
int cx = cx_initPos;
int cy = cy_initPos;
const int button_width = 80;
const int button_height = 25;
const int cx_margin = 10;
int nMaxButtonPerRow = tab_width / (button_width + cx_margin);
for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
{
if(i != 0 && (i % nMaxButtonPerRow) == 0) {
cy += cy_breakSize;
cx = cx_initPos;
}
hButton[i] =
CreateWindow(L"button", title[i],
WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL,
cx,
cy,
button_width,
button_height,
hwnd, (HMENU) id++, NULL, NULL);
cx += button_width;
}
const int edit_width = 180;
const int edit_height = 25;
hEdit1 = CreateWindow(L"Edit", L"Hello, world!",
WS_VISIBLE | WS_CHILD | WS_BORDER,
cx,
// put below tab control's display area
getHeight(hTab) + cx,
edit_width,
edit_height,
hwnd,
(HMENU) id++,
NULL, NULL);
pushControl(hEdit1);
cx += edit_width;
}
void setUpScrollBar(HWND hwnd)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = scrollHeight();
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
int getHeight(HWND control)
{
RECT rt;
if(!GetWindowRect(control, &rt)) {
ErrorExit(NAMEOF(getHeight), __LINE__, __FILENAME__);
}
return rt.bottom - rt.top;
}
int scrollHeight(void)
{
int mTop = 0;
int mBottom = 0;
RECT rt = {0};
for(int i = 0; i < MAX_CONTROLS; i++)
{
HWND h = allControls[i];
if(h == NULL) break;
memset(&rt, 0, sizeof(RECT));
if(!GetWindowRect(h, &rt))
{
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
mBottom = my_max(mBottom, rt.bottom);
mTop = my_min(mTop, rt.top);
}
return mBottom - mTop;
}
inline int my_max(int a, int b)
{
return a > b ? a : b;
}
inline int my_min(int a, int b)
{
return a < b ? a : b;
}
RECT GetLocalCoordinates(HWND hWnd)
{
RECT Rect;
GetWindowRect(hWnd, &Rect);
MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT) &Rect, 2);
return Rect;
}
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
TCITEMW tci = {0};
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = lstrlenW(text);
SendMessage(tabHwnd, TCM_INSERTITEM, id, (LPARAM) &tci);
}
void InitComControls()
{
INITCOMMONCONTROLSEX icex;
/* initialize this component is required to use tab control,
it seems.
*/
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
}
// display the error message from last error seen then
// exit the application, with that last error code seen.
// to test function, do something like:
// if(!GetProcessId(NULL))
// errorExit(TEXT("GetProcessId"));
// not quite a unittest but yeah.
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename)
{
DWORD dw = ShowLastError(lpszFunction, line, filename);
ExitProcess(dw);
}
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename)
{
#define MAX_DIGITS 16
/*
* NOTE!!: calling GetLastError() must be done before calling
* any other function, that would reset the GetLastError(), making
* this function report error about the wrong function.
*/
DWORD dw = GetLastError();
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) +
lstrlen((LPCTSTR)lpszFunction) + 40 +
(line > 0 ? MAX_DIGITS : 0) +
(filename != NULL ? lstrlen(filename) : 0)) *
sizeof(TCHAR)
);
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with %d: %s"),
lpszFunction, dw, lpMsgBuf
);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
return dw;
}
This line in your scrollHeight()
function is the problem:
mTop = my_min(mTop, rt.top);
mTop
starts out as 0, so will always be the minimum. Instead of the distance between the top and bottom controls you end up with the distance from the top of the screen to the bottom of the bottom control (note that because you're using GetWindowRect
you are dealing with screen coordinates, not client coordinates - so this magnifies the problem).
If you change that line to the following it gives a much better result:
mTop = mTop ? my_min(mTop, rt.top) : rt.top;