I've got a ListView bound to an ObservableCollection that implements ISupportIncrementalLoading. Everything is working nicely however when new items are added to the collection via LoadMoreItemsAsync() the ListView jumps to the top of the list.
The ListView should of course carry on scrolling from the current position. Any ideas what could be wrong?
UPDATE:
There is no selected item and individual items are added one at a time i.e.: 10 items added results in 10 collection changed events. It shouldn't be the last item in the list after its been updated, but the first updated item.
list.ScrollIntoView(i);
where i is the i-th selected item.
Related
I implemented some logic for the user to be able to copy selected values from a ListView to the clipboard. While playing playing around with it, I noticed that ListView.SelectedItems returns the items in a different order depending on how you shilft-select items.
For simplicity, let's assume my List has 5 items, each containing a string with a single number in ascending order, e.g. first item = "1", second item = "2", etc.
Scenario 1: Click on item 2, hold shift, click on item 4 -> SelectedItems has the expected order of elements "2,3,4".
Scenario 2: Click on item 4, hold shift, click on item 2 -> SelectedItems has an unexpected order of elements "4,2,3".
Am I missing something really simple here? If I select a range using shift "from the bottom to the top", the first item clicked is moved to the first position of SelectedItems, then following all the others in correct order.
Is the ListView noticing that the item previously selected is also within the range? So to say remembering it, leaving it at the first position in SelectedItems and just "adding" all the remaining items? Wouldn't bother me if you'd only shift-select from the top to bottom, but the other way around it messes up the order... :/
Any help/idea is appreciated!
I'm using a Windows Forms ListView control to display a list of items, up to 1000 items in total, though only about 20 are visible within the listview at one time (listview is using Details view).
I'm frequently adding new items to the bottom of the listview, and scroll to the newly added item automatically (using item.EnsureVisible()) so the user can see the most recent data. When the list size is above 1000 items, I remove the oldest list item (i.e. index 0, the one at the top of the listview) from the list to keep it at 1000 items.
Now to my problem:
Whenever the selection in the listview changes, additional details associated with the item are shown elsewhere in the form. When this selection change occurs, I stop the auto-scroll-to-new-items so the user's selected item stays where it is (i.e. the list doesn't scroll to newest items when an item in it is selected), and only re-enable the auto-scroll-to-newest when the user dismisses the additional details part of the form.
This all works fine, except when I remove the oldest item from the listview (to ensure the list doesn't grow beyond 1000 items): When I remove that oldest item, the listview scrolls everything up by 1 automatically (i.e. nothing I've done programatically does this scrolling). I realise that if the selected item is one of the earliest 20 events (which makes the earliest 20 events the visible ones), it will have no choice but to scroll the visible items when it removes the earliest, but if the selection is, say, midway through the list, it should have no need to scroll the listview items.
Is there any way I can prevent the listview automatically scrolling up by one when I remove the oldest item? Or will I have to work around it by making sure the visible items remain in the position they were before I removed the oldest item, after removing it (which seems a real hassle)?
Ok, this is my not-ideal (but at least mostly working) solution, in C# (converted from VB.NET so StackOverflow's syntax highlighter can display it properly, so apologies for any typos!).
Any better solutions, please do suggest them!
// loop through every item in the list from the top, until we find one that is
// within the listview's visible area
private int GetListViewTopVisibleItem()
{
foreach (ListViewItem item In ListView1.Items)
{
if (ListView1.ClientRectangle.IntersectsWith(item.GetBounds(ItemBoundsPortion.Entire)))
{
// +2 as the above intersection doesn't take into account the height
// of the listview's header (in Detail mode, which my listview is)
return (item.Index + 2);
}
}
// failsafe: return a valid index (which won't be used)
return 0;
}
private void RemoveOldestItem()
{
// pause redrawing of the listview while we fiddle...
ListView1.SuspendLayout();
// if we are not auto-scrolling to the most recent item, and there are
// still items in the list:
int TopItemIndex = 0;
if (!AllowAutoScroll && (ListView1.SelectedItems.Count > 0))
TopItemIndex = GetListViewTopVisibleItem();
// remove the first item in the list
ListView1.Items.RemoveAt(0);
// if we are not auto-scrolling, and the previous top visible item was not
// the one we just removed, make that previously top-visible item visible
// again. (We decrement TopItemIndex as we just removed the first item from
// the list, which means all indexes have shifted down by 1)
if (!AllowAutoScroll && (--TopItemIndex > 0))
ListView1.Items(TopItemIndex).EnsureVisible();
// allow redraw of the listview now
ListView1.ResumeLayout()
}
(This assumes, of course, that the selected item is currently visible otherwise it doesn't make a whole lot of sense to do; it always is visible in my scenario though, unless the selected event is the one being removed from the top of the list (in which case nothing is selected anymore so the issue goes away))
In WPF, I select multiple items in the listview. In the code-behind, I inserted an element at index 2 using ObservableCollection.SetItem(). The multiple items I selected a while ago will be deselected. How can I insert an element in the listview while not losing the multiple items I selected?
The code below triggers the deselection of the selected items.
this.SetItem(index2, new student("name",age,"school"));
Any help is appreciated. Thanks
For one, SetItem() does not insert an item, it replaces it. To insert an item use Insert().
As for multi-selection, you're best off implementing a bind-able multi-select ListView. One issue with ListView is that the SelectedItems is not a DependencyProperty so it cannot be bound to, and isn't the most reliable thing to deal with in ViewModels or code.
A good example of how to implement a better ListView can be found Here. There is a 3-part series on binding ListViews there which can provide some useful ideas as well.
Create a list of bools for each item in your checklist.
Before inserting new item, check each existing item and see if it is selected and store result in bool list.
Once item is inserted, simply parse back over the bool list and re-set the previously selected items.
With respect to a Virtual ListView control in a Winforms App, what are ALL the functions of RetrieveVirtualItem event?
Scenario: There is a Dictionary object which acts as my ListView cache. And its items are displayed on the ListView. On click of a column, the cache dictionary is sorted in memory and the ListView is Refresh()ed. The effect on UI, the ListView is sorted well. :)
But I want to understand what the role of RetrieveVirtualItem Event is, which gets triggered on Refresh().
In the RetrieveVirtualItem event handler [ Someone else's code, :( ], following are done:
Based on RetrieveVirtualItemEventArgs.ItemIndex, get the message from Cache
Set RetrieveVirtualItemEventArgs.Item = retreived item above
It appears that what's done in the event handler is important because, if I take it out, ListView cries. What is the significance of this event?
EDIT
Or let me re-phrase the question... My concern is, Why, after Sorting (and RetrieveVirtualItem event handler), the selected item remains at the location where it was before sorting. I.e, If I select item #5 and sort, and if sorting makes this item as the last item, I would prefer the LAST item to be selected after Sort. But in my case 5th item is selected after sort. So what is the default behavior of RetrieveVirtualItem WRT selected item?
A virtual ListView should only call RetreiveVirtualItem for the rows currently visible on the screen.
As you navigate in the ListView, for example, you press the page down key, the ListView will calculate what should now be the index of the top row and will then call RetrieveVirtualItem so that your code can provide the item to use at each row index.
Unless you cache or otherwise store the items you are providing via RetrieveVirtualItem, they will no longer exist once they are scrolled out of the listview.
This is what the Virtual in VirtualListView means - there aren't any real rows, the rows are virtual. That is how it could display a list containing hundreds of thousands of rows - because it will ever only actually contain how ever many rows are visible on screen.
In effect, the ListView is like a window that is moving up and down your internal list of data - the RetreiveVirtualItem method is what it calls to move items into that window as it moves along. It says, hey I just moved to row 15 - give me the item for that row. It will proceed to call RetreiveVirtualItem for each row index which would be visible. If the ListView was 5 rows in height on the screen, you would receive 5 calls to RetrieveVirtualItem - even if the actual data backing the listview had 3000 items. Each time the top row of the ListView changed (because of navigation), you would receive 5 calls to RetrieveVirtualItem (this is not always the case, but it is the right idea - for example, if you scroll down one row, it will simply ask you for the new last row - it will also simply discard the data that was used for the old top row that scrolled out of view).
I guess it might be even easier to explain if we assume the ListView was only one row high on the display (meaning only a single row is ever actually visible on the screen) - as you move the ListView up and down your list of data (i.e. the user navigates the ListView), it would call RetrieveVirtualItem exactly one time every time it moves to a new row.
Hope that helps...
Good Luck
Virtual listviews only deal with indices. So, if the 5th item is selected before the sort, the 5th item will still be selected after the sort. The control itself has no way of knowing that the data that used to be on the 5th row is now on the first row.
You will have to program that for yourself in your sorting method:
remember which items were selected (remember: you can't use SelectedItems property when in virtual mode)
do the sort
find the indices of the previously selected item now live
select those indices
You can see all this in action in ObjectListView -- a wrapper around a standard .NET ListView.
The RetrieveVirtualItem event is only used when the ListView is put into virtual mode. Rather than retaining ListViewItems in the Items collection (as when not in virtual mode), it creates the ListViewItems dynamically, when needed.
If you don't handle the RetrieveVirtualItem event then no ListViewItems will be added to your ListView. I've attached some sample code for a typical method written to handle the event:
//Dynamically returns a ListViewItem with the required properties; in this case, the square of the index.
void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
//Check if cache has been instantiated yet, and if so, whether the item corresponding to the index requested has been added to the cache already
if (myCache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + myCache.Length)
{
//Return cached item for index
e.Item = myCache[e.ItemIndex - firstItem];
}
else
{
//When item not in cache (or cache not available) return a new ListViewItem
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem(x.ToString());
}
}
This example is taken from MSDN (http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode(v=vs.90).aspx) where further details can be found.
How can I retrieve the value of a clicked item in a multi-select listbox?
Currently, if I click one item and use lstBox.SelectedValue, it will return the proper value; however, if I then click on another item, I am still shown the first item's value.
Basically, I want the value of the item most recently clicked on, regardless of whether or not it is the SelectedValue.
If it is a multiple selection listbox, you can get a collection of all the selected items by using SelectedItems instead of SelectedItem.
If you need to know the sequence in which the items were selected, or which was selected most recently, I think you would need to record if yourself by SelectedIndexChanged event.
The SelectedIndexChanged handler will get called when you select/unselect an item in the listbox.
However, it doesn't indicate which one was selected/unselected.
listbox1.SelectedItems
will contain the currently selected items and you could internally keep track of which index was most recently added.