I'm implementing a list that could easily have 10,000 small pictures in it. Actual use case is showing a list of thumbnails of a video so you can scroll thru a video frame by frame. I put in a thumbnail of the video into the list every 2/3rds of a second in the video. I need to support very long videos (e.g. 1hr video).
So virtualization options:
http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh780657.aspx
I tried "Incremental data virtualization" and that consumes too much memory for me because the images can only be referred to via streams and I'd end up opening 10,000 streams. This would crash a windows phone application due to an out of memory.
Now I'd like to try "Random access data virtualization". I see how to implement the interfaces IObservableVector<object>, INotifyCollectionChanged
(yes <object>
b/c <T>
doesn't work). The tricky part is how I can dispose of images and load images. Loading images is an Async method.
Additionally I believe this solution should have placeholders just as the MSFT doc says "An example of this type of data virtualization is often seen in photo viewing apps. Instead of making the user wait to download all photos in an album, the app shows placeholder images. As each image is retrieved, the app replaces the placeholder element for that image with a rendering of the actual photo. Even though all of the images have not been downloaded and displayed, the user can still pan and interact with the collection."
Looking at the MSFT sample for placeholders - using "ContainerContentChanging" seems like an important path. I'm guessing here that there is a way to dispose of the image within this event, and start the load of an image as well.
https://code.msdn.microsoft.com/windowsapps/ListViewSimple-d5fc27dd
Boiling this down to a question - Where is it possible to dispose of the image stream and start the load of an image for a random access virtualization list? This is a very common scenario in photo apps and is super easy to do in iOS, but seems no one has done it on windows runtime yet.
You must adapt the implementation of VirtualizingCollection, please check the following article http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization .
I wrote an sample application using an adaptation of VirtualizingCollection for Windows Phone 8.1 Runtime App.
public class ThumbnailItem
{
public Uri ImageUri { get; set; }
}
Later write the ThumbnailItem provider.
public class ThumbnailProvider : IItemsProvider<ThumbnailItem>
{
private readonly int _itemsCount;
public ThumbnailProvider(int itemsCount)
{
_itemsCount = itemsCount;
}
public int FetchCount()
{
return _itemsCount;
}
public IList<ThumbnailItem> FetchRange(int startIndex, int count)
{
var items = new List<ThumbnailItem>();
while (count-- > 0)
{
items.Add(new ThumbnailItem()
{
ImageUri = new Uri("ms-appx:///Assets/Square71x71Logo.scale-240.png")
});
}
return items;
}
}
Then, inside your ViewModel, you must create a IList property and set the value using an implementation of VirtualizingCollection. I suggest you use AsyncVirtualizingCollection.
Items = new AsyncVirtualizingCollection<ThumbnailItem>(new ThumbnailProvider(1000000), 100);
Finally, on the view you must set the DataContext object using an instance of your ViewModel and your ListView should look similar to:
<ListView
ItemsSource="{Binding Items,Mode=OneWay}"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 0 20 20">
<Image Source="{Binding ImageUri,Mode=OneTime}"
Width="72" Height="72"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Of course, the logic of the provider must be changed according your requierements, the code I wrote is just a sample.
Please, mark it as answer if helped you.
Best regards,
Denys
EDIT
Friend @Quincy, I posted a simple example, you can adapt it. Maybe for your application the ThumbnailItem class will contain the Filename property indicating the IsolateStorageFile filename. In that case you must create a Binding with converter, so you needs to implement an IValueConverter object to create a BitmapImage instance using the IsolateStorageFile.
BitmapImage image = new BitmapImage();
image.SetSource(sourceFile);
return image;
About image closing, the VirtualizingCollection has defined a pagesize, 100 by default. Yours IsolateStorageFiles will be used one time to create the BitmapImage in your IValueConverter object. Later the VirtualizingCollection will delete old pages if they are not in use(not displayed, check VirtualizingCollection implementation), and finally the GC will close&dispose the BitmapImage.
Porting VirtualizingCollection is easy, I remember I just did changes on AsyncVirtualizingCollection class. My solution was simple:
Replace ThreadPool.QueueUserWorkItem for ThreadPool.RunAsync.
Replace Trace for Debug (just debug messages, not really important).
Replace SynchronizationContext methods invocation by using:
(for windows phone app) CoreApplication.MainView.CoreWindow.Dispatcher.
(for windows app) CoreApplication.MainView.Dispatcher.
I hope it helps you.