Why virtualized ListBox requests old items - c#

I have my own DefferedList:IList class. I overrode
Count, IndexOf, this[]
members and put
Debug.WriteLine("Get " + index);
into this[int index].
Then I binded instance of this class to ListBox.ItemSource.
Everything is fine but UI virtualization works a bit strange for me. I run application. ListBox is populated by first 17 items (from 0 to 17). Then I jump to 200th item
TheList.ScrollIntoView(Item200);
ListBox requests 200th item, then again items from 0 to 16 and finally 184-209.
So why does it request again 0-16 items while they are not visible anymore? It affects performance of my data virtualization very badly.
How to avoid it?

I have some really bad experience with numerous hours wasted in productivity over using custom virtualization of lists used in a standard ListBox. I would advise against using custom lists and use an ObservableCollection instead. I think ListBox was probably mostly tested with an ObservableCollection and any tricks you try to make to avoid populating the entire list are likely to backfire since the ListBox is not really as flexible as you would expect based on the fact that ItemsSource is just an IEnumerable. Actually based on that expectation - you could assume that it needs to enumerate all items between 0 and 200 if you want to display item 200.
If you have a few hundred items in a list - you might want to simplify the data model for displaying them, so you populate an ObservableCollection with all the items from the beginning and only load the item data when the contents of the items are requested and not already available. If that is too much - you could add a small delay before loading the data and check if the collection item view is visible on screen after that delay before you start loading.
Another thing that is supposedly helpful is if your ItemTemplate is of predefined dimensions.

Related

Binding values from local array to specific property (Visibility) of all listbox items

I need to change Visibility of all items in listbox in code based on changing some filters. I tried to loop the items using
myListbox.ContainerFromIndex(i)
but only first a few didn't return null. The answer why I quickly found here:
Why ItemContainerGenerator.ContainerFromIndex() returns null and how to avoid this behavior?
Turning off virtualization doesn't look like respectable solution to me, also I don't feel well about scrolling every item to view before calling ContainerFromIndex, even if it could probably work.
I'm not very clever from the rest of topics dealing with bindings and dependency properties etc, maybe also because majority of them are written in xaml and not in code. But I'm pretty sure the answer should be there. I'm very new to c# and I didn't find any clear-cut solution to such a basic problem.
I will prepare something like this array of Visibilities
Visibility[] visibilities=new Visibility[100]
...
and I want myListBox with 100 items to use these values. Either automatically with some c# magic or in a cycle as I used to do in Win32.
Based on the comment of Depechie, I made a research targeted on collections and created a functional code. Here is what I did:
Instead of adding items directly to listbox by
lbGames.Items.Add(...)
I created two collections:
List<object> gameList=new List<object>();
ObservableCollection<object> filteredGameList = new ObservableCollection<object>();
Adding items this way:
gameList.Add(...); or filteredGameList.Add(...)
While gameList is holding all items (stack panels), filteredGameList is holding only those I want visible. I had some problems with using only one big collection using its Visibility property, maybe my mistake, maybe not.
Withouth ObservableCollection I wasn't able to redraw listbox, that was problem with another List<> holding filtered data. When I filter data, I first clear filteredGameList and then I add all items I want visible, never changing Visibility property. ObservableCollection always tells listbox when items change.
On the application init, I attach listbox to this filteredGamesList.
lbGames.ItemsSource = filteredGameList;
Now everything works fine.

massive string array (40000) elements in listbox

I know this is a stupid question, I need to let you guys know that I am fully aware that it is useless in 99% of situations to make a listbox with this many elements in c#:
That being said I need this to be done...is there any way to populate a listbox with 40000 elements without it completely destroying performance/freezing up, thanks!
note: I have tried it, this is per the exact requirements of a professor...when adding 40000 elements through a DataSource and DataBind the application freezes up
You tell me.
for(i=0;i<40000;i++)
{
listBox1.Items.Add("click me");
}
Even if is possible (I never tried it), the usability for this form will be 0.
In that cases a more usable implementation is via lookup text-boxes and lists, where the user can enter a text to search record that matches this text and displays them in a any kind of list.
It is of course possible to do it, but not very practicable.
When using a desktop technology like WinForms or WPF, for a large number of items like this you are better off using something like an auto complete textbox, and have it set to filter/search after the user has typed two or three characters. In this case you can also use a control that offers scrolling virtualisation - this means that there is only a limited number of UI elements created in the scrolling portion of the dropdown, and those elements get reused when a scroll occurs. If you don't use virtualisation then a new element gets created for every list item that gets scrolled in to view. (Note that Silverlight controls have this functionality - just in case it's an option).
For ASP.NET though I would suggest that you do not want to do anything that would cause a large transfer of data (large items, or small items but lots of them) as it won't be performant. Instead you should look to do what Google does - retrieve search results in a paged fashion.

ComboBox.Items.AddRange Performance

I have a ComboBox which when I repopulate it, it seems to be quite a time consuming task. After doing some profiling I have found that the majority of the time is spent in the ComboBox.Items.AddRange(Array) method. I have included an example method below which shows how I carryout the repopulating of the ComboBox.
public void Repopulate(IList<MyType> sortedList)
{
MyComboBox.BeginUpdate();
try
{
MyComboBox.Items.Clear();
MyComboBox.Items.AddRange(sortedList.ToArray());
}
finally
{
MyComboBox.EndUpdate();
}
}
sortedList contains around 280 items, and there are up to 53 ComboBox's which need to be repopulated. So it can take quite some time to repopulated all these controls (approx. 700ms on a high spec machine, 8000ms on a low spec machine) which is too slow for my requirements. I tried adding the sortedList to a new IList and that took around 1ms (on my high spec machine).
I need to get the repopulating of the ComboBox so that it takes significantly less time, ideally similar times to the IList but any performance increase would be good. So far I have been unable to find any way to improve the speed of the re-population.
Does anyone have any idea's on how I can reduce the time taken to re-populate the ComboBox?
Your problem could be that you have enabled the combobox's Sorted property. When this is enabled and you call AddRange the combobox sorts all those items, which if your items are already sorted, is unnecessary.
To prove my point, I created two combobox's that were populated using 10,000 sorted ints and AddRange. The only difference was that one combobox had the Sorted property enabled, the other one didn't. Here are the times in milliseconds of the resulting AddRange call
notSortedCombo: 5ms
sortedCombo: 1140ms
Could this be your problem? Could you have 53 comboboxes that have the sorted property enabled?
AddRange already calls BeginUpdate and EndUpdate under the hood, so you aren't gaining anything by calling it yourself.
This shaved a few milliseconds off of it for me:
public void Repopulate(IList<string> sortedList) {
comboBox1.BeginUpdate();
comboBox1.Items.Clear();
foreach (string item in sortedList) {
comboBox1.Items.Add(item);
}
comboBox1.EndUpdate();
}
The bigger problem is probably the design: 53 combo boxes is a lot of combo boxes to throw at a user — the user won't be able to interact with all 53 controls at once. You could get a little hacky and just populate the combo boxes with the visible value (1 item) and then populate the list as the control gets focused or in a background timer.
But consider reducing the number of controls on the screen. White space is considered a good thing.
You can improve performance by changing property FormattingEnabled of your combobox to False
The problem is that Visual Studio keeps it as True if you don't specify items "manually" in the designer, but add them programmatically.
Try, it solved issue for me
Regarding UI virtualization: Virtualization exists both for WPF (Example) and WinForms. But only some controls support it, like ListView, DataGridView, TreeView, etc. Those are the controls designed for bigger amounts of data. If possible you could switch to one of these controls.
Are all controls visible on screen at the same time? Maybe only updating the visible ones will help.
An alternative is to update the combo boxes asynchronously. If you're lucky enough to work with the new async/await stuff in .NET this is easily done. If you want to do it manually, you can just update a single combobox and schedule the update of the next combobox a few milliseconds in the future (use a timer or the TPL). This way the UI is at least still responsive while the update takes place.
Yet another way is to update the list only when the user focusses a certain ComboBox. There's usually no need to update the contents because the user will only see it when he uses the ComboBox.
You could also try to hide the combobox before you update its data. Or you could tell windows not to redraw the UI while modifying the contents (StackOverflow topic here). But I haven't tested whether this really improves the performance.

WPF: listview display recently added items on top instead of bottom

I got a collection bind to a listview. This collection gets items added every 4-5 seconds and new items will automatically be added in bottom of the listview. So if you gonna see newest items then you need to scroll down to the bottom.
My question is:
is it possible to like reverse the listview so the new newest items are on top and oldest items in bottom ?
Thanks
Use Insert instead of Add :
collection.Insert(0, newItem);
Note that it's slower than Add since it has to shift all items by 1 position. It might be an issue if the list is very big.
Even though you already voted the other answer as accepted, as they pointed out, while that is a simple, one-line solution, that does a lot of shifting and such which can affect performance.
IMHO, a better, correct way to do this would be to use sorting. It's easy to get the default view of a collection, then apply sorting to it, or just use a CollectionViewSource and apply sorting there. Of course that means you have to have a field to sort on, but that could be as simple as adding an index property if you don't already have a time-stamp or something similar.
Let me know if you want a code example here and I'll provide one.
Two approaches:
1) Use CollectionChange event to capture any item-change in the list.
Then sort the list in descending order and then render.
// subscribe to CollectionChanged event of the ObservableCollection
_recordingsHidden.CollectionChanged += RecordingsHiddenOnCollectionChanged;
private void RecordingsHiddenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
var view = (CollectionView)CollectionViewSource.GetDefaultView(lvCallHistory.ItemsSource);
view.SortDescriptions.Add(new SortDescription("StartTime", ListSortDirection.Descending));
}
Here 'lvCallHistory' is my list view and sorting is done based on 'StartTime'.
This approach is effective only if the list contains less items. Because sorting is a costly operation and if you update the list frequently, then it might affect the performance of the application. The user may experience 2-3 seconds lag each time the view is rendered.
2) Avoid sorting: Every time you update the list, point the scroll bar to the last added item. (Pros: Very effective and no performance issue. Cons: Items still added to the end of list, even though scroll bar takes you to the new item whenever a new entry comes.)
lvCallHistory.SelectedItem = <your_collection>.Last();
lvCallHistory.ScrollIntoView(lvCallHistory.SelectedItem);
Set sorting order to descending and use add items
this.listView1.Sorting = System.Windows.Forms.SortOrder.Descending;
listView1.Items.Add( listviewitem);
or remove sorting and insert item
listView1.Items.Insert(0, listviewitem);
either of them will work..

Best way to unbind wpf listview

I have WPF listview bound to collection of objects. Objects are continuously added in to collection from remote server and same is reflecting in to listview. Now we have requirement that we should be able to freeze listview for some time,
That is objects should still get added in to collection but should not appear in listview till we unfreeze it ( we have button to freeze and unfreeze) . What is the best way to do it, when listview is bound to collection ? How to unbind collection and rebind it ? and Will i still able to filter and sort when collection is unbound from listview ? Waiting for answer please reply
Regards
Sandeep
You can just break the binding. In your freeze button handler say:
listView = _list
Which will freeze it at that point. Then in your unfreeze handler set the binding back up:
listView.SetBinding(ListView.ItemsSourceProperty, New Binding("_list"))
I hope this helps.
You should just stop updating the collection when the list is frozen, if you must keep adding to the collection even when the list is frozen (if the same collection is used somewhere else that shouldn't be frozen) then consider having two collections, one always updated and the other one bound to the listview.
What is the list type you are using as the data-source? Can you use BindingList<T>? If so, this has a settable RaiseListChangedEvents property, allowing you to turn off notifications. This will probably get very messy re your last points, though (sort/filter).
Personally, I'd work with 2 lists - the bound one, and the one that the server is updating. When paused, simply stop pumping data between the two. This should leave the paused view free for sorting / filtering etc.

Categories