В прошлой теме был рассмотрен вывод в ListView простейших данных - массива строк. На самом деле в простейшем варианте ListView получает для каждого объекта списка стоковое представление и выводит его в отдельной ячейке. Однако, как правило, данные, которыми манипулируют приложения, представляют более сложные по структуре объекты, которые могут содержать множество свойств. И для организации отображения каждого объекта класс ListView определяет свойство ItemTemplate или шаблон данных. Этому свойству передаестя объект DataTemplate, который устанавливает, как объект будет отображаться в списке.
Рассмотрим, как выполнять привязку к списку объектов на примере ListView. Для создания объектов определим класс User:
public class User { public string Name { get; set; } = ""; public int Age { get; set; } }
Теперь определим следующий класс страницы:
namespace HelloApp; class StartPage : ContentPage { public List<User> Users { get; set; } public StartPage() { Label header = new Label { Text = "Список пользователей", FontSize = 18 }; // определяем данные Users = new List<User> { new User {Name="Tom", Age=38 }, new User {Name = "Bob", Age= 42}, new User {Name="Sam", Age = 28}, new User {Name = "Alice", Age = 33} }; ListView usersListView = new ListView(); // определяем источник данных usersListView.ItemsSource = Users; // определяем шаблон данных usersListView.ItemTemplate = new DataTemplate(() => { // привязка к свойству Name Label nameLabel = new Label { FontSize = 16 }; nameLabel.SetBinding(Label.TextProperty, "Name"); // привязка к свойству Age Label ageLabel = new Label { FontSize = 14 }; ageLabel.SetBinding(Label.TextProperty, "Age"); // создаем объект ViewCell. return new ViewCell { View = new StackLayout { Padding = new Thickness(0, 5), Orientation = StackOrientation.Vertical, Children = { nameLabel, ageLabel } } }; }); Content = new StackLayout { Children = { header, usersListView }, Padding=7}; } }
С помощью свойства ItemsSource
в качестве источника данных устанавливаем список объектов User.
Свойство ItemTemplate
определяет, как эти данные будут отображаться. ItemTemplate представляет объект DataTemplate.
Его конструктор принимает делегат Func<object>
, который устанавливает, как данные будут располагаться и как будет выглядеть ячейка.
Содержимое DataTemplate представляет одну из ячеек, то есть класс Cell. В данном случае это тип ViewCell, поэтому делегат возвращает объект
ViewCell, который определяет структуру строки в ListView.
Внутри ViewCell внутренние элементы упавляются контейнером StackLayout, а отдельные элементы Label внутри StackLayout привязаны к отдельным свойствам объекта User.
Определение DataTemplate в коде XAML:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage" xmlns:local="clr-namespace:HelloApp"> <ContentPage.Resources> <ResourceDictionary> <x:Array x:Key="users" Type="{x:Type local:User}"> <local:User Name="Tom" Age="38" /> <local:User Name="Bob" Age="42" /> <local:User Name="Sam" Age="28" /> <local:User Name="Alice" Age="33" /> </x:Array> </ResourceDictionary> </ContentPage.Resources> <StackLayout Padding="7"> <Label Text="Список пользователей" FontSize="18" /> <!--<Label Text="{Binding Source={Reference usersList}, Path=SelectedItem}" />--> <ListView ItemsSource="{StaticResource users}"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.View> <StackLayout> <Label Text="{Binding Name}" FontSize="16" /> <Label Text="{Binding Age}" FontSize="14" /> </StackLayout> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
Здесь практически тоже самое. Для простоты набор данных определен в виде ресурса users непосредственно в XAML, хотя также можно было бы задать список объектов User в коде C#. Для определения визуального шаблона выводимых данных применяется объект DataTemplate, в котором через элемент ViewCell устанавливается формат отображения - в виде вертикального стека элементов Label, каждый из которых привязан к одному из свойств объекта User:
<DataTemplate> <ViewCell> <ViewCell.View> <StackLayout> <Label Text="{Binding Name}" FontSize="16" /> <Label Text="{Binding Age}" FontSize="14" /> </StackLayout> </ViewCell.View> </ViewCell> </DataTemplate>
В этом случае может возникнуть вопрос, а как же обрабатывать выбор подобных сложных данных? В случае выше каждый элемент в ListView будет представлять объект User
,
поэтому свойство SelectedItem
будет представлять выбранный объект User. Обращаясь к свойствам этого элемента, мы можем привязать элементы интерфейса к отдельным свойствам
выделенного объекта. Например, в примере выше для XAML изменим код StackLayout:
<StackLayout Padding="7"> <Label Text="{Binding Source={Reference usersListView}, Path=SelectedItem.Name, StringFormat='Selected: {0}'}" FontSize="18"/> <ListView x:Name="usersListView" ItemsSource="{StaticResource users}"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.View> <StackLayout> <Label Text="{Binding Name}" FontSize="16" /> <Label Text="{Binding Age}" FontSize="14" /> </StackLayout> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout>
Здесь текст метки привязан к свойству Name выделенного элемента:
<Label Text="{Binding Source={Reference usersListView}, Path=SelectedItem.Name, StringFormat='Selected: {0}'}"
Причем при привязке будет идти форматирование.
Если бы мы просто указали бы "SelectedItem", которая метка пыталась получить строковое представление объекта и отобразить его
<Label Text="{Binding Source={Reference usersListView}, Path=SelectedItem}"
Аналогичный пример установки привязки в коде C#:
namespace HelloApp; class StartPage : ContentPage { public List<User> Users { get; set; } = new(); public StartPage() { // определяем данные Users = new List<User> { new User {Name="Tom", Age=38 }, new User {Name = "Bob", Age= 42}, new User {Name="Sam", Age = 28}, new User {Name = "Alice", Age = 33} }; ListView usersListView = new ListView(); // определяем источник данных usersListView.ItemsSource = Users; // определяем шаблон данных usersListView.ItemTemplate = new DataTemplate(() => { // привязка к свойству Name Label nameLabel = new Label { FontSize = 16 }; nameLabel.SetBinding(Label.TextProperty, "Name"); // привязка к свойству Age Label ageLabel = new Label { FontSize = 14 }; ageLabel.SetBinding(Label.TextProperty, "Age"); // создаем объект ViewCell. return new ViewCell { View = new StackLayout { Padding = new Thickness(0, 5), Orientation = StackOrientation.Vertical, Children = { nameLabel, ageLabel } } }; }); Label header = new Label { FontSize = 18 }; // определяем привязку к выбранному элементу Binding userBinding = new Binding { StringFormat = "Selected: {0}", Path="SelectedItem.Name", Source = usersListView }; header.SetBinding(Label.TextProperty, userBinding); Content = new StackLayout { Children = { header, usersListView }, Padding=7}; } }
При нажатии или выделении элемента в списке в обработчиках событий ItemTapped
и ItemSelected
нажатый/выделенный объект будет представлять тип объектов списка,
то есть в случаях выше типа User. Например, определим в C# следующую страницу:
using Microsoft.Maui.Controls.Internals; namespace HelloApp; class StartPage : ContentPage { public List<User> Users { get; set; } = new(); Label selectedItemHeader = new Label { FontSize = 18 }; Label tappedItemHeader = new Label { FontSize = 18 }; public StartPage() { // определяем данные Users = new List<User> { new User {Name="Tom", Age=38 }, new User {Name = "Bob", Age= 42}, new User {Name="Sam", Age = 28}, new User {Name = "Alice", Age = 33} }; ListView usersListView = new ListView(); // определяем источник данных usersListView.ItemsSource = Users; // определяем шаблон данных usersListView.ItemTemplate = new DataTemplate(() => { // привязка к свойству Name Label nameLabel = new Label { FontSize = 16 }; nameLabel.SetBinding(Label.TextProperty, "Name"); // привязка к свойству Age Label ageLabel = new Label { FontSize = 14 }; ageLabel.SetBinding(Label.TextProperty, "Age"); // создаем объект ViewCell. return new ViewCell { View = new StackLayout { Padding = new Thickness(0, 5), Orientation = StackOrientation.Vertical, Children = { nameLabel, ageLabel } } }; }); usersListView.ItemSelected += UsersListView_ItemSelected; usersListView.ItemTapped += UsersListView_ItemTapped; Content = new StackLayout { Children = { selectedItemHeader, tappedItemHeader, usersListView }, Padding=7}; } private void UsersListView_ItemTapped(object sender, ItemTappedEventArgs e) { var tappedUser = e.Item as User; if (tappedUser != null) tappedItemHeader.Text = $"Нажато: {tappedUser.Name}"; } private void UsersListView_ItemSelected(object sender, SelectedItemChangedEventArgs e) { var selectedUser = e.SelectedItem as User; if (selectedUser != null) selectedItemHeader.Text = $"Выбрано: {selectedUser.Name}"; } }
В обработчиках событий мы можем привести объект, для которого сработало событие, к его изначальному типу:
// событие выделения объекта var selectedUser = e.SelectedItem as User; // событие нажатия объекта var tappedUser = e.Item as User;
Для целей тестирования обработчиков события здесь определены две отдельные метки, которые выводят данные нажатого элемента: