Forcing a ListView to scroll

L

Lord Zoltar

Hello,
I'm trying to force a listView to scroll to some location when a
certain button is clicked on. I've found that the SendMessage function
seems to be the choice way of doing it. Here's what I've put in my
button handler:

//somewhere else in my code:
private const int WM_VSCROLL = 277; // Vertical scroll
..
..
..
//inside button click handler:
SendMessage(myListView.Handle,WM_VSCROLL,IntPtr.Zero,
(IntPtr)scrollAmt);

The application compiles and runs fine, and when I click on the scroll
button, the method is invoked (I stepped through in the debugger), but
the listview never scrolls to scrollAmt.
I think I may be using SendMessage incorrectly, but I just can't
figure out how.
Can anyone help with this?
 
L

Lord Zoltar

Minor hint: in C#/.NET, unmanaged code is _never_ "the choice way" of  
doing anything.  It is sometimes the only way, but it's never a choice. 
It's something you do because you have to.

In this particular case, the ListView class has the TopItem property and  
the EnsureVisible() method that can be used to accomplish what you seem to  
be trying to do, but without having to mess with the unmanaged API.

Hope that helps.

Pete

I had a look at TopItem. It gave me trouble because my ListView
displays in LargeIcon mode. Using EnsureVisible() gave me some odd
behaviour:
I have several dynamically generated groups in my listview and a
button for each group. The button should scroll the listview to the
top of the group. Using EnsureVisible() caused ALL the buttons to
scroll to the first item that had EnsureVisible() invoked on it.
I know the height of each item and each group header, so determining
the exact amount to scroll wouldn't be hard. Is there any other way to
do this besides the SendMessage method?
 
L

Lord Zoltar

Yes, there is.  Use TopItem or EnsureVisible().

Ok so after some experimentation, I did get EnsureVisible() to sort of
work (failed earlier due to bugs elsewhere in my code).
It works, but the behaviour of the interface is not consistent: if the
user clicks a button for an item that is not visible, the ListView
will scroll to the item's location, but it could be at the top or
bottom of the ListView's visible area. If the item is already visible,
then nothing will happen. The requested behaviour is that the ListView
ALWAYS scrolls so that the group header is at the top of the
ListView's visible area. Assuming I know the location of the group
header, how would I do this? Would I need SendMessage or is there a
better way?
 
L

Lord Zoltar

[...] The requested behaviour is that the ListView
ALWAYS scrolls so that the group header is at the top of the
ListView's visible area. Assuming I know the location of the group
header, how would I do this? Would I need SendMessage or is there a
better way?

Point of order: the "requested behavior" is not literally possible, unless  
you also restrict the size of any given group to have no more items than  
can fit in the visible portion of the control.

That said, it seems to me that you could set the TopItem property to the  
first item in the group that the item of interest belongs to.

Unfortunately, TopItem does not work with LargeIcon views:
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.topitem.aspx
...which kinda makes sense. If there's a horizontal row of icons, how
would it make sense to say that one of them is at the "top"?
Other than that, at that level of control, I suppose you might be stuck  
using unmanaged code.  IMHO, that's a very good reason to simply change 
the problem description so that "the requested behavior" is different.

After more work on this, it looks like it will probably be unmanaged
code to do this. It really does have to scroll so that the group
header is right at the top of the visible part of the listView. The
effect should be the same as if I did my ListView in HTML, put an
anchor at each group, then provided a link to all the anchors. The
browser should scroll down the page so that anchor is right at the top
of the browser's content area. Maybe the ListView is not the right
container for this? I'd be open to trying something else if someone
knows a container that can do this better.
I've seen a few examples of subclassing ListView and overriding
WndProc so I'll have to give those a try. but I'm still open to any
suggestion that does not use SendMessage()...
 
L

Lord Zoltar

Well, I did finally get it working. As it turns out, WM_VSCROLL is the
wrong message. I should have been using LVM_SCROLL... it seems there's
a whole bunch of messages just for listviews:
http://msdn.microsoft.com/en-us/library/cc656508(VS.85).aspx
for anyone who's interested in the solution, the core of it looks
something like this:

int prevScrollPos = 0;
SCROLLINFO currentInfo = new SCROLLINFO();
currentInfo.cbSize = Marshal.SizeOf(currentInfo);
currentInfo.fMask = (int)ScrollInfoMask.SIF_ALL;

GetScrollInfo(this.Handle, (int)ScrollBarDirection.SB_VERT, ref
currentInfo)

prevScrollPos = currentInfo.nPos;

//The LVM_SCROLL message will take a delta-x and delta-y which tell
the list view how
//much to scroll, relative to the current scroll positions. We are
getting the scroll
//position as an absolute position, so some adjustments are necessary:
scrollPos -= prevScrollPos;
//Send the LVM_SCROLL message to scroll the list view.
SendMessage(new HandleRef(null, this.Handle),
(uint)ListViewMessages.LVM_SCROLL, (IntPtr)0, (IntPtr)scrollPos);


....The key here is that scrollPos is the location of the group header.
I found that someListViewGroup.Items[0].Position.Y - 30 seemed to work
fine (but I would still like to find a better way than hardcoding
"30").
Hope this helps anyone else who has to scroll to groups in ListViews.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top