I've made a nice little three-item wide list of tiles that work as switches. It looks something like this:
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.
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; }
}
A few ideas which might be helpful:
- Find or implement a virtualizing WrapPanel. It's the most appropriate solution, but I don't think I've seen a solid implementation of one yet. But for this purpose, maybe you don't need perfection and can get away with something someone else has already written.
- Use a parent virtualizing vertical StackPanel containing horizontal StackPanel children. To do this, you'd need to re-shape your single sequence of data into a shorter sequence of 3-item entries. However, that may not be too hard and should give you most of the benefits of the ideal solution.
- Consider implementing "lazy" containers like I did for DeferredLoadListBox. The basic idea is to delay rendering containers until they show up on screen. I have more info and example code here: http://blogs.msdn.com/b/delay/archive/2010/09/08/never-do-today-what-you-can-put-off-till-tomorrow-deferredloadlistbox-and-stackpanel-help-windows-phone-7-lists-scroll-smoothly-and-consistently.aspx