WinForms ListView, Remembering Scrolled Location on Reload
Asked Answered
W

9

14

I've got a list view that I'm populating with 8 columns of user data. The user has the option to enable auto refreshing, which causes the ListView to be cleared and repopulated with the latest data from the database.

The problem is that when the items are cleared and repopulated, the visible area jumps back to the top of the list. So if I'm looking at item 1000 of 2000, it's very inconvenient to get back to that item.

Basically, what I'm asking is, how do I get the current scroll distances (x and y) and then restore them?

Williwaw answered 9/3, 2009 at 13:51 Comment(0)
T
6

I had the same problem with a while ago and I ended up implementing an algorithm to compare the model with the list, so I only added/removed elements that had changed. This way if there were no massive changes the list didn't jump to the beginning. And the main thing I wanted to achieve was the efficiency (so that the list doesn't blink).

Teem answered 9/3, 2009 at 13:58 Comment(2)
This is what I did. I added a tag to the first sub item and then used that to do a comparison and only update when required.Williwaw
I use this technique too. however, the drawback is that removing items is really slow, so I'm trying this https://mcmap.net/q/827753/-c-listview-items-i-remove-is-very-slow, but then I fall into the problem of this question.Haywood
R
15

I just wanted to provide some information for those who desperately try to use the buggy ListView.TopItem property:

  1. You MUST set the TopItem property AFTER calling ListView.EndUpdate
  2. The items of the ListView control MUST have their Text property set to something other than String.Empty, or the property won't work.
  3. Setting the ListView.TopItem throws null reference exceptions intermittently. Always keep this line of code inside a Try...Catch block.

Of course, this will cause the ListView's scrollbar to jump to 0 and back to the location of the top item, which is annoying. Please update this question if you find a workaround to this problem.

Repeater answered 24/1, 2010 at 12:45 Comment(3)
I've found that I get an off-by-one error when assigning to TopItem (that is, it scrolls to the item above the one I ask for) but if I call it twice then it works. Buggy, you say? :-)Enigmatic
It certainly work only when I put it after EndUpdate but it's not throwing any errors without try-catch? Did it fixed by now or should I put try catch anyway?Unction
looks like it doesn't work when you have groups in details viewCivics
S
9

I used the following successfully:

int topItemIndex = 0;
try
{
     topItemIndex = listView1.TopItem.Index;
}
catch (Exception ex)
{ }
listView1.BeginUpdate();
listView1.Items.Clear();
//CODE TO FILL LISTVIEW GOES HERE
listView1.EndUpdate();
try 
{ 
    listView1.TopItem = listView1.Items[topItemIndex];
}
catch (Exception ex)
{ }
Sherilyn answered 11/10, 2012 at 1:10 Comment(1)
It won't work with LargeIcon, throwing: "Cannot get the top item in LargeIcon, SmallIcon, or Tile view." How nice..Retha
T
6

I had the same problem with a while ago and I ended up implementing an algorithm to compare the model with the list, so I only added/removed elements that had changed. This way if there were no massive changes the list didn't jump to the beginning. And the main thing I wanted to achieve was the efficiency (so that the list doesn't blink).

Teem answered 9/3, 2009 at 13:58 Comment(2)
This is what I did. I added a tag to the first sub item and then used that to do a comparison and only update when required.Williwaw
I use this technique too. however, the drawback is that removing items is really slow, so I'm trying this https://mcmap.net/q/827753/-c-listview-items-i-remove-is-very-slow, but then I fall into the problem of this question.Haywood
F
3

The TopItemIndex property on ListView is what you are looking for, however it has some confirmed bugs that should have been addressed in VS2010 release.. not sure (haven't checked).

Anyway, my workaround for making this work is to do this:

listViewOutput.TopItemIndex = outputList.Count - 1;
listViewOutput.TopItemIndex = myNewTopItemIndex;

For some reason setting it directly does not update it, but setting it to the last item and then the one I want works reliably for me.

Full answered 6/12, 2011 at 19:32 Comment(1)
FWIW, I don't see TopItemIndex in the WinForms property list: msdn.microsoft.com/en-us/library/…Psychology
H
2

Look at the ListView.TopItem property. It has an index, which should contain its position in the list. Find that index in the new list, and set TopItem to that item, and it should do the scrolling automatically.

Hydrograph answered 9/3, 2009 at 14:5 Comment(1)
The ListView.TopItem doesn't seem to work. I thought it might have something to do with sorting so I disabled it, but when I set the TopItem to an item and check the property immediately after that it doesn't get changed (changes to another item, not the one I specified). Do you have any idea?Repeater
R
1

Unfortunately you will need to use some interop to scroll to the exact position in the ListView. Use GetScrollInfo winapi function to get the existing scroll position and SendMessage to scroll to the position.

There in an article on CodeProject named Scrolling to a group with a ListView that might guide you to the solution.

Ringster answered 9/3, 2009 at 15:16 Comment(0)
E
0

I was having sort-of the same problem. I have a listView that I populate every 1/2 sec and when I set the TopItem to an ListItem whose index > visible items, then the list jumped between the topItem and back 2 spots.

So, to correct the problem, I set the TopIterm AFTER the call to EndUpdate.

lvB.EndUpdate();
lvI.EndUpdate();
lvR.EndUpdate();

if (lstEntryInts.Items.Count > 0)
    lstEntryInts.TopItem = lstEntryInts.Items[iTopVisIdx];
if (lstEntryBools.Items.Count > 0)
    lstEntryBools.TopItem = lstEntryBools.Items[iTopVisIdx];
if (lstEntryReals.Items.Count > 0)
    lstEntryReals.TopItem = lstEntryReals.Items[iTopVisIdx];​
Exemplar answered 25/3, 2010 at 19:22 Comment(0)
B
0

My solution to maintaining scroll position:

Form level variable:

private static int scrollSpot = 0;

Inside listview refresh (ie Timer,button) to store the current spot:

scrollSpot = this.listView1.TopItem.Index;
refreshTheForm();

Inside refreshTheForm method to show the stored spot (put at very end of method):

if (scrollSpot <= 1)
{
     listView1.Items[scrollSpot].Selected = true;
}
else
{
     listView1.Items[scrollSpot - 2].Selected = true;
}
listView1.TopItem = listView1.SelectedItems[0]; 
Behest answered 10/11, 2011 at 16:55 Comment(0)
M
0

In my tests, you did not even need the TopItem, although I used a int to save the selected item. Also TopItem throws an exception if you are using View.Tile or View.LargeIcon.

This code does not move the scroll bars:

listView1.BeginUpdate();
listView1.Items.Clear();

// loop through your add routine
listView1.Items.Add(lvi);

listView1.EndUpdate();
Moskva answered 11/8, 2013 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.