Skip to content

Latest commit

 

History

History
303 lines (225 loc) · 14.3 KB

cs_layout2.md

File metadata and controls

303 lines (225 loc) · 14.3 KB
Предыдущая лекция Следующая лекция
Создание подключения к БД MySQL. Получение данных с сервера. Содержание Пагинация, сортировка, фильтрация, поиск

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

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

Критерий Баллы
Список продукции отображается в соответствии с макетом 0.5
У каждой продукции в списке отображается изображение 0.3
При отсутствии изображения отображается картинка-заглушка из ресурсов 0.3

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

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

<ListBox 
    Grid.Row="1"
    Background="White"
    x:DataType="model:Product"
    ItemsSource="{Binding #root.productList}">
    <!-- сюда потом вставить 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>

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

Авалонии для вывода изображения нужно преобразовать имя файла в объект пиксельной графики: класс Bitmap. Есть два способа это сделать:

  • Конвертер (в принципе ничего сложного, в инете куча примеров, но я не хочу пока нагружать вас лишними сущностями)
  • Вычисляемое свойство. Этот вариант я и буду использовать.
<Image
    Width="64" 
    Height="64"
    Source="{Binding ImageBitmap}" />

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

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

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

namespace AvaloniaApplication1.esmirnov;

public partial class Product
{
    public Bitmap? ImageBitmap
    {
        get
        {
            var imageName = Environment.CurrentDirectory + (Image ?? "");
            // windows лояльно относится к разным слешам в пути, 
            // а вот linux не находит такие файлы, 
            // поэтому меняю обратные слеши на прямые
            imageName = imageName.Replace('\\', '/');

            // если файл существует, то возвращаю его
            // иначе картинку заглушку
            var result = System.IO.File.Exists(imageName) ? 
                new Bitmap(imageName) :
                new Bitmap(Environment.CurrentDirectory+"/picture.png");

            return result;
        }
    }
}

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

Так как данные выводятся в несколько строк, то заворачиваю их в 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)

public string TypeAndName
{
    get
    {
        // обратите внимание, мы читаем свойство TitleType виртуального поля ProductType
        return ProductType?.TitleType + " | " + Title;
    }
}

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

Строка материалов (вычисляемое свойство MaterialString) должна формироваться динамически по таблице связей ProductMaterial, про неё раскажу ниже. Пока пишем заглушку:

public string MaterialString { get; } = "тут будет список используемых  материалов";

В третьей колонке выводим сумму материалов, т.е. опять динамически будем формировать по таблице связей.

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

И пока тоже заглушка:

public string MaterialSum { get; } = "тут будет сумма используемых  материалов";

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

Для того, чтобы считать связанные данные, нужно при чтении данных использовать метод Include (можно несколько раз для нескольких связанных таблиц):

Количество включений не ограничено, но всё сразу лучше не загружать - C# достаточно "умный", чтобы вычислять нужные свойства только при отображении, поэтому строку материалов и сумму можно считать отдельно

Меняем в конструкторе код получения данных:

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

Теперь типы выводятся нормально:

Расчёт материалов

Материалы (название и цену) мы можем взять из таблицы Material, которая связана с продуктами (Product) отношением многие-ко-многим через таблицу ProductMaterial. Массив этих связей мы в продукты уже включили (product.ProductMaterials), осталось выбрать материалы (переписываем заглушки в файле Classes/Product.cs):

private string? _materialString = null;
private double _materialSum = 0;

public string MaterialString
{
    get
    {
        if (_materialString == null)
        {
            using (var context = new esmirnovContext())
            {
                _materialString = "";
                foreach (var item in ProductMaterials)
                {
                    var material = context.Materials
                        .Where(m => m.Id == item.MaterialId).First();

                    _materialString += material?.Title + ", ";

                    _materialSum += Convert.ToDouble(material?.Cost ?? 0) *
                        (item.Count ?? 0);
                }
            }
        }
        return _materialString;
    }
}

public double MaterialSum
{
    get {
        return _materialSum;
    }
}

Что тут происходит?

Во-первых, чтобы каждый раз не пересчитывать данные мы их кешируем:

private string? _materialString = null;
private double _materialSum = 0;

Если материалы продукта вычисляются в первый раз (значение равно null), то происходит реальное чтение из базы, иначе возвращаем ранее вычисленное значение.

Во-вторых, перебираем список материалов и формируем строку и сумму материалов

// перебираем массив материалов продукта 
// (значения из связи ProductMaterials, 
// полученные вместе с основным запросом к базе)
foreach (var item in ProductMaterials)
{
    // ищем материал по его Id
    var material = context.Materials
        .Where(m => m.Id == item.MaterialId)
        .First();

    // формируем строку
    _materialString += material?.Title + ", ";

    // и цену, учитывая количество материалов
    _materialSum += Convert.ToDouble(material?.Cost ?? 0) * (item.Count ?? 0);
}

Теперь отображается всё что требуется по заданию, причём мы не написали ни одного запроса к БД, всё за нас сделал ORM фреймворк.

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

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

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

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

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

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

И ещё нужно поменять ширину второй колонки элемента (у нас стоит "на всё свободное место", вместо этого нужно прописать фиксированное значение)

Получается примерно такое (первая ячейка получилась шире остальных из-за того, что третья колонка имеет ширину "auto" - это поправьте сами)


Предыдущая лекция Следующая лекция
Создание подключения к БД MySQL. Получение данных с сервера. Содержание Пагинация, сортировка, фильтрация, поиск