📜 ⬆️ ⬇️

We do data virtualization in LongListSelector

Greetings.

This post led me to the almost complete absence of a description of how to do the virtualization of long lists on the WP8 platform. The methods used in Windows 8 do not work here. For example, the same ISupportIncrementalLoading is simply missing on WP8.

And as I (in my free time as a manager) make an application where such virtualization is vital, I decided to share my decision. I’ll say right away that I don’t pretend to ideality, it’s just a working option that can save you hours of googling and tests.
')
PS Now I switched to iOS under Monotouch and there are no such problems at all, so I decided to get an article from drafts. You never know who will be useful.

The presentation will be in the form of a tutorial, so that even those who have little knowledge of the platform and .net applications can reproduce it.

What I'm talking about




What you need to do


Create a XAML LongListSelector

It is the longlist selection that is officially recommended by MS for the development of lists. ListBox is strongly recommended not to use anymore. Well, we will not.

<phone:LongListSelector Width="480" DataContext="{Binding}" Name="List_ListSelector" ItemTemplate="{StaticResource List_ListSelectorItemDataTemplate}" /> 


Create an App.xaml DataTemplate with a template for our LongListSelector.


In my case, this is just a text that displays the item number and a picture.

 <Application.Resources> <DataTemplate x:Key="List_ListSelectorItemDataTemplate"> <StackPanel Margin="0,0,0,27" Height="400"> <TextBlock Text="{Binding Path=ID}" /> <Image Source="{Binding Path=ImageToShow}", Name="ListImage"></Image> </StackPanel> </DataTemplate> </Application.Resources> 


We create a helper class, which will be a wrapper for our sheet, collection and data. Let's call it LongVirtualList.


  class LongVirtualList { public LongListSelector List; //    public ObservableCollection<BaseListElement> Collection; // ,      public DataSource DataSource;//     .   -       - .     ,   . public LongVirtualList(LongListSelector longListSelector) { this.List = longListSelector; this.Collection = new ObservableCollection<BaseListElement>(); this.DataSource = new DataSource(); this.InitializeCollection(this.DataSource); //        , maxCount   .     . this.List.ItemsSource = this.Collection; longListSelector.ItemRealized+=this.longListSelector_ItemRealized; longListSelector.ItemUnrealized+=this.longListSelector_ItemUnrealized; } private void InitializeCollection(DataSource dataSource) { for (int i = 0; i < dataSource.Count; i++) { this.Collection.Add(new ListTestElement(i)); //ListTestElement  -  BaseListElement. } } 


This is a blank for a class. It is important that we bind the entire collection to the control. This allows for smooth scrolling, no jerking of elements (some implementations of dynamic lists also imply the use of a short collection and reuse of elements. This is not our option). A collection with empty elements does not cause memory problems even with a very large amount (I checked on a million and everything was fine).

Now comes the most interesting, in fact, for the sake of what I am writing all this here.


MS presented the following events:

ItemRealized and ItemUnrealized

The first of them works when the List wants to load a new item into itself. The second works when the given item needs to be unloaded.

A very important addition: You cannot manage the challenge of these events. They are called automatically when the phone “feels” that it will need data soon. How does he understand this? According to how many list items fit on the screen + slightly previous and next. And then an interesting cave-stone is hidden, which I found out by experience, killing for several hours. The number of list items on the screen it determines before rendering. Elements with a dynamic size (for example, pictures) are ignored, unless you manually specify their size.

For example, if you specify the height of StackPanel Height = "400" in XAML, then the ItemRealized event will be triggered sequentially for ~ 6 items in the list. If in the same example you do not specify the height, then the external result will be the same (if you use a large picture), however, the engine will try to load already 50 pieces of elements and it is likely to catch the memory overflow error.

So:
 public void longListSelector_ItemUnrealized(object sender, ItemRealizationEventArgs e) { BaseListElement item = (BaseListElement)e.Container.Content; if (item != null) { item.NullCache(); } } public void longListSelector_ItemRealized(object sender, Microsoft.Phone.Controls.ItemRealizationEventArgs e) { BaseListElement item = (BaseListElement)e.Container.Content; if (item != null) { if (item.Cached == false) { item.FillCache(); } } } 


It's time to go through the list items themselves.


The base item in the list is the BaseListElement class. In the same list, you can add any descendants of the base class.

 class BaseListElement : PropertyHelper // ,   PropertyChangedEventHandler   .        BaseListElement,         EventHandler.  -  BaseListElement  PropertyHelper   . { public int ID; public bool Cached; private BitmapImage imageToShow; public BitmapImage ImageToShow { get { return this.imageToShow; } set { this.imageToShow = value; NotifyChange("ImageToShow"); } } public BaseListElement(int id) { this.ID = id; this.Cached = false; } public virtual void NullCache() { this.Cached = false; if (this.ImageToShow != null) { this.ImageToShow = null; GC.Collect(); } } public virtual void FillCache() { this.Cached = true; // this.ImageToShow = DataSource.LoadImage(this.ID);     ,        // ,  BitmapImage bi = new BitmapImage(new Uri("Assets/test.jpg", UriKind.Relative)); bi.DecodePixelWidth = 400; bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache; this.ImageToShow = bi; } //        ,      . public virtual async Task FillCacheAsync() { this.FillCache(); } } 


Think everyone? As if not so. Code with a similar class implementation will die after several hundred loaded images. This is because WP8 is very “arbitrarily” (not the right word!) Handles the BitmapImage data cache and does not upload any images on its own to any!

Therefore, we modify the methods NullCache () and FillCache (). Now they require a reference to the Image control, which can be passed to them from the methods. We will get this link from the e.Container container of the ItemUnrealized and ItemRelized methods.

So, the correct caching of images:

 public virtual void NullCache(Image image) { if (this.ImageToShow != null) { //   ,   . BitmapImage bitmapImage = image.Source as BitmapImage; bitmapImage.UriSource = null;//   image.Source = null;// ,        . DisposeImage(this.ImageToShow)//     ,      .   =null   ,            . GC.Collect(); } this.Cached = false; } public virtual void FillCache(Image image) { this.Cached = true; BitmapImage bi = new BitmapImage(new Uri("Assets/test.jpg", UriKind.Relative)); bi.DecodePixelWidth = 400; bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache; this.ImageToShow = bi; //    image    source,         .    XAML   . Binding ImageValueBinding = new Binding("ImageToShow"); ImageValueBinding.Source = this; args.ImageControl.SetBinding(Image.SourceProperty, ImageValueBinding); } public static void DisposeImage(BitmapImage image) { Uri uri= new Uri("oneXone.png", UriKind.Relative);//   1x1,     StreamResourceInfo sr=Application.GetResourceStream(uri); try { using (Stream stream=sr.Stream) { image.DecodePixelWidth=1; //  .    ,      .    "",       BitmapImage    .   ,         WP8,   . (..    ,       ). image.SetSource(stream); } } catch {} } 


Where do we get Image for our methods of loading / unloading elements?
From here:

  public void longListSelector_ItemRealized(object sender, Microsoft.Phone.Controls.ItemRealizationEventArgs e) { BaseListElement item = (BaseListElement)e.Container.Content; Image img= FindChild<Image>(e.Container, "ListImage"); if (item != null) { if (item.Cached == false) { item.FillCache(); } } } public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject { if (parent == null) { return null; } T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { DependencyObject child = VisualTreeHelper.GetChild(parent, i); var childType = child as T; if (childType == null) { //      foundChild = FindChild<T>(child, childName); if (foundChild != null) { break; } } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; //     if (frameworkElement != null && frameworkElement.Name == childName) { foundChild = (T)child; break; } //    ,            foundChild = FindChild<T>(child, childName); } else { foundChild = (T)child; break; } } return foundChild; } 


Only a little remains, I'll show the implementation of the PropertyHelper helper class, we have a detailed tutorial after all:

  public abstract class PropertyHelper:INotifyPropertyChanged { protected void NotifyChange(string args) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(args)); } } public event PropertyChangedEventHandler PropertyChanged; } 


The presence of PropertyChanged events in the properties of the elements of the list ensures that we update the elements even if they are already loaded into the list, without additional gestures. It is very convenient. For example, we can change the language in the application settings and when updating the list of resources, its elements will update by themselves, without reloading the page.

The last moment and everything is ready.

  public MainPage() { InitializeComponent(); LongVirtualList virtualList = new LongVirtualList(List_ListSelector); } 


On this basis, you can screw up the bells and whistles, such as an additional cache or something else you want to do.

This list works for me with a test collection of thousands of 1600 * 1200 pictures, ensuring their smooth scrolling and timely loading.

I didn’t touch upon the question of asynchronous data loading.

Glad if all this will be useful to someone. In any case, I did not find a break in the entire English Internet, I did not find any collective recipe like this, I had to invent everything myself.

Source: https://habr.com/ru/post/164543/


All Articles