# Вывод данных согласно макету (ListView, Image).
>Старый вариант с SQL-запросами находится [тут](./cs_layout.md)
>Напоминаю как выглядит макет списка продукции
>
>
>Критерий | Баллы
>---------|:-----:
>Список продукции отображается в соответствии с макетом | 0.5
>У каждой продукции в списке отображается изображение | 0.3
>При отсутствии изображения отображается картинка-заглушка из ресурсов | 0.3
Для создания такого макета используется элемент **ListView**
В разметке вместо **DataGrid** вставляем **ListView**
```xml
```
Внутри него вставляем шаблон для элемента списка (*ListView.ItemTemplate*): пока у нас только прямоугольная рамка со скруглёнными углами (в этом макете вроде скрулять не надо, возможно осталось от другого шаблона)
```xml
```
Внутри макета вставляем **Grid** из трёх колонок: для картинки, основного содержимого и стоимости.
```xml
```
**В первой** колонке выводим изображение:
```xml
```
Обратите внимание, вместо поля *Image* я вывожу вычисляемое поле *ImagePreview* - в геттере проверяю есть ли такая картинка, т.к. наличие названия в базе не означает наличие файла на диске.
Вычисляемое поле можно добавить в сгенерированный класс **Product** (файл `Models/Product.cs`), но этот файл может быть перезаписан при повторном реконструировании БД, поэтому лучше создавать модифицированные классы в другом месте. Классы в C# могут быть описаны в нескольких файлах (главное чтобы они были в одном namespace), для этого используется ключевое слово **partial**:
На демо экзамене есть критерии оценки за логическую и файловую структуру, поэтому куда попало классы писать не надо. Создайте каталог `Classes` и в нём класс **Common**. В созданном классе поменяйте namespace (напоминаю, оно должно быть таким же, как у модели) и добавьте класс **Product** с вычисляемыми полями:
```cs
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**, иначе программа ищет их в ресурсах исполняемого файла
1. К имени файла добавляю путь к текущему каталогу
1. Если такого файла нет, то возвращаю **null**, в этом случае срабатывает параметр привязки *TargetNullValue* - отображать изображение по-умолчанию.
1. Изображение по-умолчанию задается в ресурсах окна (первый элемент в теге Window)
```xml
```
тут, как раз, указывается путь к изображению в ресурсах (в моём случае в приложении создан каталог `Images` и в него ЗАГРУЖЕН файл)
**Во второй** колонке вывожу основную информацию о продукте: тип + название, аритикул и список материалов.
Так как данные выводятся в несколько строк, то заворачиваю их в **StackPanel** (тут можно использовать и **Grid**, но их и так уже много в разметке)
```xml
```
Вообще выводимый текст можно форматировать, но чтобы не запоминать лишних сущностей можно нарисовать ещё один геттер *TypeAndName* (в том же классе **Product**)
Артикул выводится как есть
Если мы сейчас попробуем запустить наше приложение, то получим исключение (что-то с **NULL**). Дело в том, что по-умолчанию в модель загружаются данные только текущей таблицы (Product), а виртуальное свойство **ProductType** остаётся не заполненным. Для того, чтобы считать связанные данные, нужно при чтении данных использовать метод **Include**:
```cs
using (var context = new esmirnovContext())
{
ProductList = context.Products
.Include(product => product.ProductType)
.ToList();
}
```
На данный момент приложение выглядит примерно так

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

# Вывод данных "плиткой"
Такое задание было на одном из прошлых соревнований WorldSkills, вполне вероятно что появится и на демо-экзамене.
Компоненты **ListBox** и **ListView** по умолчанию инкапсулируют все элементы списка в специальную панель **VirtualizingStackPanel**, которая располагает все элементы по вертикали. Но с помощью свойства **ItemsPanel** можно переопределить панель элементов внутри списка.
Мы будем использовать уже знакомую вам **WrapPanel**:
```xml
```
>Атрибут *HorizontalAlignment* используем, чтобы "плитки" центрировались.

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

Свойство *ItemContainerStyle* уже не нужно и его можно убрать.
Размеры наших элементов по-прежнему зависят от содержимого - тут надо править шаблон **ItemTemplate**.
Итоговая разметка для вывода "плиткой" должна выглядеть примерно так:
```xml
...
```