Опрос
Вы участвуете в программе Windows Insider?
Популярные новости
Обсуждаемые новости

18.06.2018 21:46 | dronov_va

Как и было обещано, вниманию уважаемых читателей представляется вторая статья из подцикла об использовании списков при программировании универсальных приложений Windows 10 (UWP). Эта статья расскажет об использовании сложных списков: таблицы (ListView) и перечня (GridView).

Подразумевается, что читатели уже прочитали опубликованные ранее статьи "Разработка универсальных приложений Windows 10. Самое первое приложение", "Разработка универсальных приложений Windows 10. Размещение элементов на страницах", "Разработка универсальных приложений Windows 10. Привязка данных", "Разработка универсальных приложений Windows 10. Использование простых списков" и уже имеют понятие об универсальных приложениях и принципах их разработки.



Обычный и раскрывающийся - то есть простые - списки рекомендуется использовать для представления коллекций из, как правило, простых сущностей, обычно строк. В то время как таблица и перечень наилучшим образом подойдут, чтобы представить коллекции, состоящие из более сложных сущностей - объектов какого-то класса.


1. ListView: таблица



Класс ListView представляет особый список, который автор назвал таблицей. Он выводит выстроенный по вертикали набор пунктов, сформированных на основе коллекции каких-либо значений, которые могут принадлежать любому типу, и позволяет выбрать, в зависимости от настроек, либо один пункт, либо произвольное их количество. В этом смысле он практически не отличается от обычного списка ListBox, рассмотренного в предыдущей статье.

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


1.1. Базовые возможности таблицы

Таблица, как и обычный список, позволяет:

  • создавать набор пунктов на основе коллекции значений любого типа с применением привязки данных;
  • задавать коллекцию значений любого типа, на основе которых будут сформированы пункты, непосредственно в этом элементе управления;
  • создавать пункты из объектов специализированного класса ListViewItem, по возможностям полностью аналогичного классам ComboBoxItem и ListBoxItem (см. статью "Разработка универсальных приложений Windows 10. Использование простых списков");
  • задавать шаблоны и стили для всех пунктов;
  • указывать разные шаблоны и стили для разных пунктов на основании выполнения каких-либо условий;
  • настраивать параметры контейнера для пунктов списка.



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

В качестве примера универсального приложения, использующего таблицу, автор взял таковой из статьи портала Microsoft Dev Center "Item containers and templates". Это приложение - своего рода шпаргалка по стандартным цветам, поддерживаемым платформой UWP.

Создадим в Visual Studio новый проект и дадим ему имя ListView1. В контейнере-сетке Grid, присутствующем на начальной странице, создадим один столбец и одну строку, задав для них такие размеры, чтобы они растянулись на всё пространство страницы (то есть звёздочку *). К сожалению, в Visual Studio 2017 15.6, которой пользовался автор, визуальные инструменты для указания разметки сетки не работают (будем надеяться, что эта проблема будет решена в следующих версиях среды разработки), и нам придётся ввести соответствующий XAML-код вручную. Вот этот код:

<Grid . . .>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>


Поместим на начальную страницу таблицу ListView. Этот элемент управления можно найти в панели Панель элементов, в ветви Все элементы управления XAML. Зададим для таблицы следующие параметры:

  • имя - lsvColors;
  • Width - Auto;
  • Height - Auto;
  • Row - 0;
  • Column - 0;
  • RowSpan - 1;
  • ColumnSpan - 1;
  • HorizontalAlignment - Stretch;
  • VerticalAlignment - Stretch;
  • Margin - 10 пикселов со всех сторон.



Добавим в проект новый файл программного кода NC.cs, в котором запишем код, объявляющий класс NamedColor, что будет представлять стандартный именованный цвет UWP:

public class NamedColor
{
public string Name { get; set; }
public Windows.UI.Color Color { get; set; }
public Windows.UI.Xaml.Media.SolidColorBrush Brush {
get { return new Windows.UI.Xaml.Media.SolidColorBrush(this.Color); }
}

public NamedColor(string name, Windows.UI.Color color) {
this.Name = name;
this.Color = color;
}
}


Свойство Name будет хранить имя стандартного цвета, представленное в виде строки, а свойство Color - сам этот цвет в виде объекта класса Color. Доступное только для чтения свойство Brush вернёт одноцветную кисть, созданную на основе текущего цвета, в виде объекта класса SolidColorBrush. Эта кисть понадобится нам, чтобы вывести в пункте таблицы пример этого цвета.

Переключимся на функциональный код начальной страницы и добавим в набор выражений импорта (using) ещё одно:

using System.Reflection;

Если мы этого не сделаем, то не сможем использовать в коде, что напишем далее, метод расширения GetRuntimeProperties() из пространства имён System.Reflection.

Дополним код, объявляющий класс начальной страницы, чтобы он выглядел следующим образом:

public sealed partial class MainPage : Page
{
private List<NamedColor> NamedColors = new List<NamedColor>();
public MainPage()
{
this.InitializeComponent();

IEnumerable<PropertyInfo> pis = typeof(Windows.UI.Colors).GetRuntimeProperties();
PropertyInfo pi;
for (int i = 0; i < pis.Count(); i++)
{
pi = pis.ElementAt(i);
this.NamedColors.Add(new NamedColor(pi.Name, (Windows.UI.Color)pi.GetValue(null)));
}
this.lsvColors.ItemsSource = this.NamedColors;
}
}


Мы объявляем в классе частное поле NamedColors, которое будет хранить коллекцию стандартных цветов, представленную в виде объекта обобщённого класса List<T>. Элементы этой коллекции - отдельные стандартные цвета - будут представлять собой объекты объявленного ранее класса NamedColor. После чего сразу же присваиваем этому полю пустую коллекцию.

Код, получающий стандартные цвета и заносящий их в коллекцию NamedColors, мы запишем в конструкторе класса. Поместим его после вызова метода InitializeComponent(), который выполняет вывод страницы и инициализацию размещённых на ней элементов интерфейса.

Все поддерживаемые платформой UWP стандартные цвета записаны в статических свойствах класса Colors. Следовательно, чтобы получить все стандартные цвета, нам нужно извлечь сведения об этом классе, добраться до списка его свойств, перебрать их, на основе каждого свойства создать объект класса NamedColor и добавить его в коллекцию NamedColors. Сказано - сделано.

Сведения о классе Colors мы получаем с помощью оператора typeof. Эти сведения представлены в виде объекта класса Type. У него мы сразу же вызываем метод расширения GetRuntimeProperties() из пространства имён System.Reflection. (Именно поэтому мы и импортировали данное пространство имён ранее.) Метод вернёт в качестве результата коллекцию, реализующую обобщённый интерфейс IEnumerable<T> и содержащую все свойства класса Colors (то есть все имеющиеся в наличии стандартные цвета) в виде объектов класса PropertyInfo.

Далее мы перебираем в цикле все элементы полученной таким образом коллекции и для каждого создаём в коллекции NamedColors элемент - объект класса NamedColor. Имя свойства, а фактически - имя цвета, мы извлекаем из свойства Name класса PropertyInfo, а значение свойства - сам цвет - получаем вызовом метода GetValue(), передав ему в качестве параметра null (поскольку извлекаем значение статического свойства) и не забыв преобразовать возвращённый им результат в нужный тип.

Наконец, мы присваиваем коллекцию цветов NamedColors свойству ItemsSource нашей таблицы lstColors, тем самым привязывая таблицу к коллекции.

Сохраним код и запустим приложение на выполнение, чтобы проверить, не допустили ли мы ошибок. И, удостоверившись в этом, завершим приложение - всё равно смотреть там пока особо не на что.

Переключимся на интерфейсный код начальной страницы и зададим для пунктов таблицы шаблон. Впишем в создающий таблицу тег <ListView> следующий XAML-код:

<ListView . . .>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<StackPanel Orientation="Horizontal">
<Rectangle Width="100" Height="40" Fill="{x:Bind Brush}" Margin="0,0,10,0"/>
<TextBlock Text="{x:Bind Name}" FontSize="20" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>


Поскольку мы, во-первых, создаём необъектную привязку, а, во-вторых, задаём коллекцию-источник программно, нам придётся явно указать для шаблона тип значений, на основе которых будут формироваться пункты таблицы (иначе компилятор Visual Studio не сможет выяснить, существуют ли указанные в объявлении привязки свойства, и выдаст ошибку). Это выполняется с помощью атрибута x:DataType тега <DataTemplate>. В качестве типа мы указываем наш класс NamedColor, обязательно предварив его префиксом local, обозначающим пространство имён приложения.

Шаблон у нас включает всего два элемента интерфейса: прямоугольник (класс Rectangle), закрашенный текущим цветом (кисть, сформированная на его основе, берётся из свойства Brush класса NamedColor), и текстовая надпись (класс TextBlock), в которой выводится имя цвета (взятое из свойства Name). Эти элементы выстроены по горизонтали с применением контейнера-стопки (класс StackPanel).

Запустим приложение на выполнение. Теперь наша таблица выводит одновременно образец цвета и его название (рис. 1).


Рис. 1. Окно приложения ListView1




1.2. Указание шапки и поддона у таблицы

Таблица может выводить шапку и поддон с произвольным содержимым. Для их указания применяются следующие свойства класса ListView:

  • Header - задаёт содержимое шапки;
  • Footer - задаёт содержимое поддона.
  • Тип обоих этих свойств - object. Это значит, что для шапки и поддона мы можем задать любое содержимое: элементарное значение (например, строку) или элемент интерфейса, в том числе контейнер.Следующие два свойства пригодятся, если для шапки и поддона в качестве содержимого указано значение, не являющееся элементом интерфейса;
  • HeaderTemplate - задаёт шаблон для шапки;
  • FooterTemplate - задаёт шаблон для поддона.
  • Шаблон, как обычно, задаётся в виде объекта класса DataTemplate.



Давайте придадим таблице в приложении ListView1 действительно "табличный" вид. А именно, выведем все параметры цвета в виде таблицы, создадим шапку с заголовками столбцов и поддон, в котором будет выведено полное количество цветов, перечисленных в таблице.

Начнём мы с реализации "табличной" шапки.

Откроем интерфейсный код начальной страницы и добавим в тег <ListView>, создающий таблицу, следующий XAML-код:

<ListView . . .>
. . .
<ListView.Header>
<Grid Padding="10,10,10,10" Background="LightGray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Образец цвета" FontWeight="Bold" TextAlignment="Center"/>
<TextBlock Text="Имя цвета" Grid.Column="1" FontWeight="Bold" TextAlignment="Center"/>
<TextBlock Text="R" Grid.Column="2" FontWeight="Bold" Foreground="Red" TextAlignment="Center"/>
<TextBlock Text="G" Grid.Column="3" FontWeight="Bold" Foreground="Green" TextAlignment="Center"/>
<TextBlock Text="B" Grid.Column="4" FontWeight="Bold" Foreground="Blue" TextAlignment="Center"/>
</Grid>
</ListView.Header>
</ListView>


В шапку таблицы мы поместили контейнер-сетку из одной строки и пяти столбцов. Четыре столбца имеют фиксированную ширину, а один, второй по счёту, будет растягиваться, чтобы занять всю ширину таблицы.

В столбцы мы поместили текстовые надписи с заголовками. Для всех мы задали полужирный шрифт (значение Bold свойства FontWeight), для четырёх из них - выравнивание по середине (значение Center свойства TextAlignment; задание выравнивание посредством свойства HorizontalAlignment не приведёт к нужному результату), а для трёх - разный цвет текста.

Теперь найдем код, создающий шаблон для пунктов таблицы, и заменим его таким кодом:

<ListView . . .>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Width="130" Height="40" Fill="{x:Bind Brush}"/>
<TextBlock Text="{x:Bind Name}" Grid.Column="1" FontSize="20" VerticalAlignment="Center"/>
<TextBlock Text="{x:Bind Color.R}" Grid.Column="2" Foreground="Red" TextAlignment="Center"/>
<TextBlock Text="{x:Bind Color.G}" Grid.Column="3" Foreground="Green" TextAlignment="Center"/>
<TextBlock Text="{x:Bind Color.B}" Grid.Column="4" Foreground="Blue" TextAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
. . .
</ListView>


Здесь мы, в общем, сформировали точно такую же таблицу из пяти столбцов. В первом столбце будет выводиться, опять же, прямоугольник, закрашенный образцом текущего цвета, во-втором - имя цвета, а в последующих трёх - доли, соответственно, красного, зелёного и синего в текущем цвете, выраженные целыми числами (эти величины можно взять из полей R, G и B класса Color).

Помимо шаблона, мы задали стиль для объектов класса ListViewItem, представляющих пункты таблицы. Этот стиль предписывает пунктам растягиваться на всю ширину таблицы, задав для свойства HorizontalContentAlignment значение Stretch. Если этого не сделать, каждый пункт таблицы будет иметь ширину, соответствующую ширине его содержимого, и нужный нам эффект настоящей таблицы с шапкой достигнут не будет. Стиль для пунктов указывается в виде объекта класса Style и присваивается свойству ItemContainerStyle таблицы.

Запустим приложение на выполнение и посмотрим, как оно теперь выглядит (рис. 2). Существенно лучше, не находите?


Рис. 2. Окно исправленного приложения ListView1. У таблицы цветов задана шапка



Теперь займёмся поддоном. Сделаем так, чтобы в нём выводилось количество цветов, перечисленных в таблице. Причём эту величину мы будем задавать занесением его в свойство Footer таблицы, а для представления поддона применим шаблон, который присвоим свойству FooterTemplate.

Сначала подготовим шаблон. В тег <ListView> добавим такой код:

<ListView . . .>
. . .
<ListView.FooterTemplate>
<DataTemplate x:DataType="x:String">
<Grid Background="WhiteSmoke">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Всего" Margin="0,6,0,6" FontStyle="Italic" HorizontalAlignment="Right"/>
<TextBlock Text="{x:Bind}" Margin="3,6,0,6" FontStyle="Italic" Grid.Column="1"/>
<TextBlock Text="цветов" Margin="3,6,36,6" FontStyle="Italic" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>


Здесь всё просто. Мы используем контейнер-сетку для того, чтобы разместить по горизонтали друг за другом надписи, выводящие строку "Всего", количество цветов и надпись "цветов" соответственно, и сдвинуть их к правому краю. Для надписей мы указали курсивный шрифт, а для сетки - очень светло-серый цвет фона, чтобы визуально выделить поддон. Надпись, выводящую количество цветов, мы связали со значением, присвоенным свойству Footer, с помощью необъектной привязки, а для самого шаблона указали строковый тип данных.

Переключимся на функциональный код начальной страницы и добавим в самый конец кода конструктора класса страницы выражение:

this.lsvColors.Footer = this.NamedColors.Count.ToString();

Оно присвоит свойству Footer таблицы количество элементов в коллекции NamedColors, то есть количество цветов, предварительно преобразовав его в строковый тип вызовом метода ToString().

Сохраним проект и запустим приложение. Прокрутим таблицу в самый конец, чтобы увидеть созданный нами поддон (рис. 3). А что, неплохо получилось!..


Рис. 3. Окно исправленного приложения ListView1. У таблицы цветов задан поддон



У таблицы UWP есть недостаток: и шапка, и поддон прокручиваются вместе с её содержимым. Сделать, по крайней мере, шапку постоянно видимой можно, указав для неё стиль, код которого можно найти здесь. К сожалению, код этого стиля довольно велик, и автор не стал приводить его непосредственно в тексте статьи, чтобы не увеличивать её объём.


Дополнительные материалы




Продолжение следует...


Владимир Дронов, MSInsider.ru Team
Июнь 2018

Комментарии

Комментариев нет...
Для возможности комментировать войдите в 1 клик через

По теме

Акции MSFT
420.55 0.00
Акции торгуются с 17:30 до 00:00 по Москве
Все права принадлежат © ms insider @thevista.ru, 2022
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 2.34 (Страница создана за 0.065 секунд (Общее время SQL: 0.044 секунд - SQL запросов: 53 - Среднее время SQL: 0.00084 секунд))
Top.Mail.Ru