Давайте начнем с плохих новостей: если ваше приложение WinRT XAML использует GridView с группировкой , ваше приложение может зависнуть и зависнуть на планшетах с Windows RT. Не имеет значения, будет ли в GridView тысячи элементов или 50, он потерпит крах. Просто запустите приложение на планшете Windows RT, выполните некоторую навигацию между GridView и сведениями об элементе, и вы заметите, что 1) приложение перестанет реагировать на прикосновения и 2) оно просто закроется. И все это время приложение будет отлично работать на симуляторе и на рабочем столе.
Эта проблема
Когда в GridView включена группировка, виртуализация не работает . А когда виртуализация не работает, GridView будет иметь серьезные проблемы с производительностью, и ваше приложение будет зависать. Без виртуализации ваше приложение будет использовать гораздо больше памяти, но проблема не полностью вызвана этим: я видел, что приложения, занимающие менее 70 МБ, зависали и зависали при включении группировки.
Сбой произойдет, когда вы вернетесь на страницу, которая имеет:
- GridView с включенной группировкой
- NavigationCacheMode включен
Решение
Никогда не включайте группировку в GridView. Без группировки GridView может обрабатывать тысячи и тысячи элементов. Производительность будет отличной.
Если вам нужно сгруппировать элементы, решение состоит в том, чтобы сделать группы вручную :
- Поместите все предметы в одну коллекцию. Коллекция должна содержать не только предметы, но и группы. Например, вот коллекция из 5 предметов, из которых 2 группы: 2012, Фильм 1, Фильм 2, 2011, Фильм 3.
- Используйте ItemTemplateSelector GridView для отображения элементов и групп по-разному.
- Если вам требуется Semantic zoom, создайте отдельную коллекцию, которая содержит только группы. Итак, одна коллекция со всеми элементами и группами, как описано в 1, и, кроме того, коллекция только с группами.
Приложение не будет выглядеть так, как при встроенной группировке, но будет выглядеть достаточно хорошо. И что важно, он не потерпит крах.
Давайте используем шаги, описанные выше, чтобы преобразовать сбойное приложение WinRT XAML в приложение с высокой производительностью.
Пример приложения
Вот пример приложения, которое загружает сведения о фильме из финской службы «Видео по запросу» и отображает их в GridView, сгруппированном по году выпуска:
Он загружает и отображает 125 фильмов. Он отлично работает на рабочем столе, но на планшете с Windows RT он зависает и падает. Перед сбоем производительность уже вялая. Но перейдите несколько раз (обычно от 3 до 10 раз) к деталям фильма и обратно, и вы заметите, что приложение перестанет реагировать на прикосновения и в конечном итоге произойдет сбой.
Когда мы фиксируем производительность, создавая группы вручную, мы можем показывать тысячи фильмов в одном GridView и при этом иметь доступ к таким функциям, как семантическое масштабирование. Компромисс в том, что конечный результат выглядит иначе: заголовки групп являются частью таблицы.
Создание групп вручную
Код примера приложения показывает все шаги, необходимые для создания групп вручную, но давайте пройдемся по некоторым основам.
Шаблоны
Прежде всего, у вас есть два шаблона: один для элементов (фильмы) и один для заголовков группы (годы):
<DataTemplate x:Key="MovieTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding Cover}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding Year}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MovieCategoryTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
</StackPanel>
</Grid>
</DataTemplate>
Вам также нужен TemplateSelector, который может выбрать правильный шаблон на основе элемента:
public class MyTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var movie = item as MovieInfo;
if (movie != null)
return (DataTemplate) App.Current.Resources["MovieTemplate"];
return (DataTemplate)App.Current.Resources["MovieCategoryTemplate"];
}
}
GridView
GridView не должен устанавливать ItemTemplate, вместо этого он указывает на TemplateSelector:
<GridView x:Name="itemGridView"
TabIndex="1"
Grid.RowSpan="2"
Padding="116,157,40,46"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}"
SelectionMode="None"
IsSwipeEnabled="false" IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
</GridView>
Данные
В приведенном выше примере GridView использует ObservableCollection с именем «Items» в качестве источника элемента. Эта коллекция ни в коем случае не должна быть сгруппирована. Вместо этого он должен содержать как группы фильмов, так и фильмы:
public ObservableCollection<object> Items { get; set; }
...
var moviesByYear = movies.GroupBy(x => x.Year);
foreach (var group in moviesByYear)
{
this.Items.Add(group.Key.ToString());
foreach (var movieInfo in group)
{
this.Items.Add(movieInfo);
}
}
Семантический зум
Если требуется семантическое масштабирование, данные фильма следует разделить на две коллекции: одну, содержащую как фильмы, так и годы, а другую — только годы:
public ObservableCollection<object> Items { get; set; }
public ObservableCollection<string> Groups { get; set; }
...
var moviesByYear = movies.GroupBy(x => x.Year);
foreach (var group in moviesByYear)
{
// The group is added to two collections: Collection containing only the groups and the collection containing movies and the groups
this.Groups.Add(group.Key.ToString());
this.Items.Add(group.Key.ToString());
// The movies are only added to the collection containing movies and groups
foreach (var movieInfo in group)
{
this.Items.Add(movieInfo);
}
}
ZoomedOutView должен использовать группы как ItemsSource:
<SemanticZoom.ZoomedOutView>
<GridView VerticalAlignment="Center" Margin="200,-100,0,0" x:Name="ZoomedOutGrid" ItemsSource="{Binding Groups}"
SelectionMode="None">
Чтобы семантическое масштабирование работало правильно, GridView в ZoomedInView следует вручную прокрутить до выбранной группы:
private void SemanticZoom_OnViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e)
{
if (e.IsSourceZoomedInView)
return;
this.itemGridView.Opacity = 0;
}
private void SemanticZoom_OnViewChangeCompleted(object sender, SemanticZoomViewChangedEventArgs e)
{
if (e.IsSourceZoomedInView)
return;
try
{
var selectedGroup = e.SourceItem.Item as string;
if (selectedGroup == null)
return;
itemGridView.ScrollIntoView(selectedGroup, ScrollIntoViewAlignment.Leading);
}
finally
{
this.itemGridView.Opacity = 1;
}
}
Мы играем с Opacity, чтобы избавиться от мерцания.
Также возможно уменьшить представление, когда пользователь щелкает заголовок группы, заставляя GridView вести себя как JumpList в Windows Phone:
void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is MovieInfo)
this.Frame.Navigate(typeof (MovieDetailsPage));
else
this.Zoom.IsZoomedInViewActive = false;
}
Заключение и исходный код
Элемент управления GridView — отличный способ показать множество элементов пользователю. К сожалению, встроенная поддержка группировки будет зависать и вызывать сбой вашего приложения на планшете Windows RT. Если требуется группировка, создайте группы вручную.
Пример приложения (WinRT-GridView-XAML-Performance-Problem) доступен на GitHub . По умолчанию он начинается со страницы, которая имеет хорошую производительность и не вылетает на планшете Windows RT. Чтобы попробовать версию с включенной встроенной группировкой, измените начальную страницу приложения на BadPerformancePage.