Шаблоны и контейнеры в WinRT

142

Шаблон элемента управления, производного от ItemsControl - например, ListBox,- очень похож на шаблон любого другого элемента управления, не считая того, что он должен содержать элемент с именем ItemsPresenter. Фактически это заполнитель, представляющий список вариантов. Он не требует шаблонной привязки. Как можно убедиться из шаблона по умолчанию для ListBox, основную часть шаблона занимает элемент ScrollViewer. Вы можете заменить ScrollViewer в ListBox, если сочтете, что сможете запрограммировать что-более качественное или подходящее для вашего приложения.

Когда вы щелкаете на варианте в ListBox или используете клавиши управления курсором для перебора вариантов списка, активный вариант визуально выделяется. Откуда берется это визуальное выделение? Кто отвечает за него?

Класс, непосредственно выполняющий визуальное выделение, принадлежит к категории классов, производных от ContentControl, которые еще не рассматривались. Это элементы, производные от SelectorItem:

Object
    DependencyObject
        UIElement
            FrameworkElement
                Control
                    ContentControl
                        SelectorItem (без создания экземпляров)
                            ComboBoxItem
                            ListBoxItem
                            GridViewItem
                            ListViewItem
                            FlipViewItem

Эти пять классов соответствуют пятью классам с возможностью создания экземпляров, производным от Selector, и используются для управления отдельными вариантами списковых элементов управления. У класса ItemsControl аналогичного класса нет, потому что его варианты не могут выделяться.

Мы еще не рассматривали эти классы, потому что обычно вы не создаете их экземпляры в своих приложениях - они генерируются самим элементом управления Selector. Так как эти классы являются производными от ContentControl, они имеют собственные шаблоны (определенные в generic.xaml), и в этих шаблонах задействован класс ContentPresenter.

Допустим, вы хотите реализовать другой тип визуального выделения. Как это сделать? Как применить стиль к классу ListBoxItem, который вам вообще не виден?

ItemsControl определяет свойство ItemContainerStyle, которому задается объект Style. Например, при работе с ListBox указывается стиль, у которого свойство TargetType содержит ListBoxItem. В объекте Style может быть задан шаблон Template.

Просматривая стиль ListBoxItem по умолчанию в файле generic.xaml, вы найдете в нем группу визуальных состояний с именем SelectionStates, которая содержит шесть взаимоисключающих состояний: Unselected, Selected, SelectedUnfocused, SelectedDisabled, SelectedPointerOver и SelectedPressed.

Если вы хотите, чтобы все состояния выделения были одинаковыми, определите шаблон, отражающий выделенное состояние, а затем определите объект Storyboard для состояния Unselected. Именно такой способ я использую в проекте CustomListBoxItemStyle. Он похож на проект ListBoxWithItemTemplate, но в нем добавилось определение Style, заданное свойству ItemContainerStyle:

<Page ...>
    
    <Page.Resources>
        <local:NamedColor x:Key="namedcolors" />
    </Page.Resources>

    <Grid>
        <ListBox Name="list"
                 ItemsSource="{Binding Source={StaticResource namedcolors},
                                       Path=All}"
                 Width="380">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent},
                                                  Path=Foreground}"
                            BorderThickness="2" 
                            Width="340" Margin="8">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>

                            <Rectangle Height="80" Width="80"
                                       Margin="5,5,10,5">
                                <Rectangle.Fill>
                                    <SolidColorBrush Color="{Binding Color}" />
                                </Rectangle.Fill>
                            </Rectangle>

                            <TextBlock Grid.Column="1" FontSize="32"
                                       VerticalAlignment="Center"
                                       Text="{Binding Name}" />
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>

            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="HorizontalContentAlignment" Value="Left" />
                    <Setter Property="Background" Value="Transparent" />
                    <Setter Property="TabNavigation" Value="Local"></Setter>
                    <Setter Property="Padding" Value="10" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ListBoxItem">
                                <Border Background="{TemplateBinding Background}"
                                        BorderThickness="{TemplateBinding BorderThickness}"
                                        BorderBrush="{TemplateBinding BorderBrush}">

                                    <VisualStateManager.VisualStateGroups>
                                        <VisualStateGroup x:Name="SelectionStates">
                                            <VisualState x:Name="Unselected">
                                                <Storyboard>
                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                                   Storyboard.TargetProperty="FontStyle">
                                                        <DiscreteObjectKeyFrame Value="Normal" KeyTime="0" />
                                                    </ObjectAnimationUsingKeyFrames>

                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                                   Storyboard.TargetProperty="FontWeight">
                                                        <DiscreteObjectKeyFrame Value="Normal" KeyTime="0" />
                                                    </ObjectAnimationUsingKeyFrames>
                                                </Storyboard>
                                            </VisualState>

                                            <VisualState x:Name="Selected" />
                                            <VisualState x:Name="SelectedUnfocused" />
                                            <VisualState x:Name="SelectedDisabled" />
                                            <VisualState x:Name="SelectedPointerOver" />
                                            <VisualState x:Name="SelectedPressed" />
                                        </VisualStateGroup>
                                    </VisualStateManager.VisualStateGroups>

                                    <Grid Background="Transparent">
                                        <ContentPresenter x:Name="ContentPresenter"
                                                          FontStyle="Italic"
                                                          FontWeight="Bold"
                                                          Content="{TemplateBinding Content}"
                                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                                          ContentTransitions="{TemplateBinding ContentTransitions}"
                                                          ContentTemplate="{TemplateBinding ContentTemplate}"
                                                          Margin="{TemplateBinding Padding}" />
                                    </Grid>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>

        <Grid.Background>
            <SolidColorBrush Color="{Binding ElementName=list,
                                             Path=SelectedItem.Color}" />
        </Grid.Background>
    </Grid>
</Page>

Конечно, стиль, задаваемый свойству ItemContainerStyle объекта ListBox, может быть определен как ресурс. Я решил, что выделенный вариант должен помечаться жирным курсивным шрифтом; отсюда определения свойств FontStyle и FontWeight объекта ContentPresenter. Когда вариант списка находится в невыделенном состоянии (нормальный случай), свойства FontStyle и FontWeight анимируются до нормальных значений. Вот как это выглядит:

Задание шаблона для элемента списка

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

Настоящее предназначение шаблонов - сделать элементы управления не более необычными (хотя это, безусловно, интересно), но сделать их более практичными за счет адаптации визуального оформления элемента к его функциональности.

Далее мы продолжим рассмотрение списковых элементов управления на примере классов, производных от ListViewBase (ListView и GridView), и изучим особенности применения этих элементов управления с моделями представлений.

Пройди тесты
Лучший чат для C# программистов