Спонсор: Microsoft
Опубликован: 21.03.2013 | Доступ: свободный | Студентов: 6315 / 126 | Длительность: 06:49:00
Лабораторная работа 2:

Обработка данных

XAML+C#. Практическое занятие №2

Стилизация приложения

Задание: продолжая проект, полученный в результате выполнения первого практического задания, измените внешний вид приложения в соответствии со спецификой выбранной темы (источников данных) и рекомендациями ниже.

  • Не забудьте убедиться, что ваше приложение корректно выглядит при разных размерах экрана и в разных состояниях (snap, fill, full).
  • В качестве подтверждения выполнения лабораторной работы от вас потребуется предоставить скриншот стилизованного приложения (главный экран).

Сейчас ваше приложение проверяет доступ в интернет и при запуске должно выглядеть аналогично следующей картинке.


У нас все выглядит гораздо лучше, чем раньше, но почему все топики отображаются плитками одного размера? Пожалуй, надо сделать, как в Windows Store!

Это несколько более сложная задача, чем все то, что мы делали раньше. Но мы не останавливаемся перед трудностями!

Для того, чтобы реализовать подобный функционал, нам придется создать свой класс, наследник GridView. Добавьте в проект новый файл класса с именем: VariableSizeGridView.cs. В новом файле, добавьте в блок using директиву:

using Windows.UI.Xaml.Controls;

укажите, что класс наследуется от GridView и переопределите в нем метод класса: PrepareContainerForItemOverride. В результате должно получиться что-то вроде этого:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;

namespace MyReader
{
    class VariableSizeGridView: GridView 
    {
        protected override void PrepareContainerForItemOverride
         (Windows.UI.Xaml.DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
        }
    }
}

Собственно метод PrepareContainerForItemOverride и будет определять логику, по которой будут отображаться плитки разного размера в сгруппированном представлении. Теперь необходимо подготовить разметку в файле GroupedItemsPage.xaml.

Сначала заменяем GridView на наш класс — local:VariableSizeGridView. Далее в ItemsPanelTemplate дополняем свойства VariableSizedWrapGrid указав размер Item-а и максимальное количество строк и колонок, так, что он будет выглядеть следующим образом:

<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"
  ItemWidth="200" ItemHeight="125" MaximumRowsOrColumns="9" />

Добавим собственный шаблон Item в ресурсы страницы:

<DataTemplate x:Key="CustomItemTemplate">
    <Grid HorizontalAlignment="Left">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" VerticalAlignment="Bottom">
                    <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" 
             Height="90" Margin="15,0,15,0" FontSize="30" />
        </StackPanel>
    </Grid>
  </DataTemplate>

И укажем его в качестве шаблона Item, вместо стандартного в local:VariableSizeGridView::

<local:VariableSizeGridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Grid.RowSpan="2"
    Padding="116,137,40,46"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource CustomItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick">

Запускаем приложение.


Выглядит не очень красиво. Но это пока мы не добавим наш код с логикой в PrepareContainerForItemOverride.

Итак, добавляем код:

class VariableSizeGridView: GridView 
{
    private int rowVal;
    private int colVal;
        
    protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
    {
        RSSDataItem dataItem = item as RSSDataItem;
        int index = -1;

        if (dataItem != null)
        {
            index = dataItem.Group.Items.IndexOf(dataItem);

        }

        if (index == 1)
        {
            colVal = 2;
            rowVal = 4;
        }
        else
        {
            colVal = 2;
            rowVal = 2;
        }

        if (index == 2)
        {
            colVal = 2;
            rowVal = 4;
        }

        if (index == 5)
        {
            colVal = 4;
            rowVal = 4;
        }

        VariableSizedWrapGrid.SetRowSpan(element as UIElement, rowVal);
        VariableSizedWrapGrid.SetColumnSpan(element as UIElement, colVal);

        base.PrepareContainerForItemOverride(element, item);
    }

Как видно, мы просто руками указываем какую по порядку плитку, как отображать.

Запустим еще раз.


Выглядит гораздо лучше, а реализуя шаблоны для Item и логику работы PrepareContainerForItemOverride можем добиться практически любого представления в пределах GridView.

Теперь у нас красивые плитки разных размеров, причем, мы можем, при желании замостить по разному группы. Давайте это сделаем. Блог Стаса Павлова сделаем с плитками разных размеров, а для Блога Сергея Пугачева – не будем баловать пользователя таким разнообразием.

Как это сделать? Все очень просто. Логика, которая определяет размеры плиток вся реализована в функции PrepareContainerForItemOverride класса VariableSizeGridView, так что можно добавить туда соответствующую логику.

Чтобы отличить группы, будем использовать UniqueID, для группы он соответствует URL на RSS. Добавим следующий код в функцию PrepareContainerForItemOverride:

int group = -1;

if (dataItem.Group.UniqueId.Contains("stas"))
{
    group = 1;
}

А теперь изменим поведение функции, в зависимости от того, для какой группы она работает, добавив условие.

if (group > 0)
{ 
    if (index == 2)
    {
        colVal = 2;
        rowVal = 4;
    }

    if (index == 5)
    {
        colVal = 4;
        rowVal = 4;
    }
}

Запустим программу, чтобы проверить, как теперь это работает.


Поставим себе еще более сложную задачу. А что если у нас для отображения необходимо использовать вообще разные шаблоны отображения? Это также решаемая задача. Нам опять придется реализовать свой класс, который будет содержать логику, но теперь он будет уже наследоваться от DataTemplateSelector.

Итак, добавим в решение новый класс с именем MyDataTemplateSelector, указываем, что он наследутся от DataTemplateSelector. Добавяляем в using директивы:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

И переопределяем функцию SelectTemplateCore. В результате должно получится следующее:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MyReader
{
    class MyDataTemplateSelector : DataTemplateSelector
    {
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            return base.SelectTemplateCore(item, container);
        }
    }
}

Теперь нужно определить в XAML файле те шаблоны из которых мы будем выбирать. Для этого открываем GroupedItemsPage.xaml и переходим к ресурсам страницы, где у нас определен шаблон CustomItemTemplate:

<DataTemplate x:Key="CustomItemTemplate">
  <Grid HorizontalAlignment="Left">
    <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
        <Image Source="{Binding Image}" Stretch="UniformToFill" 
               AutomationProperties.Name="{Binding Title}"/>
    </Border>
    <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" 
                VerticalAlignment="Bottom">
         <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"  
                    Height="90" Margin="15,0,15,0" FontSize="30" />
    </StackPanel>
  </Grid>
</DataTemplate>

Определим еще один шаблон с минимальными изменениями – изменим выраванивания:

<DataTemplate x:Key="CustomItemTemplate2">
    <Grid HorizontalAlignment="Right">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill" 
                    AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" VerticalAlignment="Top">
            <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"  
                       Height="90" Margin="15,0,15,0" FontSize="30" />
        </StackPanel>
    </Grid>
</DataTemplate>

Вернемся теперь к нашему классу MyDataTemplateSelector и добавим код, который определяет, какой шаблон использовать, в зависимости от того, элемент какой группы отображается:

using MyReader.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MyReader
{
    class MyDataTemplateSelector : DataTemplateSelector
    {

        public DataTemplate Template1 { get; set; }
        public DataTemplate Template2 { get; set; }

        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            RSSDataItem dataItem = item as RSSDataItem;
            
            if (dataItem.Group.UniqueId.Contains("stas"))
            {
                return Template1;
            }
            else
                return Template2;
        }
    }
}

Возвращаемся к GroupedItemsPage.xaml и переходим к ресурсам страницы, где у нас определены шаблоны и инициализируем наш селектор:

<local:MyDataTemplateSelector x:Key="MyDataSelector" 
        Template1="{StaticResource CustomItemTemplate}" 
        Template2="{StaticResource CustomItemTemplate2}"/>

И указываем селектор вместо указания шаблона Item для local:VariableSizeGridView:

<local:VariableSizeGridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Grid.RowSpan="2"
    Padding="116,137,40,46"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplateSelector="{StaticResource MyDataSelector}" 
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick">

Запускаем приложение, чтобы убедиться, что все работает, как запланировано.


И, давайте, как пример дальнейшей стилизации приложения, изменим стиль текста подписи на плитках группы.

Создадим на основе стиля BasicTextStyle необходимые нам стили и разместим их в ресурсах страницы GroupedItemsPage.xaml:

<Style x:Key="ExtendedTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BasicTextStyle}">
   <Setter Property="LineHeight" Value="40"/>
   <Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
   <!-- Properly align text along its baseline -->
   <Setter Property="RenderTransform">
      <Setter.Value>
         <TranslateTransform X="-1" Y="4"/>
       </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ExtendedTitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource ExtendedTextStyle}">
     <Setter Property="FontWeight" Value="SemiBold"/>
</Style>

И наложим этот стиль на текст в наших шаблонах::

<DataTemplate x:Key="CustomItemTemplate">
    <Grid HorizontalAlignment="Left">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill" 
                   AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" VerticalAlignment="Bottom">
            <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"  
                       Style="{StaticResource ExtendedTitleTextStyle}" Height="90" Margin="15,0,15,0" FontSize="30" />
        </StackPanel>
    </Grid>
</DataTemplate>

<DataTemplate x:Key="CustomItemTemplate2">
    <Grid HorizontalAlignment="Right">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill" 
                   AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" VerticalAlignment="Top">
            <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"  
                       Style="{StaticResource ExtendedTitleTextStyle}" Height="90" Margin="15,0,15,0" FontSize="30" />
        </StackPanel>
    </Grid>
</DataTemplate>

Запустим приложение и посмотрим, как теперь выглядят подписи на сгруппированых элементах.


Все выглядит гораздо лучше. Если есть желание, можно поиграться со стилями, добиваясь желаемого эффекта.

Кстати, если вам не нравится темная тема, то вы можете поменять тему на светлую. Сделать это очень просто. Необходимо в файле App.xaml в свойствах корневого xaml элемента указать:

RequestedTheme="Light"

В результате это будет выглядеть похоже на следующий код:

<Application
    x:Class="ИмяВашегоПриложения.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ИмяВашегоПриложения "
    xmlns:localData="using:ИмяВашегоПриложенияData"
    RequestedTheme="Light">

А запущенное приложение станет выглядеть похоже на:

В качестве дополнительной работы можно творчески поработать с шаблоном отображения элемента на главной странице и сделать его, например таким:

<DataTemplate x:Key="CustomItemTemplate">
    <Grid HorizontalAlignment="Left">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <Border BorderBrush="#FF59C102" BorderThickness="2"/>
        <StackPanel Background="#FF59C102" VerticalAlignment="Bottom">
            <TextBlock Text="{Binding Title}" Foreground="White"  Style="{StaticResource ExtendedTitleTextStyle}" 
            Margin="15,8,15,15" FontSize="14.667" FontWeight="Normal" LineHeight="16" />
        </StackPanel>
    </Grid>
</DataTemplate>

Тогда, ваш клиент будет выглядеть еще более привлекательно:

Андрей Милютин
Андрей Милютин

Будьте добры сообщите какой срок проверки заданий и каким способом я буду оповещен!

Данила Слупский
Данила Слупский

К сожалению, я не могу выполнить данную практическую работу в VS 2013 на WIndows 8.1. Код описанных файлов отличается от кода в моем проекте. Как мне быть?