Search code examples
c#windows-phone-7xamllistboxvirtualization

How to create a fast loading wrapping ListBox?


I've made a nice little three-item wide list of tiles that work as switches. It looks something like this:

tile switcher

Looking good huh? Well, I have about 130 of these tiles in a vertically scrolling list, and it takes ages to load. According to the performance analysis tool, each element takes about 18ms to render - which gives me about a 2.3 second rendering time. On the device, it's often twice that time. This wouldn't really be a crisis, but the UI is totally black and unresponsive up until these elements have been drawn.

After some research online, I realized this is because the WrapPanel control from the toolkit doesn't virtualize its items - thus making the GPU render all objects at once (using up a lot of memory in the process).

Now, are there any ways to make this go faster?

XAML:

<ListBox x:Name="ChannelsListBox" Grid.Row="2" Margin="0,40,0,0">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <toolkit:WrapPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.Template>
        <ControlTemplate>
            <ItemsPresenter />
        </ControlTemplate>
    </ListBox.Template>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid x:Name="ChannelTile" Margin="6,6,6,6" Tap="ChannelTile_Tap">
                <!-- context menu code removed -->
                <Rectangle Width="136" Height="136" Fill="{StaticResource LightGrayColor}" />    
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The ListBox's ItemsSource is set in the codebehind - if you wondered.


Solution

  • Well, if you populate the listbox asynchronously from another thread, you can avoid the unresponsive UI.

    EDITED2:

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
    
            /* In the xaml code:
               <ListBox x:Name="ChannelsListBox" ItemsSource="{Binding ListOfTestClasses}" ...
            */
    
            var vm = new MainPageViewModel();
            DataContext = vm;
    
            vm.StartLoadingDataAsync(10000);
        }
    }
    
    public class MainPageViewModel
    {
        public ObservableCollection<TestClass> ListOfTestClasses { get; set; }
        private BackgroundWorker workerThread;
        public MainPageViewModel()
        {
            ListOfTestClasses = new ObservableCollection<TestClass>();
            workerThread = new BackgroundWorker();
            workerThread.DoWork += new DoWorkEventHandler((object sender, DoWorkEventArgs e) =>
            {
                for (int i = 0; i < (int)e.Argument; i++)
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        ListOfTestClasses.Add(new TestClass { Text = "Element " + (i + 1) });
                    });
    
                    Thread.Sleep(150);
                }
            });
        }
    
        public void StartLoadingDataAsync(int numberOfElements)
        {
            workerThread.RunWorkerAsync(numberOfElements);   
        }
    }
    
    public class TestClass
    {
        public string Text { get; set; }
    }