Have an ObservableCollection update the UI as elements get added in

a 夏天 提交于 2019-12-12 01:35:34

问题


I'm writing a silverlight app and I'm trying trying to improve the loading time.

When my page loads, I first initialize my ObservableCollection:

        this.MyItems = new ObservableCollection<Item>();

My UI is a ListBox which I bind to an ObservableCollection through code. In MainPage_Loaded:

        MyList.ItemsSource = App.ViewModel.MyItems;

Now I bind the UI to my model. I expect this to be efficient as the collection is empty, and the rest of the UI can continue to load (not sure if my assumption is correct).

        DataContext = App.ViewModel;

Now I want to add items to my collection:

        for (int i = 0; i < number_of_items; i++)
        {
            this.MyItems.Add(myItems[i]); // myItems is a List<Item> already populated
            Thread.Sleep(20);
        }

My goal was to let the thread sleep so that it would have time to render the UI for each list box item. Also, I expected my UI to display one item at a time.

The result is that the ListBox elements appear altogether at once. If I set a Sleep of 1 second, the ListBox gets populated after 1 second times the number of elements.

What's the good way of optimizing this operation? If it's futile, I may also just bind my ListBox to a fully populated ObservableCollection. Thanks!


回答1:


Try moving the loop to a background thread. Here is one way to do that.

Phạm Tiểu Giao - Threads in WP7

Note you'll need to dispatch the UI update. Something like

Dispatcher.BeginInvoke( () => { this.MyItems.Add(myItems[i]); } );

Sleep will work if you want to use a fixed time period. Just make sure the time period is always longer than the time taken to update the display, or you will potentially overload the UI thread with updates faster than it can process.




回答2:


The reason we bind to an ObservableCollection is so the built-in notification occurs of property updates through the INotifyPropertyChanged interface implementation. This causes an event to be fired on each update to the underlying collection, which in turn causes a redraw of the related UI element(s) (ListBox in this case). The data template is applied on each redraw to each item in the collection, and is automatically done so through data binding. The items are being added to your collection faster than the draw can take place (on a separate thread), hence why it appears your load is delayed until all items are added. Youre missing the redraw cycles visually on the screen since theyre being drawn and invalidated on the screen when new items are added.

This means that your Thread.Sleep call is only delaying the complete redraw of the element on each item that is added (* number of items being added explains why your UI is being redrawn entirely on each item, but only after all have had their respective Thread.Sleep calls made which blocks the UI thread for n * sleepValue time). This is why we need to use a Dispatcher object as indicated above as these calls are made on a different thread. This allows us to redraw from the UI thread, which in essence, synchronizes the blocking calls.

I would absolutely not use the Dispatcher here as it is redundant and prevents the native synchronization to occur since the dispatcher could be referenceing an element that has not yet been created and added to the visual or logical trees (as clearly experienced by your comment on settign the value to 1000ms versus 20ms). You will still be redrawing all items in the collection as each is added making your sleep call invalid, or nonfunctional, for lack of a better term.

What I offer as an alternative solution is that you could add a storyboard animation on the Opacity property of the root element of your data template to create the visual effect of the items doing something "one at a time as being added." This way, as each item is added to the underlying collection, they will be drawn with the opacity fade animation, giving the illusion that each item is being added one at a time (and animating into view) with separate animations (at different offsets within the defined animation). I believe this will give you the effect youre looking for. But since the draw call comes from ListBox as it maintains its collection of items, the entire collection will be invalidated on each .Add call to your ViewModel items ObservableCollection object. There really is no way to override this behavior since it happens one level upstream in the hierarchy. I would advise against the provided approach.



来源:https://stackoverflow.com/questions/4677757/have-an-observablecollection-update-the-ui-as-elements-get-added-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!