Child Window with Child Scrollbars

Little by little, this project is coming together. I now wish to use child scrollbars to scroll a child window. Can anyone explain this procedure? I have already created child windows, and child scrollbars. I was just wondering how to link them. I am assuming that this involves the "SendMessage()" function. Or, does this involve subclassing?

I should note, that I wish to use two child windows. The vertical scrollbar will control both children simultaneously. However, each child will have its own horizontal scroll.

For the moment though, I would like to try a simple exercise. I want to create a child window that displays an image. There is one child scrollbar for the horizontal, and one for the vertical. Just a simple explanation of the procedure should do, as I have already worked with these individual components (loading images, child windows, child scrollbars, etc).

On a somewhat related topic, can the thumbs of child scrollbars be made "proportional".
Last edited on
I could help you with that too anachronon, but I'm still working on the tutorial about dynamic multi-dimensional arrays. I've posted a lot of it in your subclassing post, as I can't start new topics in this forum - only reply to other's posts like I'm doing now. When I get it all done I believe I'll post it too on my board at another forum, as nobody will find it here under a subclassing topic.

I will write a sentence or two on your scrolling though. Your parent window acts as a viewport of a larger child window containing your graphics. Say you have a 600X600 pixel parent window, and the child of the parent is 1200X1200 pixels. Windows will automatically/transparently clip the child so you will only see a bit less than 600X600 pixels of it. In your WM_HSCROLL and WM_VSCROLL message handlers you use MoveWindow() to move the child which reveals parts hidden previously by the parent.
It only took me a couple minutes to find one of my 'proof of concept' test apps from around 2012 and test check it for build-ability under VC2019 under x86 and x64. At that time I was mostly using GCC. In any case, here is ScrollControls.cpp and ScrollControls.h. You'll need to adapt the code to scroll your graphics - this app scrolls child window button, label, and edit controls on a 'pane' child of the main parent. First, ScrollControls.h....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//ScrollControls.h
#ifndef Main_h
#define Main_h
#define                       ID__PANE              1500
#define                       IDC_FIRST_NAME        1505
#define                       IDC_MIDDLE_NAME       1510
#define                       IDC_LAST_NAME         1515
#define                       IDC_ADDRESS1          1520
#define                       IDC_ADDRESS2          1525
#define                       IDC_CITY              1530
#define                       IDC_STATE             1535
#define                       IDC_COUNTRY           1540
#define                       IDC_ZIP               1545
#define                       IDC_EMAIL             1550
#define                       IDC_TELEPHONE         1555
#define                       IDC_INTERESTS         1560
#define                       IDC_SUBMIT            1565
#define                       dim(x)                (sizeof(x) / sizeof(x[0]))

struct WndEventArgs
{
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;
};

typedef WndEventArgs*         lpWndEventArgs;

struct EVENTHANDLER
{
 unsigned int                 iMsg;
 LRESULT                      (*fnPtr)(lpWndEventArgs);
};

// For Main Program Window
LRESULT fnWndProc_OnCreate    (lpWndEventArgs Wea);
LRESULT fnWndProc_OnVScroll   (lpWndEventArgs Wea);
LRESULT fnWndProc_OnDestroy   (lpWndEventArgs Wea);

const EVENTHANDLER            EventHandler[]=
{
 {WM_CREATE,                  fnWndProc_OnCreate},
 {WM_VSCROLL,                 fnWndProc_OnVScroll},
 {WM_DESTROY,                 fnWndProc_OnDestroy}
};
#endif 
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// cl ScrollControls.cpp /O1 /Os kernel32.lib user32.lib gdi32.lib 
// cl ScrollControls.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib gdi32.lib  
//  8,192 Bytes TCLib x64
// 79,872 Bytes LIBCMT x86
// 95,232 Bytes LIBCMT x64
#include             <windows.h>
//#define TCLib
#ifdef TCLib
   #include          "string.h"
   #include          "tchar.h"
#else
   #include          <string>
   #include          <tchar.h>
#endif 
#include             "ScrollControls.h"


LRESULT CALLBACK fnPaneProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 if(msg==WM_COMMAND && LOWORD(wParam)==IDC_SUBMIT)
    MessageBox(NULL,_T("You Apparently Want To Submit The Information!"),_T("Here Is What You Want..."),MB_OK);

 return (DefWindowProc(hwnd,msg,wParam,lParam));
}


LRESULT fnWndProc_OnCreate(lpWndEventArgs Wea)
{
 HWND hCtrl,hPane;
 TCHAR szPane[8];
 SCROLLINFO vsi;
 WNDCLASSEX wc;
 RECT rc;

 Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
 _tcscpy(szPane,_T("Pane"));                           //Set up 'Pane' class
 wc.cbSize=sizeof(WNDCLASSEX),                         wc.style=CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc=fnPaneProc,                            wc.cbClsExtra=0;
 wc.cbWndExtra=0,                                      wc.hInstance=Wea->hIns;
 wc.hIcon=0,                                           wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW,             wc.lpszMenuName=NULL;
 wc.lpszClassName=szPane,                              wc.hIconSm=0;
 RegisterClassEx(&wc);
 hPane=CreateWindowEx(0,szPane,_T(""),WS_CHILD|WS_VISIBLE,0,0,325,490,Wea->hWnd,(HMENU)ID__PANE,Wea->hIns,0);
 SetWindowLongPtr(Wea->hWnd,0,(LONG_PTR)hPane);

 //Create all the child window controls on the 'Pane'
 hCtrl=CreateWindowEx(0,_T("static"),_T("First Name"),WS_CHILD|WS_VISIBLE,10,10,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,10,150,25,hPane,(HMENU)IDC_FIRST_NAME,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Middle Name"),WS_CHILD|WS_VISIBLE,10,40,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,40,150,25,hPane,(HMENU)IDC_MIDDLE_NAME,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Last Name"),WS_CHILD|WS_VISIBLE,10,70,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,70,150,25,hPane,(HMENU)IDC_LAST_NAME,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Address1"),WS_CHILD|WS_VISIBLE,10,100,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,100,150,25,hPane,(HMENU)IDC_ADDRESS1,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Address2"),WS_CHILD|WS_VISIBLE,10,130,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,130,150,25,hPane,(HMENU)IDC_ADDRESS2,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("City"),WS_CHILD|WS_VISIBLE,10,160,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,160,150,25,hPane,(HMENU)IDC_CITY,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("State"),WS_CHILD|WS_VISIBLE,10,190,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,190,150,25,hPane,(HMENU)IDC_STATE,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Country"),WS_CHILD|WS_VISIBLE,10,220,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,220,150,25,hPane,(HMENU)IDC_COUNTRY,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Zip Code"),WS_CHILD|WS_VISIBLE,10,250,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,250,150,25,hPane,(HMENU)IDC_ZIP,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Email"),WS_CHILD|WS_VISIBLE,10,280,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,280,150,25,hPane,(HMENU)IDC_EMAIL,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Telephone"),WS_CHILD|WS_VISIBLE,10,310,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER,140,310,150,25,hPane,(HMENU)IDC_TELEPHONE,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("static"),_T("Interests"),WS_CHILD|WS_VISIBLE,10,340,100,25,hPane,(HMENU)-1,Wea->hIns,0);
 hCtrl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_BORDER|ES_MULTILINE,140,340,150,100,hPane,(HMENU)IDC_TELEPHONE,Wea->hIns,0);
 hCtrl=CreateWindowEx(0,_T("button"),_T("Submit"),WS_CHILD|WS_VISIBLE,100,455,100,25,hPane,(HMENU)IDC_SUBMIT,Wea->hIns,0);

 //Initialize Window's internal scrolling apparatus (vsi think verticle scroll info)
 GetClientRect(Wea->hWnd,&rc);       //Need size of main window's client area to determine .nMax
 vsi.cbSize=sizeof(SCROLLINFO);      //Api docs say to do this
 vsi.nMin=0;
 vsi.nMax=489;
 vsi.nPage=rc.bottom;
 vsi.fMask=SIF_PAGE|SIF_RANGE;
 SetScrollInfo(Wea->hWnd,SB_VERT,&vsi,TRUE);

 return 0;
}


LRESULT fnWndProc_OnVScroll(lpWndEventArgs Wea)
{
 int iVScrollPos;
 SCROLLINFO vsi;
 HWND hPane;

 ZeroMemory(&vsi, sizeof(SCROLLINFO));
 hPane=(HWND)GetWindowLongPtr(Wea->hWnd,0);
 vsi.cbSize=sizeof(SCROLLINFO);
 vsi.fMask=SIF_ALL;   //vsi.fMask=SIF_POS|SIF_RANGE;
 GetScrollInfo(Wea->hWnd,SB_VERT,&vsi);
 iVScrollPos=vsi.nPos;
 switch(LOWORD(Wea->wParam))
 {
   case SB_LINEUP:
        if(vsi.nPos>vsi.nMin)
           vsi.nPos=vsi.nPos-10;
        break;
   case SB_PAGEUP:
        vsi.nPos = vsi.nPos - vsi.nPage;
        break;
   case SB_LINEDOWN:
        if(vsi.nPos<vsi.nMax)
           vsi.nPos=vsi.nPos+10;
        break;
   case SB_PAGEDOWN:
        vsi.nPos = vsi.nPos + vsi.nPage;
        break;
   case SB_THUMBTRACK:
        vsi.nPos=vsi.nTrackPos;
        break;
 }
 vsi.fMask=SIF_POS;
 SetScrollInfo(Wea->hWnd,SB_VERT,&vsi,TRUE);
 GetScrollInfo(Wea->hWnd, SB_VERT, &vsi);
 if(vsi.nPos != iVScrollPos)
    MoveWindow(hPane,0,-1*vsi.nPos,325,500,TRUE);

 return 0;
}


LRESULT fnWndProc_OnDestroy(lpWndEventArgs Wea)
{
 PostQuitMessage(0);
 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(&Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 char szClassName[]=_T("ScrollControls");
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName=szClassName;                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);                         wc.style=CS_HREDRAW|CS_VREDRAW;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);               wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);  wc.cbWndExtra=sizeof(void*);
 wc.lpszMenuName=NULL;                                  wc.cbClsExtra=0;
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW^WS_MAXIMIZEBOX,100,100,350,300,0,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
       TranslateMessage(&messages);
       DispatchMessage(&messages);
 }

 return messages.wParam;
}
Hey Freddie1. Thanks for that. I think I might have a handle on what I am trying to do. I was working earlier today on the child window of my test. I have that up and running. Tomorrow, I will try messing with the child scrollbars.

I do have a couple specific questions on your code. I'll try to address them one at a time. My first question is on this line of code:

 
hPane=(HWND)GetWindowLongPtr(Wea->hWnd,0);


I see that you are using it to get the handle of the child window. How does the program know which child? What if there is more than one child, that you wish to get a handle?
There are two Window Classes Registered in the program; a "ScrollControls" Class registered in WinMain(), and a "Pane" Class Registered in fnWndProc_OnCreate(). When the CreateWindow() call was made in fnWndProc_OnCreate() to create one instance of the "Pane" class, the parent of this instance was set to the instance of the main or "ScrollControls" window, and the HWND returned from the CreateWindow() call was stored in the WNDCLASSEX::cbWndExtra bytes of that main program or app window by a call to SetWindowLongPtr(). In other words, or in OOP speak, it became a member variable of a parent class. The 'Getters' and 'Setters', or, in more OOP speak, 'mutators' of the class instance, in C based OOP, are GetWindowLongPtr() and SetWindowLongPtr(). An alternate way of doing this would be with GetProp() and SetProp(). For whatever personal reason, my habit is to use the GetWindowLong/SetWindowLong() approach.

Note that the HWND hPane variable was a local/stack based 'automatic' variable in fnWndProc_OnCreate() and fell out of scope when that function returned. However, it was needed again in another critical function - fnWndProc_OnVScroll(). It was needed there because a MoveWindow() call needed to be made on that window to cause the program to scroll. I was able to get it there where it was needed because I 'persisted' the value of the variable, as mentioned just above, in the WNDCLASSEX::cbWndExtra bytes.

Your question, "How does the program know which child?", I'm not sure I fully understand your question. There is only one instance of the "Pane" class in the program. The main window only has one child, which is the "Pane" window we are discussing. And the numeric value of its HWND is the only value stored or persisted in the main window's WNDCLASSEX::cbWndExtra bytes.

There are quite a few other child windows in the program, all of which are standard Windows Controls, i.e., static, edit, and button controls. These are all childs of the "Pane" window. None of them were ever used in the program. One can think of them as 'grandchildren' of the app's main window if one would like.

By the way, recall I stated I don't use global variables in my programs? Of course, this one doesn't have any. Neither does the one I posted for Malbor in that post of his. Its really not that hard to do. If you look at all my CreateWindow() calls in fnWndProc_OnCreate() for the child window controls you'll see I essentially threw away all the HWNDs returned by the CreateWindow() calls. In fact, strictly speaking, I would not have even needed to declare a hCtrl variable there - it was never used for anything, and I could have made bare CreateWindow() calls without having the function assign it to a variable at all! How so?

Because Windows, being the good behaving operating system that it is, keeps close track of every window created, every process, so on and so forth, within internal structures which it meticulously maintains, and will return that information to you with a simple function call asking for it. Note in ScrollControls.h I have #define statements where I assigned Control IDs for every child window to b e created. These serve as '
proxies' for the HWNDs of the controls/objects. They are one of the parameters of the CreateWindow() call (the HMENU parameter), and Windows internally keeps track of them. Windows will return the HWND of any object if you tell it to in a GetDlgItem() call where you specify the parent of the window and the Control ID of the window. That was never needed in this program because there is no coding for any of the controls; they were just put there so one could see them scroll.

This is kind of 'deep' but important stuff. That's why I'm mentioning it.
Thanks Freddie1. Really, it wasn't that "deep". I did miss a couple things, while studying your code. I didn't notice that "ScrollControls" was declared as your main window class. That threw me. I was also a bit confused by the multiple uses of "GetWindowLong()". If the second parameter is "0", then the function retrieves what has been stored as "extra". Pre-defined macros in the second parameter cause the function to return info about the window.

What also confused me, was how "hwnd" is used in every callback function. I forgot that "hwnd" was a local variable in every one of those functions. So, I got confused, as to which window was being called. I think in my code, I will need to use different names, to make it more clear.

The "GetDlgItem()" function may be what I am looking for. I don't know yet. I'll have to give it a try. I know I'll have more questions.
Say, anachronon, if I don't answer any posts for a couple days it'll be because I'm may be having laptop problems. I think the hard drive may be going on the only box I'm able to get on the internet with. I'm hoping I can make it to Tuesday, cuz I gotta go up to Pueblo, CO then, and I'll get a new laptop. Geez, I only got 4-1/2 years outta this one. Last HP I had I got 13 years outta it! Hopefully it'll keep goin 'till then!

Fred
Topic archived. No new replies allowed.