Касание и выделение в WinRT

50

В проектах ColorItemsSource и ColorItemsSourceWithBinding из предыдущих статей визуальное оформление каждого варианта определяется шаблоном DataTemplate, но это не мешает разработчику получать события ввода от отдельных элементов. В этих проектах вы можете задать элементу Border, с которого начинается DataTemplate, отличный от null фон и определить обработчик для события Tapped:

<Border BorderBrush="#DEFFFFFF"
        BorderThickness="1" Width="400"
        Margin="5" Background="#FF1D1D1D"
        Tapped="Border_Tapped">
      ...
</Border>

Каждому из 144 элементов Border назначается один и тот же обработчик Tapped. В аргументе sender этому обработчику передается элемент Border; свойство OriginalSource объекта аргументов события содержит либо этот элемент Border, либо другой элемент в шаблоне. Независимо от этого свойство DataContext этого элемента содержит конкретный объект NamedColor, связанный с этим элементом, что позволяет извлечь значение Color и использовать его для назначения цвета фона:

private void Border_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    object context = ((FrameworkElement)e.OriginalSource).DataContext;
    Color color = (context as NamedColor).Color;
    ((Grid)Content).Background = new SolidColorBrush(color);
}

Ниже показан результат при касании к варианту Brown:

Обработка события касания по элементу управления

Учитывая, как легко реализуется интерфейс касания или щелчка на ItemsControl возникает вопрос - зачем нужны элементы управления, производные от Selector, и прежде всего ListBox? Простой ответ: касание и выделение - не одно и то же. При выделении варианта в ListBox изменяется его визуальное оформление. Кроме того, выделение можно перемещать между вариантами при помощи клавиш со стрелками. Если вам эта функциональность не нужна, то, безусловно, решение с ItemsControl будет вполне подходящим.

Для обозначения текущего выделенного элемента Selector определяет три разных (но конечно, взаимосвязанных) свойства:

SelectedIndex

Индекс выделенного варианта в коллекции, или -1, если выделение отсутствует.

SelectedItem

Собственно выделенный вариант, или null, если выделение отсутствует.

SelectedValue

Обычно значение свойства выделенного варианта, заданного SelectedValuePath (вскоре мы поговорим об этом подробнее).

Если значение SelectedIndex отлично от -1, то SelectedItem содержит объект, полученный индексированием свойства Items по SelectedIndex. Все эти свойства могут задаваться в программном коде или в разметке XAML. Когда объект ListBox впервые заполняется данными, его свойство SelectedIndex будет равно -1, a SelectedItem - null до того, как эти свойства будут явно изменены или пользователь выделит вариант пальцем или мышью.

Класс Selector определяет событие SelectionChanged, которое инициируется при изменении выделения. Обработчик получает выделенный элемент, используя одно из перечисленных свойств. Свойство SelectedItem поддерживается свойством зависимости; это означает, что оно может быть целевым свойством привязки данных, но чаще используется как источник привязки. Проект SimpleListBox использует NamedColor.All как источник привязки для свойства Items, но не определяет шаблон. Вместо этого используется несколько иной способ отображения вариантов:

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

        <ListBox Name="listbox"
                 ItemsSource="{Binding Source={StaticResource namedcolors},
                                       Path=All}"
                 DisplayMemberPath="Name"
                 Width="300"
                 HorizontalAlignment="Center" />

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

Класс ListBox включает собственный элемент ScrollViewer, но он захватывает все возможное экранное пространство независимо от настроек HorizontalAlignment и VerticalAlignment. Назначьте ListBox конкретную ширину (Width), как это сделано выше. Как вы вскоре увидите, существуют очень веские причины, из-за которых ListBox не может определять свою ширину по максимальной ширине своих вариантов.

Вместо того чтобы определять шаблон DataTemplate для отображения объектов NamedColor, я задал свойству DisplayMemberPath значение «Name», относящееся к свойству Name вариантов из ListBox. Объекты вариантов относятся к типу NamedColor - и к счастью, NamedColor включает свойство Name, которое используется ListBox для вывода вариантов. Изначально кисть SolidColorBrush, заданная Grid, соответствует значению Color по умолчанию, потому что выделенного варианта еще нет, но после выделения этот цвет образует фон окна.

Использование элемента управления ListBox

В программе используется темная тема оформления. Светлый фон списка ListBox и цвет выделенного варианта представляют поведение по умолчанию для ListBox. Позднее будет показано, как изменить это поведение. Немного поэкспериментировав с этой программой, вы увидите, что для перемещения выделения можно использовать клавиши управления курсором, Page Up, Page Down, Home и End.

Существует альтернативный способ определения привязки к выделенному варианту. По аналогии со свойством DisplayMemberPath, которое используется для обозначения свойства, определяющего визуальное представление варианта в списке, свойство SelectedValuePath задает имя свойства, которое будет представлено как SelectedValue:

<ListBox Name="listbox"
    ItemsSource="{Binding Source={StaticResource namedcolors},
                          Path=All}"
    DisplayMemberPath="Name"
    SelectedValuePath="Color"
    Width="300"
    HorizontalAlignment="Center" />

<Grid.Background>
    <SolidColorBrush Color="{Binding ElementName=listbox,
                     Path=SelectedValue}" />
</Grid.Background>

Свойство SelectedValuePath элемента ListBox указывает, что свойство Color вариантов ListBox должно представляться как SelectedValue, а это упрощает привязку SolidColorBrush. Свойство SelectedItem легко перепутать с SelectedValue. Если свойство SelectedValuePath не задано, они совпадают. В противном случае SelectedItem определяет объект в коллекции, a SelectedValue - свойство этого объекта.

Чаще свойству ItemTemplate объекта ListBox задается шаблон DataTemplate, как в нашем примере. Я упростил шаблон варианта, чтобы в нем не отображалось шестнадцатеричное представление цвета, но в остальном он не отличается от тех шаблонов, которые вы уже видели:

<Page ...>

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

        <ListBox Name="listbox"
                 ItemsSource="{Binding Source={StaticResource namedcolors},
                                       Path=All}"
                 Width="400"
                 HorizontalAlignment="Center">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="{Binding Path=Foreground,
                                    RelativeSource={RelativeSource TemplatedParent}}"
                            BorderThickness="1"
                            Width="340" Margin="5"
                            Loaded="Border_Loaded">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>

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

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

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

При использовании шаблона варианта проявляется одно важное отличие, связанное с окраской элемента Border. В программах, использующих ItemsControl, элемент Border может окрашиваться кистью переднего плана действующей темы оформления. С темной темой (которая использовалась ранее) цвет будет белым. Однако мы уже выяснили, что варианты ListBox используют белый фон по умолчанию, а это означает, что белая кисть растворится в фоне. На самом деле следует задать ей свойство Foreground элемента-предка TemplatedParent, а для этого существует специальный синтаксис привязки:

BorderBrush="{Binding Path=Foreground,
    RelativeSource={RelativeSource TemplatedParent}}"

Свойство RelativeSource также может принимать значения Self и TemplatedParent, причем в данном случае значение Self неприменимо, поскольку Border не имеет свойства Foreground.

Что именно представляет собой TemplatedParent? В нашем контексте это ContentPresenter - класс, который обычно встречается только при написании другой разновидности шаблонов (шаблон элемента управления), которая будет рассматриваться позднее. Элементу TextBlock для получения правильного цвета не нужны никакие привязки, потому что он просто наследует свойство Foreground, и оба элемента правильно изменяют цвет при выборе варианта в списке.

Шаблон ячейки элемента управления ListBox

Класс ListBox поддерживает множественное выделение, если вам потребуется такая функциональность. Задайте свойству SelectionMode значение Multiple или Extended и используйте свойство SelectedItems для получения выделенных вариантов.

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