1 
2 #include "stdafx.h"
3 #include "ScrollHelper.h"
4 
5 #ifdef _DEBUG
6 #define new DEBUG_NEW
7 #undef THIS_FILE
8 static char THIS_FILE[] = __FILE__;
9 #endif
10 
11 // CScrollHelper /////////////////////////////////////////////////////////////////////
12 
CScrollHelper()13 CScrollHelper::CScrollHelper()
14 {
15     m_attachWnd   = NULL;
16     m_pageSize    = CSize(0,0);
17     m_displaySize = CSize(0,0);
18     m_scrollPos   = CSize(0,0);
19 }
20 
~CScrollHelper()21 CScrollHelper::~CScrollHelper()
22 {
23     DetachWnd();
24 }
25 
AttachWnd(CWnd * pWnd)26 void CScrollHelper::AttachWnd(CWnd* pWnd)
27 {
28     m_attachWnd = pWnd;
29 }
30 
DetachWnd()31 void CScrollHelper::DetachWnd()
32 {
33     m_attachWnd = NULL;
34 }
35 
SetDisplaySize(int displayWidth,int displayHeight)36 void CScrollHelper::SetDisplaySize(int displayWidth, int displayHeight)
37 {
38     m_displaySize = CSize(displayWidth, displayHeight);
39 
40     if ( m_attachWnd != NULL && ::IsWindow(m_attachWnd->m_hWnd) )
41         UpdateScrollInfo();
42 }
43 
GetDisplaySize() const44 const CSize& CScrollHelper::GetDisplaySize() const
45 {
46     return m_displaySize;
47 }
48 
GetScrollPos() const49 const CSize& CScrollHelper::GetScrollPos() const
50 {
51     return m_scrollPos;
52 }
53 
54 
GetPageSize() const55 const CSize& CScrollHelper::GetPageSize() const
56 {
57     return m_pageSize;
58 }
59 
SetScrollPos(int bar,int newpos,BOOL redraw)60 void CScrollHelper::SetScrollPos(int bar, int newpos, BOOL redraw)
61 {
62     if (m_attachWnd == NULL)
63         return;
64 
65     CRect rect;
66     GetClientRectSB(m_attachWnd, rect);
67     CSize windowSize(rect.Width(), rect.Height());
68 
69     if ((bar == SB_HORZ) && (windowSize.cx < m_displaySize.cx))
70     {
71         int deltaPos = newpos - m_scrollPos.cx;
72         m_scrollPos.cx += deltaPos;
73         m_attachWnd->SetScrollPos(SB_HORZ, m_scrollPos.cx, redraw);
74         if (redraw)
75         {
76             m_attachWnd->ScrollWindow(0, -deltaPos);
77         }
78     }
79 
80     if ((bar == SB_VERT) && (windowSize.cy < m_displaySize.cy))
81     {
82         int deltaPos = newpos - m_scrollPos.cy;
83         m_scrollPos.cy += deltaPos;
84         m_attachWnd->SetScrollPos(SB_VERT, m_scrollPos.cy, redraw);
85 
86         if (redraw)
87         {
88             m_attachWnd->ScrollWindow(0, -deltaPos);
89         }
90     }
91 }
92 
ScrollToOrigin(bool scrollLeft,bool scrollTop)93 void CScrollHelper::ScrollToOrigin(bool scrollLeft, bool scrollTop)
94 {
95     if ( m_attachWnd == NULL )
96         return;
97 
98     if ( scrollLeft )
99     {
100         if ( m_displaySize.cx > 0 && m_pageSize.cx > 0 && m_scrollPos.cx > 0 )
101         {
102             int deltaPos = -m_scrollPos.cx;
103             m_scrollPos.cx += deltaPos;
104             m_attachWnd->SetScrollPos(SB_HORZ, m_scrollPos.cx, TRUE);
105             m_attachWnd->ScrollWindow(-deltaPos, 0);
106         }
107     }
108 
109     if ( scrollTop )
110     {
111         if ( m_displaySize.cy > 0 && m_pageSize.cy > 0 && m_scrollPos.cy > 0 )
112         {
113             int deltaPos = -m_scrollPos.cy;
114             m_scrollPos.cy += deltaPos;
115             m_attachWnd->SetScrollPos(SB_VERT, m_scrollPos.cy, TRUE);
116             m_attachWnd->ScrollWindow(0, -deltaPos);
117         }
118     }
119 }
120 
OnHScroll(UINT nSBCode,UINT nPos,CScrollBar * pScrollBar)121 void CScrollHelper::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
122 {
123     if ( m_attachWnd == NULL )
124         return;
125 
126     const int lineOffset = 60;
127 
128     // Compute the desired change or delta in scroll position.
129     int deltaPos = 0;
130     switch( nSBCode )
131     {
132     case SB_LINELEFT:
133         // Left scroll arrow was pressed.
134         deltaPos = -lineOffset;
135         break;
136 
137     case SB_LINERIGHT:
138         // Right scroll arrow was pressed.
139         deltaPos = lineOffset;
140         break;
141 
142     case SB_PAGELEFT:
143         // User clicked inbetween left arrow and thumb.
144         deltaPos = -m_pageSize.cx;
145         break;
146 
147     case SB_PAGERIGHT:
148         // User clicked inbetween thumb and right arrow.
149         deltaPos = m_pageSize.cx;
150         break;
151 
152     case SB_THUMBTRACK:
153         // Scrollbar thumb is being dragged.
154         deltaPos = Get32BitScrollPos(SB_HORZ, pScrollBar) - m_scrollPos.cx;
155         break;
156 
157     case SB_THUMBPOSITION:
158         // Scrollbar thumb was released.
159         deltaPos = Get32BitScrollPos(SB_HORZ, pScrollBar) - m_scrollPos.cx;
160         break;
161 
162     default:
163         // We don't process other scrollbar messages.
164         return;
165     }
166 
167     // Compute the new scroll position.
168     int newScrollPos = m_scrollPos.cx + deltaPos;
169 
170     // If the new scroll position is negative, we adjust
171     // deltaPos in order to scroll the window back to origin.
172     if ( newScrollPos < 0 )
173         deltaPos = -m_scrollPos.cx;
174 
175     // If the new scroll position is greater than the max scroll position,
176     // we adjust deltaPos in order to scroll the window precisely to the
177     // maximum position.
178     int maxScrollPos = m_displaySize.cx - m_pageSize.cx;
179     if ( newScrollPos > maxScrollPos )
180         deltaPos = maxScrollPos - m_scrollPos.cx;
181 
182     // Scroll the window if needed.
183     if ( deltaPos != 0 )
184     {
185         m_scrollPos.cx += deltaPos;
186         m_attachWnd->SetScrollPos(SB_HORZ, m_scrollPos.cx, TRUE);
187         m_attachWnd->ScrollWindow(-deltaPos, 0);
188     }
189 }
190 
OnVScroll(UINT nSBCode,UINT nPos,CScrollBar * pScrollBar)191 void CScrollHelper::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
192 {
193     if ( m_attachWnd == NULL )
194         return;
195 
196     const int lineOffset = 60;
197 
198     // Compute the desired change or delta in scroll position.
199     int deltaPos = 0;
200     switch( nSBCode )
201     {
202     case SB_LINEUP:
203         // Up arrow button on scrollbar was pressed.
204         deltaPos = -lineOffset;
205         break;
206 
207     case SB_LINEDOWN:
208         // Down arrow button on scrollbar was pressed.
209         deltaPos = lineOffset;
210         break;
211 
212     case SB_PAGEUP:
213         // User clicked inbetween up arrow and thumb.
214         deltaPos = -m_pageSize.cy;
215         break;
216 
217     case SB_PAGEDOWN:
218         // User clicked inbetween thumb and down arrow.
219         deltaPos = m_pageSize.cy;
220         break;
221 
222     case SB_THUMBTRACK:
223         // Scrollbar thumb is being dragged.
224         deltaPos = Get32BitScrollPos(SB_VERT, pScrollBar) - m_scrollPos.cy;
225         break;
226 
227     case SB_THUMBPOSITION:
228         // Scrollbar thumb was released.
229         deltaPos = Get32BitScrollPos(SB_VERT, pScrollBar) - m_scrollPos.cy;
230         break;
231 
232     default:
233         // We don't process other scrollbar messages.
234         return;
235     }
236 
237     // Compute the new scroll position.
238     int newScrollPos = m_scrollPos.cy + deltaPos;
239 
240     // If the new scroll position is negative, we adjust
241     // deltaPos in order to scroll the window back to origin.
242     if ( newScrollPos < 0 )
243         deltaPos = -m_scrollPos.cy;
244 
245     // If the new scroll position is greater than the max scroll position,
246     // we adjust deltaPos in order to scroll the window precisely to the
247     // maximum position.
248     int maxScrollPos = m_displaySize.cy - m_pageSize.cy;
249     if ( newScrollPos > maxScrollPos )
250         deltaPos = maxScrollPos - m_scrollPos.cy;
251 
252     // Scroll the window if needed.
253     if ( deltaPos != 0 )
254     {
255         m_scrollPos.cy += deltaPos;
256         m_attachWnd->SetScrollPos(SB_VERT, m_scrollPos.cy, TRUE);
257         m_attachWnd->ScrollWindow(0, -deltaPos);
258     }
259 }
260 
OnMouseWheel(UINT nFlags,short zDelta,CPoint pt)261 BOOL CScrollHelper::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
262 {
263     if ( m_attachWnd == NULL )
264         return FALSE;
265 
266     // Don't do anything if the vertical scrollbar is not enabled.
267     int scrollMin = 0, scrollMax = 0;
268     m_attachWnd->GetScrollRange(SB_VERT, &scrollMin, &scrollMax);
269     if ( scrollMin == scrollMax )
270         return FALSE;
271 
272     // Compute the number of scrolling increments requested.
273     int numScrollIncrements = abs(zDelta) / WHEEL_DELTA;
274 
275     // Each scrolling increment corresponds to a certain number of
276     // scroll lines (one scroll line is like a SB_LINEUP or SB_LINEDOWN).
277     // We need to query the system parameters for this value.
278     int numScrollLinesPerIncrement = 0;
279     ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &numScrollLinesPerIncrement, 0);
280 
281     // Check if a page scroll was requested.
282     if ( numScrollLinesPerIncrement == WHEEL_PAGESCROLL )
283     {
284         // Call the vscroll message handler to do the work.
285         OnVScroll(zDelta > 0 ? SB_PAGEUP : SB_PAGEDOWN, 0, NULL);
286         return TRUE;
287     }
288 
289     // Compute total number of lines to scroll.
290     int numScrollLines = numScrollIncrements * numScrollLinesPerIncrement;
291 
292     // Adjust numScrollLines to slow down the scrolling a bit more.
293     numScrollLines = max(numScrollLines/3, 1);
294 
295     // Do the scrolling.
296     for(int i = 0; i < numScrollLines; ++i)
297     {
298         // Call the vscroll message handler to do the work.
299         OnVScroll(zDelta > 0 ? SB_LINEUP : SB_LINEDOWN, 0, NULL);
300     }
301 
302     return TRUE;
303 }
304 
OnSize(UINT nType,int cx,int cy)305 void CScrollHelper::OnSize(UINT nType, int cx, int cy)
306 {
307     UpdateScrollInfo();
308 }
309 
Scroll(int bar,int delta)310 BOOL CScrollHelper::Scroll(int bar, int delta)
311 {
312     int pos;
313     int new_pos;
314     int max_pos;
315 
316     if (bar == SB_VERT)
317     {
318         pos = m_scrollPos.cy;
319         max_pos = m_displaySize.cy - m_pageSize.cy;
320 
321     }
322     else if (bar == SB_HORZ)
323     {
324         pos = m_scrollPos.cx;
325         max_pos = m_displaySize.cx - m_pageSize.cx;
326     }
327     else
328     {
329         return FALSE;
330     }
331 
332     new_pos = pos + delta;
333 
334     if (delta > 0)
335     {
336         if (new_pos > max_pos)
337         {
338             new_pos = max_pos;
339         }
340     }
341     else
342     {
343         if (new_pos < 0)
344         {
345             new_pos = 0;
346         }
347     }
348 
349     if (new_pos != pos)
350     {
351         SetScrollPos(bar, new_pos, TRUE);
352         return TRUE;
353     }
354 
355     return FALSE;
356 }
357 
Get32BitScrollPos(int bar,CScrollBar * pScrollBar)358 int CScrollHelper::Get32BitScrollPos(int bar, CScrollBar* pScrollBar)
359 {
360     // Code below is from MSDN Article ID 152252, "How To Get
361     // 32-bit Scroll Position During Scroll Messages".
362 
363     // First determine if the user scrolled a scroll bar control
364     // on the window or scrolled the window itself.
365     ASSERT( m_attachWnd != NULL );
366     HWND hWndScroll;
367     if ( pScrollBar == NULL )
368         hWndScroll = m_attachWnd->m_hWnd;
369     else
370         hWndScroll = pScrollBar->m_hWnd;
371 
372     SCROLLINFO si;
373     si.cbSize = sizeof(SCROLLINFO);
374     si.fMask = SIF_TRACKPOS;
375     ::GetScrollInfo(hWndScroll, bar, &si);
376 
377     int scrollPos = si.nTrackPos;
378 
379     return scrollPos;
380 }
381 
UpdateScrollInfo()382 void CScrollHelper::UpdateScrollInfo()
383 {
384     if ( m_attachWnd == NULL )
385         return;
386 
387     // Get the width/height of the attached wnd that includes the area
388     // covered by the scrollbars (if any). The reason we need this is
389     // because when scrollbars are present, both cx/cy and GetClientRect()
390     // when accessed from OnSize() do not include the scrollbar covered
391     // areas. In other words, their values are smaller than what you would
392     // expect.
393     CRect rect;
394     GetClientRectSB(m_attachWnd, rect);
395     CSize windowSize(rect.Width(), rect.Height());
396 
397     // Update horizontal scrollbar.
398     CSize deltaPos(0,0);
399     UpdateScrollBar(SB_HORZ, windowSize.cx, m_displaySize.cx,
400         m_pageSize.cx, m_scrollPos.cx, deltaPos.cx);
401 
402     // Update vertical scrollbar.
403     UpdateScrollBar(SB_VERT, windowSize.cy, m_displaySize.cy,
404         m_pageSize.cy, m_scrollPos.cy, deltaPos.cy);
405 
406     // See if we need to scroll the window back in place.
407     // This is needed to handle the case where the scrollbar is
408     // moved all the way to the right for example, and controls
409     // at the left side disappear from the view. Then the user
410     // resizes the window wider until scrollbars disappear. Without
411     // this code below, the controls off the page will be gone forever.
412     if ( deltaPos.cx != 0 || deltaPos.cy != 0 )
413     {
414         m_attachWnd->ScrollWindow(deltaPos.cx, deltaPos.cy);
415     }
416 }
417 
UpdateScrollBar(int bar,int windowSize,int displaySize,LONG & pageSize,LONG & scrollPos,LONG & deltaPos)418 void CScrollHelper::UpdateScrollBar(int bar, int windowSize, int displaySize,
419                                     LONG& pageSize, LONG& scrollPos, LONG& deltaPos)
420 {
421     int scrollMax = 0;
422     deltaPos = 0;
423     if ( windowSize < displaySize )
424     {
425         scrollMax = displaySize - 1;
426         if ( pageSize > 0 && scrollPos > 0 )
427         {
428             // Adjust the scroll position when the window size is changed.
429             scrollPos = (LONG)(1.0 * scrollPos * windowSize / pageSize);
430         }
431         pageSize = windowSize;
432         scrollPos = min(scrollPos, displaySize - pageSize);
433         deltaPos = m_attachWnd->GetScrollPos(bar) - scrollPos;
434     }
435     else
436     {
437         // Force the scrollbar to go away.
438         pageSize = 0;
439         scrollPos = 0;
440         deltaPos = m_attachWnd->GetScrollPos(bar);
441     }
442 
443     SCROLLINFO si;
444     memset(&si, 0, sizeof(SCROLLINFO));
445     si.cbSize = sizeof(SCROLLINFO);
446     si.fMask  = SIF_ALL;    // SIF_ALL = SIF_PAGE | SIF_RANGE | SIF_POS;
447     si.nMin   = 0;
448     si.nMax   = scrollMax;
449     si.nPage  = pageSize;
450     si.nPos   = scrollPos;
451     m_attachWnd->SetScrollInfo(bar, &si, TRUE);
452 }
453 
454 // Helper function to get client rect with possible
455 // modification by adding scrollbar width/height.
GetClientRectSB(CWnd * pWnd,CRect & rect)456 void CScrollHelper::GetClientRectSB(CWnd* pWnd, CRect& rect)
457 {
458     ASSERT(pWnd != NULL);
459 
460     //CRect winRect;
461     //pWnd->GetWindowRect(&winRect);
462     //pWnd->ScreenToClient(&winRect);
463 
464     pWnd->GetClientRect(&rect);
465 
466     int cxSB = ::GetSystemMetrics(SM_CXVSCROLL);
467     int cySB = ::GetSystemMetrics(SM_CYHSCROLL);
468 
469     int style = pWnd->GetStyle();
470     if (style & WS_HSCROLL)
471     {
472         rect.bottom += cySB;
473     }
474 
475     if (style & WS_VSCROLL)
476     {
477         rect.right += cxSB;
478     }
479 
480     if (rect.Width() < m_displaySize.cx)
481     {
482         rect.bottom -= cySB;
483     }
484 
485     if (rect.Height() < m_displaySize.cy)
486     {
487         rect.right -= cxSB;
488 
489         if (rect.Width() < m_displaySize.cx)
490         {
491             rect.bottom -= cySB;
492         }
493     }
494 }
495 // END
496 
497