windows scroll bars

I am having a problem with disappearing scroll bars. I am talking about attached scroll bars, using Windows API and for this case just SB_VERT (vertical scrollbars). I have a content area (all of the content) and a client area (content that is currently visible on the screen). The calculation I am using is that scroll Range should always be Min = 0 and Max = ( content - client ). The page size is then calculated to be ( content - Max ). For those of you that don't know, page size is the amount that the scroll position jumps when you click inside the scroll box on either side of the thumb (the thing that scrolls).

This works fine except for a few weird problems.

1 - When the client is around content/2 the scroll bars disappear
2 - The scrollbars only disappear when resizing the window to be bigger

If you are as new to this as I am, you might want to check out
http://msdn.microsoft.com/en-us/library/bb787527%28v=vs.85%29.aspx
there's some great information.

Something particularly significant is: MaxScrollPos = MaxRangeValue - (PageSize - 1)

Unfortunately windows is very vague as to how they are actually handling things in the WinAPI calls :-/
Last edited on
Scroll bar coding is really tricky, as you are finding ceruleus. You really need to know how to debug, and watch the values of all your variables pertaining to scrolling. I always open a debug log file to do it. I didn't check out your link; I learned from Charles Petzold's Programming Windows books. He has a whole chapter on it.

I have quite a few examples of scrolling that are pretty basic I could post, but when I did a quick look none I could find handle the SB_THUMBTRACK message, only line up, line down, page up, and page down, and doing the same with keys, etc.
Last edited on
Here's what I believe to be a working scroll program that handles all or most of the verticle scroll bar notification messages. I have a 'MYDEBUG' symbol defined at top that if not commented out produces quite a bit of scroll bar logic.

//compiled with VC++ 9.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//WinTypes.h
#ifndef WINTYPES_H
#define WINTYPES_H

typedef struct    WindowsEventArguments               
{
 HWND             hWnd;                               
 WPARAM           wParam;                             
 LPARAM           lParam;                             
 HINSTANCE        hIns;                               
}WndEventArgs,    *lpWndEventArgs;


struct EVENTHANDLER
{
 unsigned int    Code;
 long            (*fnPtr)(lpWndEventArgs);
};

#endif 


continued...
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
//Main.cpp
#include      <windows.h>
#include      <tchar.h>
#include      <stdio.h>
#include      <string.h>
#include      <math.h>
#include      "WinTypes.h"  
//#define       MYDEBUG                  
EVENTHANDLER  EventHandler[5];
#if defined(MYDEBUG)
FILE* fp=NULL;
#endif
   
long fnWndProc_OnCreate(lpWndEventArgs Wea)     //Offset   What's Stored There
{                                               //================================
 unsigned int iLineCount=200,i;                 //0  -  3  iLineCount
 TCHAR szBuffer[80], szNum[16];                 //4  -  7  ptrPtrBuffer 
 TCHAR** ptrPtrBuffer=NULL;                     //8  -  11 cyChar 
 TEXTMETRIC tm;                                 //12 -  15 si.nPos, i.e., iStart                             
 HDC hDC;

 #if defined(MYDEBUG)
 fp=_tfopen(_T("Output.txt"),_T("w"));
 _ftprintf(fp,_T("Output.txt Opened In fnWndProc_OnCreate()\n"));
 #endif
 Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;                  
 SetWindowLong(Wea->hWnd,0,iLineCount);                               
 ptrPtrBuffer=(TCHAR**)GlobalAlloc(GPTR,sizeof(TCHAR*)*iLineCount);   
 SetWindowLong(Wea->hWnd,4,(long)ptrPtrBuffer);
 for(i=0;i<iLineCount;i++)
 {
     _tcscpy(szBuffer,_T("  "));
     _stprintf(szNum,_T("%u"),i);
     _tcscat(szBuffer,szNum);
     _tcscat(szBuffer,_T("  This Is Line #"));
     _tcscat(szBuffer,szNum);
     ptrPtrBuffer[i]=(TCHAR*)GlobalAlloc(GPTR,sizeof(TCHAR)*(_tcslen(szBuffer)+1)); 
     _tcscpy(ptrPtrBuffer[i],szBuffer);
 }
 hDC=GetDC(Wea->hWnd);
 GetTextMetrics(hDC,&tm);
 SetWindowLong(Wea->hWnd,8,(long)tm.tmHeight);
 ReleaseDC(Wea->hWnd,hDC);
 SetWindowLong(Wea->hWnd,12,0);
 #if defined(MYDEBUG)
 _ftprintf(fp,_T("Leaving fnWndProc_OnCreate()\n\n"));
 #endif

 return 0;                                             
}


long fnWndProc_OnSize(lpWndEventArgs Wea)            
{
 int iLinesVisible,iLineCount;
 unsigned int cyChar;
 SCROLLINFO si;
 
 ZeroMemory(&si, sizeof(SCROLLINFO));
 si.cbSize =sizeof(SCROLLINFO);
 si.fMask = SIF_POS | SIF_RANGE;
 GetScrollInfo(Wea->hWnd,SB_VERT,&si);
 iLineCount=(unsigned int)GetWindowLong(Wea->hWnd,0);
 cyChar=(unsigned int)GetWindowLong(Wea->hWnd,8);
 iLinesVisible=HIWORD(Wea->lParam)/cyChar;
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask =   SIF_POS | SIF_RANGE;  
 si.nMin = 0;
 si.nMax = iLineCount-iLinesVisible;
 si.nPos=GetWindowLong(Wea->hWnd,12);
 if(si.nMax<0)
    si.nMax=0;
 SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
 
 return 0;
}


long fnWndProc_OnVScroll(lpWndEventArgs Wea)            
{
 SCROLLINFO si;

 si.nPos=(int)GetWindowLong(Wea->hWnd,12);
 switch(LOWORD(Wea->wParam))
 {
  case SB_LINEUP:
    if(si.nPos)
    {
       si.cbSize = sizeof(SCROLLINFO);
       si.nPos--;
       SetWindowLong(Wea->hWnd,12,(long)si.nPos);
       si.fMask = SIF_POS;
       SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
       ScrollWindow(Wea->hWnd,0,GetWindowLong(Wea->hWnd,8),0,0);
    }
    break;
  case SB_PAGEUP:
    {
       unsigned int iLinesVisible;
       RECT rc;
       GetClientRect(Wea->hWnd,&rc);
       iLinesVisible=rc.bottom/GetWindowLong(Wea->hWnd,8);
       si.cbSize = sizeof(SCROLLINFO);
       si.nPos=si.nPos-iLinesVisible;
       if(si.nPos<0)
          si.nPos=0;
       SetWindowLong(Wea->hWnd,12,(long)si.nPos);
       si.fMask = SIF_POS;
       SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
       ScrollWindow(Wea->hWnd,0,rc.bottom,0,0);
    }
    break;
  case SB_LINEDOWN:
    #if defined(MYDEBUG)
    _ftprintf(fp,_T("Entering SB_LINEDOWN\n"));
    #endif
    si.cbSize =sizeof(SCROLLINFO);
    si.fMask = SIF_POS | SIF_RANGE;
    GetScrollInfo(Wea->hWnd,SB_VERT,&si);
    #if defined(MYDEBUG)
    _ftprintf(fp,_T("  si.nPos = %u\n"),si.nPos);
    #endif
    if(si.nPos<si.nMax)
    {
       si.nPos++;
       SetWindowLong(Wea->hWnd,12,(long)si.nPos);
       si.fMask = SIF_POS;
       SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
       ScrollWindow(Wea->hWnd,0,-GetWindowLong(Wea->hWnd,8),0,0);
    }
    #if defined(MYDEBUG)
    _ftprintf(fp,_T("Leaving SB_LINEDOWN\n\n"));
    #endif
    break;
  case SB_PAGEDOWN:
    {
       int iLinesVisible;
       RECT rc;
       si.cbSize =sizeof(SCROLLINFO);
       si.fMask = SIF_POS | SIF_RANGE;
       GetScrollInfo(Wea->hWnd,SB_VERT,&si);
       GetClientRect(Wea->hWnd,&rc);
       iLinesVisible=rc.bottom/GetWindowLong(Wea->hWnd,8);
       if(iLinesVisible+si.nPos<=si.nMax)
          si.nPos = si.nPos + iLinesVisible;
       else
          si.nPos=si.nMax;
       SetWindowLong(Wea->hWnd,12,(long)si.nPos);
       si.fMask = SIF_POS;
       SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
       ScrollWindow(Wea->hWnd,0,rc.bottom,0,0);
    }
    break;
  case SB_THUMBTRACK:
    {
       int iLinesVisible;
       RECT rc;
       #if defined(MYDEBUG)
       _ftprintf(fp,_T("Entering SB_THUMBTRACK\n"));
       #endif
       si.cbSize =sizeof(SCROLLINFO);
       si.fMask=SIF_TRACKPOS | SIF_RANGE;
       GetScrollInfo(Wea->hWnd,SB_VERT,&si);
       #if defined(MYDEBUG)
       _ftprintf(fp,_T("  si.nTrackPos = %u\n"),si.nTrackPos);
       #endif
       GetClientRect(Wea->hWnd,&rc);
       iLinesVisible=rc.bottom/GetWindowLong(Wea->hWnd,8);
       if(iLinesVisible+si.nTrackPos<=si.nMax)
          si.nPos = si.nTrackPos;
       else
          si.nPos=si.nMax;
       SetWindowLong(Wea->hWnd,12,(long)si.nPos);
       si.fMask = SIF_POS;
       SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
       ScrollWindow(Wea->hWnd,0,rc.bottom,0,0);
       #if defined(MYDEBUG)
       _ftprintf(fp,_T("Leaving SB_THUMBTRACK\n\n"));
       #endif
    }
    break;
 }
 
 return 0;
}


long fnWndProc_OnPaint(lpWndEventArgs Wea)            
{
 unsigned int iLineCount,i,iStart,iFinish,iLine,iPos;
 TCHAR** ptrPtrBuffer=NULL;
 PAINTSTRUCT ps; 
 long cyChar;
 HDC hDC;

 iLineCount=(unsigned int)GetWindowLong(Wea->hWnd,0);  //How Many Lines In Scroll Buffer?
 ptrPtrBuffer=(TCHAR**)GetWindowLong(Wea->hWnd,4);     //Buffer Holding String Pointers
 cyChar=GetWindowLong(Wea->hWnd,8);                    //How High Is Each Line?
 iPos=GetWindowLong(Wea->hWnd,12);                     //What's The Top Line Number?
 hDC=BeginPaint(Wea->hWnd,&ps);                        //Need A Device Context To Print In Windows!
 iStart=ps.rcPaint.top/cyChar;                         //Gotta Find Bounds Of Invalid Region In
 iFinish=ps.rcPaint.bottom/cyChar;                     //Terms Of Top & Bottom.
 for(i=iStart;i<=iFinish;i++)
 {
     iLine=iPos+i;
     if(iLine<iLineCount)
        TextOut(hDC,0,i*cyChar,ptrPtrBuffer[iLine],_tcslen(ptrPtrBuffer[iLine]));
 }
 EndPaint(Wea->hWnd,&ps);
 
 return 0;
}


long fnWndProc_OnClose(lpWndEventArgs Wea)            
{
 unsigned int blnFree=NULL,iLineCount;
 TCHAR** ptrPtrBuffer=NULL;
 unsigned int i;
 
 #if defined(MYDEBUG)
 _ftprintf(fp,_T("Entering fnWndProc_OnClose()\n"));
 #endif
 iLineCount=(unsigned int)GetWindowLong(Wea->hWnd,0);
 ptrPtrBuffer=(TCHAR**)GetWindowLong(Wea->hWnd,4);
 for(i=0;i<iLineCount;i++)
     GlobalFree(ptrPtrBuffer[i]);
 GlobalFree(ptrPtrBuffer);
 DestroyWindow(Wea->hWnd);
 PostQuitMessage(WM_QUIT);
 #if defined(MYDEBUG)
 _ftprintf(fp,_T("Leaving fnWndProc_OnClose()\n"));
 fclose(fp);
 #endif
 
 return 0;
}


void AttachEventHandlers(void)  
{
 EventHandler[0].Code=WM_CREATE,    EventHandler[0].fnPtr=fnWndProc_OnCreate;  
 EventHandler[1].Code=WM_PAINT,     EventHandler[1].fnPtr=fnWndProc_OnPaint;  
 EventHandler[2].Code=WM_SIZE,      EventHandler[2].fnPtr=fnWndProc_OnSize;  
 EventHandler[3].Code=WM_VSCROLL,   EventHandler[3].fnPtr=fnWndProc_OnVScroll;  
 EventHandler[4].Code=WM_CLOSE,     EventHandler[4].fnPtr=fnWndProc_OnClose; 
}


continued...
contined from last post...

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
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;
   
 for(unsigned int i=0;i<5;i++)
 {
     if(EventHandler[i].Code==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)
{                                   
 TCHAR szClassName[]=TEXT("ScrollWindow");              //In WinMain the Message Handlers are
 WNDCLASSEX wc;                                         //attached, a WNDCLASSEX structure
 MSG messages;                                          //is filled out and registered with
 HWND hWnd;                                             //Windows, A CreateWindow() call made
                                                        //to create the main window, and the
 AttachEventHandlers();                                 //program's main message loop entered.
 wc.lpszClassName=szClassName;                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);                         wc.style=CS_DBLCLKS;
 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=16;
 wc.lpszMenuName=NULL;                                  wc.cbClsExtra=0; 
 RegisterClassEx(&wc);
 hWnd=CreateWindow(szClassName,szClassName,WS_OVERLAPPEDWINDOW|WS_VSCROLL,200,100,300,228,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
  TranslateMessage(&messages);
  DispatchMessage(&messages);
 }

 return messages.wParam;
}
thank you freddie for the great post! There's a lot of really useful stuff there that I have been dying to see.

this portion particularly interests me:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
long fnWndProc_OnSize(lpWndEventArgs Wea)            
{
 int iLinesVisible,iLineCount;
 unsigned int cyChar;
 SCROLLINFO si;
 
 ZeroMemory(&si, sizeof(SCROLLINFO));
 si.cbSize =sizeof(SCROLLINFO);
 si.fMask = SIF_POS | SIF_RANGE;
 GetScrollInfo(Wea->hWnd,SB_VERT,&si);
 iLineCount=(unsigned int)GetWindowLong(Wea->hWnd,0);
 cyChar=(unsigned int)GetWindowLong(Wea->hWnd,8);
 iLinesVisible=HIWORD(Wea->lParam)/cyChar;
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask =   SIF_POS | SIF_RANGE;  
 si.nMin = 0;
 si.nMax = iLineCount-iLinesVisible;
 si.nPos=GetWindowLong(Wea->hWnd,12);
 if(si.nMax<0)
    si.nMax=0;
 SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
 
 return 0;
}


So when you set the scroll info, you have your minimum range = 0, your maximum range = ( total content - visible content ) and you don't seem to be setting your page size at any point. Page size is the amount your view changes when you click inside the scroll box, it should usually be equal to the visible area. When you run this code what happens when you click in the scroll box? Please let me know if you're not sure what I'm talking about. I'm new to scroll bars haha

edit: to clarify, I'm talking about setting the page size refered to by si.PageSize, you need to set si.fMask = SIF_PAGE or SIF_ALL... something like that
Last edited on
For whatever reason, I just usually don't bother with the pagr size, which only controls the size of the scroll bar thumb. If you don't set it, Windows just uses a default size. Anyway, here is an updated OnSize() handler which sets and uses the page size...

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
long fnWndProc_OnSize(lpWndEventArgs Wea)            
{
 int iLinesVisible,iLineCount;
 unsigned int cyChar;
 SCROLLINFO si;
 
 ZeroMemory(&si, sizeof(SCROLLINFO));
 si.cbSize =sizeof(SCROLLINFO);
 si.fMask = SIF_POS | SIF_RANGE;
 GetScrollInfo(Wea->hWnd,SB_VERT,&si);
 iLineCount=(unsigned int)GetWindowLong(Wea->hWnd,0);
 cyChar=(unsigned int)GetWindowLong(Wea->hWnd,8);
 iLinesVisible=HIWORD(Wea->lParam)/cyChar;
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask  = SIF_POS | SIF_RANGE | SIF_PAGE;  
 si.nMin   = 0;
 si.nPage  = iLinesVisible;
 si.nMax   = iLineCount-iLinesVisible;
 si.nPos=GetWindowLong(Wea->hWnd,12);
 if(si.nMax<0)
    si.nMax=0;
 SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
 
 return 0;
}


okay I'll see if I can manage to get all the set Page calls out of the code... When you initialize a SCROLLINFO structure do you know what nPage starts out as?
As I said, scroll code is rather treacherous. I have some tutorials on it here, but they are in PowerBASIC - Not C++...

http://www.jose.it-berater.org/smfforum/index.php?topic=1299.0

However, its WinApi SDK code and the function calls & techniques are exactly the same.

There are a lot of techniques to implement scrolling. I'd say though that they fall into three catagories...
1) Using loops to output lines;

2) Using the ScrollWindow() Api call, which is done internally in Windows (my example
uses this);

3) Create a child window within the main frame window, output to that, then reposition that 'pane'.



When you initialize a SCROLLINFO structure do you know what nPage starts out as?


Maybe I'm not understanding your question, but before something/anything is initialized its either some garbage value or zero. That applies to SCROLLINFO structs or any variable really. Note in my code above I used ZeroMemory() to null out the whole struct. That's easier than setting each individual field to zero.
Yea, its garbage values until you call ZeroMemory() then obviously its zero...

I'd like to point out - (FROM MICROSOFT)

You can set a page size for a scroll bar. The page size represents the number of data units that can fit in the client area of the owner window given its current size. For example, if the client area can hold 16 lines of text, an application would set the page size to 16. The system uses the page size, along with the scrolling range and length of the scroll bar shaft, to set the size of the scroll box. Whenever a window containing a scroll bar is resized, an application should call the SetScrollInfo function to set the page size.


That's from http://msdn.microsoft.com/en-us/library/bb787527%28v=vs.85%29.aspx

So basically my nPage should always be my visible area... This works great for some window sizes and others are totally thrown off.

Sorry for the lack of information and I really appreciate your help.
Last edited on
The modifications in the code I posted are doing exactly what your quote from Microsoft is stating. Compile and run my program and let me know if something isn't working and I'll look at it.
I can't believe I havn't heard from you ceruleus! That fnWndProc_OnSize() update to handle the si.nPage situation is a disaster! You must not have run my code. I had only looked at it quick and didn't check it out 'till later. Wow! What a disaster! I'm in the process of trying to get to the bottom of the situation and fix it. It may take awhile. Now I see why I never bothered in the past with the .nPage member of SCROLLINFO. I'll work on it until I get it, then post a working update. Essentially, all my scroll code needs to change to deal with that situation.

The original code I posted is OK though. Its that update that screws things up.
You're right I didn't run your code, I just looked at the parts that pertained to my problem to see if you were doing anything different. When I noticed that you weren't setting si.nPage I knew I had a different problem. I've narrowed my problem down and it's something really strange where when I update the scroll bar on a window size changed event the function to set the Page size gets called twice for some reason (only when I make the window size bigger).

So needless to say I am very frustrated with this problem!!!!!!!!!!!!!!

Anyway, the reason why that is a problem is that in my function that updates the page size, whenever I update page size I reset the max range to include the new page size. Since that is happening twice (mysteriously) my range gets incremented by 2*page size instead of just page size. That's my new problem.

For the record, the only thing I did to prevent the scrollbars from disappearing was move my SetVisible(TRUE) call until after my SetScrollInfo() call. That fixed it.
okay, I've fixed the vertical scrollbars. What was going on was whenever I called SetRange() the Max size was getting set which caused another window size changed event. This recursion was causing my max size to go out of whack. I basically just made a new method that always returns the correct MaxRange and anywhere si.nMax was getting set I made sure it was set equal to this new method. That's a really vague description but that's basically what I did.

Now I'm on to fixing the horizontal scroll bars which is trickier because it involves locked columns! :X
When you click one of the direction arrows (up, down, left, right) on a scroll bar, there are two WM_VSCROLL or two WM_HSCROLL messages sent; one when you press the mouse button, and one when you release.

The other tricky issue with setting the nPage member is that Windows adjusts the range itself. It doesn't do that if nPage isn't set. That's the biggest thing had me thrown off.
Topic archived. No new replies allowed.