wpf_listbox.md 10 KB

Предыдущая лекция   Следующая лекция
Поиск, сортировка Содержание Стили, триггеры и темы

Вывод данных согласно макета (ListBox, Image).

Вывод списком

В реальных проектах DataGrid используется редко. Обычно используется компонент ListBox. Пример из задания одного из прошедших демо-экзаменов:

Вверху уже знакомый нам WrapPanel, а вот основная информация выводится в виде блоков

Для создания такого макета используется элемент ListBox

В разметке вместо DataGrid вставляем ListBox:

<ListBox 
    Grid.Row="1"
    Background="White"
    ItemsSource="{Binding catList}"
>
    <!-- 
        сюда потом вставить ListBox.ItemTemplate 
    -->
</ListBox>

Внутри него вставляем шаблон для элемента списка (ListBox.ItemTemplate): пока у нас только прямоугольная рамка со скруглёнными углами (в этом макете вроде скрулять не надо, возможно осталось от другого шаблона)

<ListBox.ItemTemplate>
    <DataTemplate>
        <Border 
            BorderThickness="1" 
            BorderBrush="Black" 
            CornerRadius="5"
        >

            <!-- сюда потом вставить содержимое: grid из трёх колонок -->

        </Border>
    </DataTemplate>
</ListBox.ItemTemplate>  

Внутри макета вставляем Grid из трёх колонок: для картинки, основного содержимого и ещё чего-нибудь.

<Grid 
    Margin="10" 
    HorizontalAlignment="Stretch"
>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="64"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>

    <!-- сюда потом вставить колонки -->

</Grid>

В первой колонке выводим изображение:

<Image
    Width="64" 
    Height="64"
    Source="{Binding imageBitmap,TargetNullValue={StaticResource defaultImage}}" />

Обратите внимание, в классе Cat нет поля imageBitmap. Для получения картинки я использую вычисляемое свойство imageBitmap - в геттере проверяю есть ли такая картинка, т.к. наличие названия в модели не означает наличие файла на диске. Если файла нет, то возвращается null и рисуется картинка по-умолчанию, которую надо зашить в ресурсы приложения.

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

Добавьте в класс Cat вычисляемое свойство:

public class Cat
{
    ...

    public Uri? imageBitmap {
        get {
            var imageName = Environment.CurrentDirectory + "/img/" + (photo ?? "");

            return System.IO.File.Exists(imageName) ? new Uri(imageName) : null;
        }
    }
}

Что здесь происходит?

  • Свойство Environment.CurrentDirectory возвращает путь к исполняемому файлу, т.е. что-то типа <путь к проекту>/bin/debug/net-8
  • затем к нему добавляется подкаталог img, т.е. все ваши картинки должны быть в этом подкаталоге
  • и в конце добавляется содержимое поля photo (его надо добавить в модель и в поставщике данных заполнить названиями существующих файлов изображений)
  • перед возвращением результата проверяется есть ли такой файл на диске

Изображение по-умолчанию задается в ресурсах окна. Но в принципе можно их задать и в элементе ListBox, т.к. в других элементах это изображение не используется.

<Window.Resources>
    <BitmapImage 
        x:Key='defaultImage' 
        UriSource='./Images/picture.png' />
</Window.Resources>

тут, как раз, указывается путь к изображению в ресурсах (в моём случае в приложении создан каталог Images)


Во второй колонке вывожу основную информацию о кошке: кличку и породу.

Так как данные выводятся в несколько строк, то заворачиваю их в StackPanel (тут можно использовать и Grid, но их и так уже много в разметке)

<StackPanel
    Grid.Column="1"
    Margin="5"
    Orientation="Vertical">

    <TextBlock 
        Text="{Binding name}"/>

    <TextBlock 
        Text="{Binding breed.title}"/>
</StackPanel>

В третьей колонке выводим возраст

<TextBlock 
    Grid.Column="2"
    Text="{Binding age}"/>

На данный момент приложение должно выглядеть примерно так (я поле photo не заполнил, поэтому у всех заглушка по-умолчанию)

Видно, что размер элемента зависит от содержимого.

Чтобы это исправить нужно добавить в ListBox стиль для элемента контейнера, в котором задать горизонтальное выравнивание по ширине:

<ListBox
    Grid.Row="1"
    Grid.Column="1"
    ItemsSource="{Binding ProductList}"
>
    <ListBox.ItemContainerStyle>
        <Style 
            TargetType="ListBoxItem">
            <Setter 
                Property="HorizontalContentAlignment"
                Value="Stretch" />
        </Style>
    </ListBox.ItemContainerStyle>
    ...

Теперь окно должно выглядеть как положено:


Вывод данных "плиткой"

Такое задание было на одном из прошлых чемпионатов, вполне вероятно что появится и на демо-экзамене.

Компоненты ListBox и ListView по умолчанию инкапсулируют все элементы списка в специальную панель VirtualizingStackPanel, которая располагает все элементы по вертикали. Но с помощью свойства ItemsPanel можно переопределить тип панели элементов внутри списка.

Мы будем использовать уже знакомую вам WrapPanel:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel 
            HorizontalAlignment="Center" 
            ItemsWidth="200"
        />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

Атрибут HorizontalAlignment используем, чтобы "плитки" центрировались.

Как видим, элементы отображаются горизонтальным списком, но нет переноса. Для включения переноса элементов нужно в ListBox отключить горизонтальный скролл, добавив атрибут ScrollViewer.HorizontalScrollBarVisibility="Disabled":

Свойство ItemContainerStyle уже не нужно и его можно убрать.

Размеры наших элементов по-прежнему зависят от содержимого - тут надо править шаблон (ширина элемента Grid в DataTemplate).

Итоговая разметка для вывода "плиткой" должна выглядеть примерно так:

<ListBox
    ItemsSource="{Binding catList}"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel 
                HorizontalAlignment="Center" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    
    <ListBox.ItemTemplate>
        ...

Задание

  1. Реализовать вывод списком и плиткой в проекте "кошки" (можете сразу два списка на одном экране)
  2. При выводе списка плиткой поправить вёрстку поля "имя" (у меня уезжает за край экрана, надо добавить поддержку переносов)
Предыдущая лекция   Следующая лекция
Поиск, сортировка Содержание Стили, триггеры и темы