Давайте начнем с плохих новостей: если ваше приложение 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.