Click to See Complete Forum and Search --> : How to find a window's scroll bars?


Mercury048
March 15th, 2008, 11:35 AM
Hi, all.

I'm writing a small program which will scroll the currently active window in response to certain keypresses. (The arrow keys do not do the job I want as they move the selection/text cursor instead of simply scrolling.)

I already figured out (http://www.codeguru.com/forum/showthread.php?t=446352) that I can get a window to scroll by sending/posting WM_HSCROLL or WM_VSCROLL messages.

However, I found that there are two main ways that scrollbars are implemented:

1. As part of the window style. I can determine this by testing the window's dwStyle against WS_VSCROLL or WS_HSCROLL. In this case I send the message directly to the window I want to scroll and pass 0 as the last parameter (which is supposed to be the HWND of the scrollbar).//ScrollMessage is WM_VSCROLL or WM_HSCROLL
//ScrollDirection is one of is one of SB_LINEUP, SB_LINEDOWN, SB_LINELEFT, SB_LINERIGHT
PostMessage(WindowToScroll, ScrollMessage, ScrollDirection, 0);2. As a separate window. This is what's giving me a headache. The scrollbar has its own window with its own window handle. In this case I must send the message to the scrollbar's parent window and pass the scrollbar's window handle in the last parameter of the message.//ScrollMessage is WM_VSCROLL or WM_HSCROLL
//ScrollDirection is one of is one of SB_LINEUP, SB_LINEDOWN, SB_LINELEFT, SB_LINERIGHT
//OriginatingScrollBar is the HWND of the scrollbar
PostMessage(GetParent(OriginatingScrollBar), ScrollMessage, ScrollDirection, (LPARAM)OriginatingScrollBar);
The issue here is I have to find the scrollbar first. But the scrollbar is not logically related to the window I'm trying to scroll in any predicatble way; sometimes it's a child window, sometimes a sibling window, sometimes a second cousin thrice removed... you get the picture.

So I've resorted to picking up the borders of the window I want to scroll, and looking for the scrollbar by using WindowFromPoint() (http://msdn2.microsoft.com/en-us/library/ms633558.aspx) at a few points near the border.

This is a very inelegant solution, IMO. It also fails if the scrolllbar is so far away from the border that I can't find it. Is there any better way to find the scrollbar?

olivthill
March 16th, 2008, 08:27 AM
You are right. There are scrollbars defined in window's dwStyle and there are independant scrollbars which can be children or siblings of other windows.

You can find independant scrollbars with EnumChildWindows or with EnumChildWindows. I guess you know that already. Maybe I don't understand very well the question.

Mercury048
March 16th, 2008, 10:54 AM
I tired EnumChildWindows before. But:

Since the scrollbars are not necessarily children (or descendants, for that matter) of the window I'm trying to scroll, I have to use it on the top-level window. This means I'm enumerating every child window of the main program.

Also, there may be multiple scrollbars in the application. I want to find the one that's related to the window I'm trying to scroll. But really, the only way I know how to determine this is to check its position against the window's position. (Otherwise I might get some other scrollbar.)

So if I'm going to be checking location anyway, why not shorten the search by searching in certain locaitons (using WindowFromPoint())?

I found an interesting document (http://msdn2.microsoft.com/en-us/library/ms997498.aspx) on Microsoft's site which says Currently, there is no operating system support for the tilt wheel. Microsoft IntelliType Pro or IntelliPoint software—for keyboards and mouse devices, respectively—must be installed to enable this functionality. With the software installed, horizontal scrolling works for many applications.

Horizontal scrolling will generally work if the following are true:

-The window that has focus is a standard Windows control with an internal horizontal scroll bar.
-A horizontal scroll bar is associated with a window and it is placed near the bottom of the window. Ideally, it will be placed below and adjacent to the window, and between the left and right sides of the window.
So it seems even Microsoft's software is depending on the scrollbar being in a certain location.

Mercury048
March 20th, 2008, 08:19 PM
Well, olivthill, I took your advice and settled on using EnumChildWindows, in part because it allows me to find a scrollbar offscreen (where WindowFromPoint doesn't seem to work) and also because there are cases where the window I want to scroll is a child of the window with keyboard focus. (Notably Windows Picture and Fax Viewer, in which CPreviewWnd has keyboard focus but CZoomWnd has the scrollbars.)

I am concerned about efficiency since this code will be called every time the user presses the correct key, so theoretically at the keyboard's repeat rate. Maybe I'm worrying too much and with multi-Ghz processors this is a complete non-issue?

After cleaning up the code the best I could, this is what I have:
void ScorpiusM1ScrollWindow(enum ScorpiusM1Direction d)
{
//next line gets the active thread info, extracts handle to the window with focus, and gets the window info
if (!GetGUIThreadInfo((DWORD)NULL, &gti) || (WindowToScroll = gti.hwndFocus)==NULL || !GetWindowInfo(WindowToScroll, &WindowToScrollInfo))
{
return; //if any of the above steps fail, abort
}

OriginatingScrollBar = NULL;
switch (d)
{
case Up:
ScrollMessage = WM_VSCROLL;
ScrollDirection = SB_LINEUP;
if (!(WindowToScrollInfo.dwStyle & WS_VSCROLL))
{
EnumChildWindows(GetForegroundWindow(), ScorpiusM1FindScrollBar, SBS_VERT);
}
break;
case Down:
ScrollMessage = WM_VSCROLL;
ScrollDirection = SB_LINEDOWN;
if (!(WindowToScrollInfo.dwStyle & WS_VSCROLL))
{
EnumChildWindows(GetForegroundWindow(), ScorpiusM1FindScrollBar, SBS_VERT);
}
break;
case Left:
ScrollMessage = WM_HSCROLL;
ScrollDirection = SB_LINELEFT;
if (!(WindowToScrollInfo.dwStyle & WS_HSCROLL))
{
EnumChildWindows(GetForegroundWindow(), ScorpiusM1FindScrollBar, SBS_HORZ);
}
break;
case Right:
ScrollMessage = WM_HSCROLL;
ScrollDirection = SB_LINERIGHT;
if (!(WindowToScrollInfo.dwStyle & WS_HSCROLL))
{
EnumChildWindows(GetForegroundWindow(), ScorpiusM1FindScrollBar, SBS_HORZ);
}
break;
default:
return;
}

if (OriginatingScrollBar != NULL)
{
WindowToScroll = GetParent(OriginatingScrollBar); //if we have identified a scrollbar in a separate window, send the message to its parent window
}

PostMessage(WindowToScroll, ScrollMessage, ScrollDirection, (LPARAM)OriginatingScrollBar); //send our message
}


static BOOL CALLBACK ScorpiusM1FindScrollBar(HWND ScrollBarCandidateWindow, LPARAM ScrollBarStyle)
{
if (!GetWindowInfo(ScrollBarCandidateWindow, &ScrollBarCandidateWindowInfo)) { return TRUE; }

switch (ScrollBarStyle)
{
case SBS_HORZ:
if ((ScrollBarCandidateWindowInfo.dwStyle & WS_HSCROLL)
&& IsChild(WindowToScroll, ScrollBarCandidateWindow))
{
WindowToScroll = ScrollBarCandidateWindow;
return FALSE;
}
if (ScrollBarCandidateWindowInfo.atomWindowType == ScrollBarAtom
&& !(ScrollBarCandidateWindowInfo.dwStyle & SBS_VERT)
&& ScrollBarCandidateWindowInfo.rcWindow.top < WindowToScrollInfo.rcWindow.bottom+8
&& ScrollBarCandidateWindowInfo.rcWindow.bottom > WindowToScrollInfo.rcWindow.top+8
&& ScrollBarCandidateWindowInfo.rcWindow.left > WindowToScrollInfo.rcWindow.left-24
&& ScrollBarCandidateWindowInfo.rcWindow.right < WindowToScrollInfo.rcWindow.right+24)
{
OriginatingScrollBar = ScrollBarCandidateWindow;
return FALSE;
}
break;
case SBS_VERT:
if ((ScrollBarCandidateWindowInfo.dwStyle & WS_VSCROLL)
&& IsChild(WindowToScroll, ScrollBarCandidateWindow))
{
WindowToScroll = ScrollBarCandidateWindow;
return FALSE;
}
if (ScrollBarCandidateWindowInfo.atomWindowType == ScrollBarAtom
&& (ScrollBarCandidateWindowInfo.dwStyle & SBS_VERT)
&& ScrollBarCandidateWindowInfo.rcWindow.left < WindowToScrollInfo.rcWindow.right+8
&& ScrollBarCandidateWindowInfo.rcWindow.right > WindowToScrollInfo.rcWindow.left+8
&& ScrollBarCandidateWindowInfo.rcWindow.top > WindowToScrollInfo.rcWindow.top-24
&& ScrollBarCandidateWindowInfo.rcWindow.bottom < WindowToScrollInfo.rcWindow.bottom+24)
{
OriginatingScrollBar = ScrollBarCandidateWindow;
return FALSE;
}
break;
default:
return FALSE;
}

return TRUE;
}