Вторая часть статьи, посвящённой использованию сложных списков в универсальных приложениях Windows 10.
1.3. Реализация группировки
Одно из ключевых преимуществ таблицы ListView перед обычным списком ListBox - возможность группировки пунктов по какому-либо признаку. Сейчас мы узнаем, как это выполняется.
1.3.1. Базовые средства группировки
Для того чтобы сгруппировать перечисленные в таблице пункты по нужному признаку без особых изысков, следует выполнить четыре шага: создать представление, сгруппировать элементы коллекции, привязать таблицу к представлению и задать для таблицы представление группировки.
Изучать реализацию группировки мы будем на примере. Мы сделаем так, чтобы цвета в нашем приложении группировались по первой букве их имени.
Шаг первый: создание представления. Представление UWP - это объект класса CollectionViewSource. Он реализует часть действий по группировке пунктов. (Помимо этого, он применяется для привязки элементов интерфейса к выбранному в списке пункту, чем мы и занимались в статье "Разработка универсальных приложений Windows 10. Привязка данных".)
Если мы собираемся создать группировку с применением представления, нам понадобятся следующих два свойства класса CollectionViewSource:
Source - задаёт коллекцию, из которой представление будет брять данные;
IsSourceGrouped - если True, представление будет выполнять группировку данных, если False, не будет (поведение по умолчанию).
Давайте создадим в приложении ListView1 представление, добавив его в ресурсы начальной страницы. В интерфейсном коде начальной страницы, в тег <Page>, создающий страницу, добавим такой код:
Мы сразу указали, что данные должны группироваться, и задали для представления имя cvsColors.
Шаг второй - группировка коллекции - обычно выполняется с применением средств LINQ. Сгруппируем созданную нами коллекцию стандартных цветов по первой букве имени каждого цвета.
Переключимся на функциональный код начальной страницы и исправим код объявления класса страницы следущим образом:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
IEnumerable<PropertyInfo> pis = typeof(Windows.UI.Colors).GetRuntimeProperties();
List<NamedColor> lncs = new List<NamedColor>();
PropertyInfo pi;
for (int i = 0; i < pis.Count(); i++)
{
pi = pis.ElementAt(i);
lncs.Add(new NamedColor(pi.Name, (Windows.UI.Color)pi.GetValue(null)));
}
var result = from lnc in lncs group lnc by lnc.Name.Substring(0, 1) into grp orderby grp.Key select grp;
this.cvsColors.Source = result;
this.lsvColors.Footer = lncs.Count.ToString();
}
}
Здесь мы создаём коллекцию цветов, группируем её необходимым образом и заносим в свойство Source нашего представления cvsColors. Свойство NamedColors класса страницы, таким образом, нам более не нужно, и мы удалили его.
Шаг третий: привязка таблицы к представлению. Как это сделать, мы знаем из статьи "Разработка универсальных приложений Windows 10. Привязка данных". Необходимый XAML-код, который следует вписать в тег <ListView>, выглядит так:
И, наконец, шаг четвёртый, и последний: задание представления группировки для таблицы. Такое представление задаётся с помощью свойства GroupStyle класса ListView. Значением данного свойства должен быть объект класса GroupStyle, который, собственно, и укажет это представление.
Сейчас нам понадобятся три свойства класса GroupStyle:
HeaderTemplate - задаёт шаблон для шапки группы в виде объекта класса DataTemplate;
HeaderContainerStyle - указывает стиль для самой шапки группы в виде объекта класса Style;
HidesIfEmpty - если True, группы, не содержание ни одного пункта, будут скрыты, если False, они будут показаны.
Исходя из всего этого, напишем XAML-код, создающий шаблон и стиль для шапки группы, поместив его в тег <ListView>:
Для вывода первого символа имени цвета, по которому у нас и выполняется группировка, мы используем старую добрую надпись. Свяжем её со свойством Key создаваемой группы, применив объектную привязку (чтобы не возиться с указанием типа шаблона, которым станет обобщённый интерфейс IGrouping<TKey,?TElement>). Поместим надпись в сетку из одного столбца и одной строки. Для надписи зададим полужирный шрифт достаточно большого размера, а для сетки - светло-светло-серый цвет фона, чтобы сделать шапку заметнее.
Помимо этого, мы зададим стиль для шапки группы. Он укажет шапке растягиваться на всю ширину таблицы.
Отметим важный момент. Шапка группы "внутри" таблицы ListView представляется в виде объекта класса ListViewHeaderItem. Поэтому, чтобы заданный в свойстве HeaderContainerStyle стиль был успешно применён к шапке гурппы, для него следует задать для него в качестве целевого типа класс ListViewHeaderItem.
Теперь можно сохранить проект и запустить приложение на выполнение. Мы сразу увидим, что пункты таблицы сгруппированы по первому символу их имён (рис. 4).
Рис. 4. Окно исправленного приложения ListView1. Реализована группировка цветов по первой букве их имён
Любопытная деталь: если мы начнём прокручивать таблицу, то заметим, что шапка группы не уходит за границы видимой области таблицы, а остаётся наверху (рис. 5). Так пользователь сразу увидит, к какой группе относятся видимые в таблице пункты.
Рис. 5. Окно исправленного приложения ListView1. Шапка группы при прокрутке таблицы временно остаётся наверху
1.3.2. Указание разных представлений для группировки
Таблица позволяет использовать для группировки ещё и совершенно разные представления, то есть объекты класса GroupStyle, в зависимости от результатов выполнения какого-то условия. Сейчас мы узнаем, как это сделать.
Прежде всего, нам понадобится поддерживаемое классом ListView свойство GroupStyleSelector. Ему нужно присвоить объект класса - потомка класса GroupStyleSelector, который будет проверять нужное условие и в зависимости от результатов проверки выбирать соответствующее представление. В этом классе нужно переопределить метод SelectGroupStyleCore:
В этом методе и выполняется вся работа по выбору представления. Которое должно быть возвращено в качестве результата в виде объекта класса GroupStyle.
При переопределении метода SelectGroupStyleCore нужно иметь в виду один момент, который не описан в документации по UWP и вообще плохо документирован где-либо. Сначала значение первого параметра следует привести к типу ICollectionViewGroup - этот интерфейс представляет группу, отображаемую в таблице. Полученное значение может оказаться равным null, и этот случай следует обработать. Если же значение не равно null, объект, представляющий группу, можно извлечь из свойства Group, поддерживаемого интерфейсом ICollectionViewGroup. Свойство Group хранит значение типа object, поэтому перед использованием его нужно привести к типу обобщённого интерфейса IGrouping<TKey,TElement>; первым параметром-типом следует указать тип значения, по которому выполняется группировка, а вторым - тип элемента коллекции.
Давайте сделаем так, чтобы в нашей таблице шапки нечётных групп выводились одним цветом, а шапки чётных - другим. Выбор нужного представления мы реализуем с помощью класса GS.
Добавим в проект ListView1 новый файл программного кода GS.cs. В этом файле мы запишем код, объявляющий класс GS:
public class GS: Windows.UI.Xaml.Controls.GroupStyleSelector
{
public Windows.UI.Xaml.Controls.GroupStyle OddStyle { get; set; }
public Windows.UI.Xaml.Controls.GroupStyle EvenStyle { get; set; }
В этом классе мы объявили два свойства: OddStyle, которое будет хранить представление для нечётной группы, и EvenStyle - представление для чётной группы.
Свойство Key группы хранит первую букву имени цвета в виде строки. Нам нужно получить целочисленный Unicode-код этого символа. Поэтому мы вызываем у этого значения обобщённый метод First<T>(), который, при передаче ему в качестве параметра-типа тип char, возвращает первую букву в виде значения данного типа. Это значение мы приводим к целочисленному типу - и получаем код первой буквы.
Из полученного кода мы вычитаем шестнадцатиричное число 0x40 - код символа "@", который в кодовой таблице располагается непосредственно перед латинской буквой "A". Далее мы берём остаток от деления результирующей разности на 2; если получится число 0, значит, группа чётная, если 1 - нечётная.
Остальное нам уже знакомо.
Оба представления, что мы применим в приложении, будут использовать один и тот же шаблон шапки группы. Мы создадим его на основе уже имеющегося шаблона и поместим в состав ресурсов страницы. Вот XAML-код этого шаблона:
Обратим внимание, что здесь мы не задаём фоновый цвет для контейнера-сетки, в котором находится надпись, выводящая первую букву имени цвета. Фоновый цвет мы укажем с помощью стиля, применённого к самой шапке группы.
Теперь укажем, что выбором представления для шапки таблице будет заниматься объект нашего класса GS. Для этого впишем в тег <ListView> такой код:
И не забудем удалить из тега <ListView> тег <GroupStyle>, создающий единое представление шапки группы, - теперь он совершенно не нужен.
Настала пора проверить, что у нас получилось. Сохраним код и запустим приложение на выполнение. Теперь шапки групп имеют разный цвет фона (рис. 6).
Рис. 6. Окно исправленного приложения ListView1. Шапки групп имеют разный цвет фона
1.4. Обработка выбора пунктов в таблице
Таблица ListView может позволять пользователю выбрать один пункт, произвольное количество пунктов или вообще не предоставлять такую возможность, в зависимости от заданного режима выбора. Также она позволяет реагировать на щелчок мышью на каком-либо пункте.
1.4.1. Режимы выбора и получение выбранных пунктов
Для указания режима выбора класс ListView поддерживает свойство SelectionMode. В качестве его значения указывается один из элементов перечисления ListViewSelectionMode:
None - пользователь не может выбрать ни одного пункта в таблице;
Single - пользователь может выбрать только один пункт (поведение по умолчанию).
Для получения выбранного пункта можно использовать инструменты, знакомые нам по статье "Разработка универсальных приложений Windows 10. Использование простых списков". Это свойства SelectedIndex, хранящее целочисленный индекс выбранного пункта, и SelectedItem, которое хранит объект, на основе которого был создан выбранный пункт.
Для отслеживания выбора пункта можно обрабатывать событие SelectionChanged. В данном случае оно будет возникать всякий раз, когда пользователь выбирает в таблице новый пункт;
Extended - пользователь может выбирать произвольное количество пунктов, пользуясь клавишами-модификаторами <Shift> и <Ctrl>.
Для получения выбранных пунктов следует задействовать также знакомое нам свойство SelectedItems. Оно хранит коллекцию объектов, на основе которых были созданы выбранные пункты.
Для отслеживания изменения состава выбранных пунктов применяется всё то же событие SelectionChanged;
Multiple - пользователь может выбирать произвольное количество пунктов, не пользуясь клавишами-модификаторами, а просто щёлкая на нужных пунктах.
Для получения выбранных пунктов следует использовать свойство SelectedItems, а для отслеживания изменения состава выбранных пунктов - событие SelectionChanged.
Свойство SelectionMode можно задать в панели Свойства. Нужный параметр находится в ветки Разное.
Давайте попрактикуемся. Создадим новый проект ListView2. В контейнере-сетке, присутствующем на начальной странице, создадим один столбец, растянутый на всю ширину старинцы, и две строки, из которых вторая получит ширину, равную ширине её содержимого, а первая займёт всё оставшееся пространство. Для этого нам понадобится ввести вот такой XAML-код:
Поместим на страницу таблицу с такими параметрами:
имя - lsvLs;
Width - Auto;
Height - Auto;
Row - 0;
Column - 0;
RowSpan - 1;
ColumnSpan - 1;
HorizontalAlignment - Stretch;
VerticalAlignment - Stretch;
Margin - 10 пикселов со всех сторон;
SelectionMode - Extended.
После чего добавим туда же надпись (TextBlock) с параметрами:
имя - lblLs;
Text - "" (пустая строка);
Width - Auto;
Height - Auto;
Row - 1;
Column - 0;
RowSpan - 1;
ColumnSpan - 1;
HorizontalAlignment - Stretch;
VerticalAlignment - Stretch;
Margin - 10 пикселов со всех сторон.
Заполним таблицу пунктами, представляющими названия различных языков программирования, которые мы ради простоты представим в виде обычных строк. Нам понадобится вставить в тег <ListView> вот такой код:
Привяжем к событию SelectionChanged таблицы обработчик с таким кодом:
private void lsvLs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IList<object> si = this.lsvLs.SelectedItems;
string s = "";
for (int i = 0; i < si.Count; i++)
{
if (i > 0)
{
s += ", ";
}
s += (string)si;
}
this.lblLs.Text = s;
}
Комментировать здесь совершенно нечего, так как всё уже нам знакомо.
Сохраним код и запустим приложение на выполнение. Выберем в таблице несколько языков программирования - и в надписи под таблицей появятся их названия, разделённые запятыми (рис. 7).
Рис. 7. Окно приложения ListView2
Если число выбираемых в таблице пунктов предполагается очень большим, на создание коллекции объектов, формирующих выбранные пункты, которая хранится в свойстве SelectedItems, может уйти много времени. В таком случае удобнее пользоваться свойством SelectedRanges, которое хранит коллекцию объектов класса ItemIndexRange. Данный класс представляет целый набор следующих друг за другом выбранных пунктов таблицы.
Класс ItemIndexRange поддерживает следующие свойства:
FirstIndex - возвращает целочисленный индекс первого пункта в наборе следующих друг за другом выбранных пунктов;[/url]
LastIndex - возвращает целочисленный индекс последнего пункта в наборе следующих друг за другом выбранных пунктов;
Length - возвращает целочисленное количество выбранных пунктов, имеющихся в таком наборе.
Что касается коллекции, находящейся в свойстве SelectedRanges таблицы, то она представляет собой объект, реализующий обобщённый интерфейс IReadOnlyList<T>.
Мы можем без труда переделать приложение ListView2 таким образом, чтобы для вывода перечня выбранных в таблице пунктов применялось свойство SelectedRanges. Для этого достаточно лишь заменить код обработчика события SelectionChanged таблицы:
private void lsvLs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IList<object> items = this.lsvLs.Items;
IReadOnlyList<ItemIndexRange> sranges = this.lsvLs.SelectedRanges;
ItemIndexRange range;
string s = String.Empty;
for (int i = 0; i < sranges.Count; i++)
{
range = sranges;
for (int j = range.FirstIndex; j <= range.LastIndex; j++)
{
if (s != String.Empty)
{
s += ", ";
}
s += (string)items[j];
}
}
this.lblLs.Text = s;
}
Если мы запустим приложение и выберем в таблице несколько пунктов, то убедимся, что оно работает точно так же, как и ранее, до переделки.
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 2.34 (Страница создана за 0.03 секунд (Общее время SQL: 0.013 секунд - SQL запросов: 53 - Среднее время SQL: 0.00025 секунд))