cs_layout2.md 12 KB

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

Старый вариант с SQL-запросами находится тут

Напоминаю как выглядит макет списка продукции

Создание подключения к БД MySQL. Получение данных с сервера. Содержание Пагинация, сортировка, фильтрация, поиск
Критерий Баллы
Список продукции отображается в соответствии с макетом 0.5
У каждой продукции в списке отображается изображение 0.3
При отсутствии изображения отображается картинка-заглушка из ресурсов 0.3

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

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

<ListView
    Grid.Row="1"
    Grid.Column="1"
    ItemsSource="{Binding ProductList}"
>
    <!-- сюда потом вставить ListView.ItemTemplate -->
</ListView>

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

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

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

        </Border>
    </DataTemplate>
</ListView.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 ImagePreview,TargetNullValue={StaticResource defaultImage}}" />

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

Вычисляемое поле можно добавить в сгенерированный класс Product (файл Models/Product.cs), но этот файл может быть перезаписан при повторном реконструировании БД, поэтому лучше создавать модифицированные классы в другом месте. Классы в C# могут быть описаны в нескольких файлах (главное чтобы они были в одном namespace), для этого используется ключевое слово partial:

На демо экзамене есть критерии оценки за логическую и файловую структуру, поэтому куда попало классы писать не надо. Создайте каталог Classes и в нём класс Product. В созданном классе поменяйте namespace (напоминаю, оно должно быть таким же, как у модели) и добавьте в класс Product вычисляемые поля:

namespace WpfApp3.Models
{
    public partial class Product
    {
        public Uri ImagePreview
        {
            get
            {
                var imageName = Environment.CurrentDirectory + (Image ?? "");
                return System.IO.File.Exists(imageName) ? new Uri(imageName) : null;
            }
        }

        public string TypeAndName
        {
            get
            {
                // обратите внимание, мы читаем свойство Title виртуального поля ProductType
                return ProductType?.Title + " | " + Title;
            }
        }
    }
}
  1. Файлы подгружаемые с диска должны быть в формате Uri, иначе программа ищет их в ресурсах исполняемого файла
  2. К имени файла добавляю путь к текущему каталогу
  3. Если такого файла нет, то возвращаю null, в этом случае срабатывает параметр привязки TargetNullValue - отображать изображение по-умолчанию.
  4. Изображение по-умолчанию задается в ресурсах окна (первый элемент в теге Window)

    <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 TypeAndName}"/>

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

    <TextBlock 
        Text="{Binding MaterialString}"/>
</StackPanel>

Вообще выводимый текст можно форматировать, но чтобы не запоминать лишних сущностей можно нарисовать ещё один геттер TypeAndName (в том же классе Product)

Артикул выводится как есть

Если мы сейчас попробуем запустить наше приложение, то получим исключение (что-то с NULL). Дело в том, что по-умолчанию в модель загружаются данные только текущей таблицы (Product), а виртуальное свойство ProductType остаётся не заполненным. Для того, чтобы считать связанные данные, нужно при чтении данных использовать метод Include:

using (var context = new esmirnovContext())
{
    ProductList = context.Products
        .Include(product => product.ProductType)
        .ToList();
}

На данный момент приложение выглядит примерно так

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

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

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

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

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

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

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

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

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel 
            HorizontalAlignment="Center" />
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

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

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

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

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

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

<ListView
    ItemsSource="{Binding ProductList}"
    x:Name="ListView"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel 
                HorizontalAlignment="Center" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>

    <!--ListView.ItemContainerStyle>
        <Style 
            TargetType="ListViewItem">
            <Setter 
                Property="HorizontalContentAlignment"
                Value="Stretch" />
        </Style>
    </-->
    
    
    <ListView.ItemTemplate>
        ...
    </ListView.ItemTemplate>
</ListView>    
Создание подключения к БД MySQL. Получение данных с сервера. Содержание Пагинация, сортировка, фильтрация, поиск