| Предыдущая лекция | Следующая лекция | |
|---|---|---|
| Пагинация, сортировка, фильтрация, поиск | Содержание | Создание, изменение продукции |
В списке продукции необходимо подсвечивать светло-красным цветом те продукты, которые не продавались агентами в последний месяц.
| Критерий | Баллы |
|---|---|
| Реализовано выделение (любым образом) продуктов, которые не продавались агентами в последний месяц | 1.5 |
| Выделение реализовано в виде светло-красной подсветки элемента продукции | 0.5 |
| Итого | 2 |
В самом списке продукции данных о продажах нет. Судя по названиям таблиц данные эти должны быть в таблице ProductSale. Судя по связям этой таблицы, мы должны заполнить ещё таблицы Agent и AgentType.
На демо-экзамене до этого вы вряд-ли дойдёте (хотя критерий достаточно жирный), но в рамках курсовой/дипломной работы реализовать дополнительный функционал надо.
Добавляем типы агентов
Исходных данных для этой и последующих таблиц нет, поэтому вы можете писать туда что угодно (но близко к предметной области). Для типов агентов подойдут "индивидуальный предприниматель" и "Общество с ограниченной ответственностью" (для не обязательных таблиц много данных придумывать не надо, достаточно одной-двух записей)
И так как исходных данных нет, то добавляем данные прямо в таблицы.
Добавляем агента
Тут заполняем только обязательные поля. AgentTypeID смотрим в таблице AgentType.
Создание продаж продукции
Добавляем несколько записей в таблицу ProductSale. Поля AgentID и ProductID смотрим в соответствующих таблицах. Дата продажи заполняется в формате YYYY-MM-DD
Можно в методе получения данных с сервера добавить Include для виртуального списка ProductSales, но я уже упоминал в лекциях, что списки лучше загружать динамически. Мы это сделаем на следующем шаге, при получении цвета ячейки.
Тут элементарно. У элемента рамка (Border) задаем цвет фона:
<Border
BorderThickness="1"
BorderBrush="Black"
Background="{Binding BackgroundColor}"
^^^^^^^^^^^^^^^^^^^^^^^^^
CornerRadius="5">
И добавляем в модель Product геттер BackgroundColor
public string BackgroundColor
{
get
{
// вычисляем дату для сравнения ("сегодня" минус 30 дней)
var compareDate = DateTime.Now.AddDays(-30);
using (var context = new esmirnovContext())
{
// ищем количество продаж, совершённых позже указанной даты
// фильтруя продажи по Id продукта
var saleCount = context.ProductSales
.Where(ps => ps.ProductId==this.Id)
.Count(ps => ps.SaleDate >= compareDate);
// возвращаем цвет, в зависимости от количества продаж
if (saleCount > 0) return "#fff"; // белый
return "#fee"; // розовый
}
}
}
Про цвета: их можно отдавать в формате #RGB. Причём, чем ближе к F, тем светлее (#FFF - белый)
Необходимо добавить возможность изменения минимальной стоимости продукции для агента сразу для нескольких выбранных продуктов. Для этой цели реализуйте возможность выделения сразу нескольких элементов в списке продукции, после чего должна появиться кнопка “Изменить стоимость на ...”. При нажатии на кнопку необходимо отобразить модальное окно с возможностью ввода числового значения, на которое и будет увеличена стоимость выбранных продуктов. По умолчанию в поле должно быть введено среднее значение цены на продукцию для агента. После нажатия кнопки “Изменить” стоимость выделенных продуктов должна быть изменена в базе данных, а также обновлена в интерфейсе.
| Критерий | Баллы |
|---|---|
| Реализована возможность выделения сразу нескольких элементов в списке | 0.2 |
| После выделения элементов в списке появляется кнопка "Изменить стоимость на ..." | 0.3 |
| При нажатии на кнопку отображается модальное окно для изменения цены | 0.1 |
| В модальном окне есть возможность ввода числового значения | 0.1 |
| По умолчанию введено значение средней цены выбранных продуктов | 0.2 |
| Реализована проверка на ввод только числового значения | 0.2 |
| После нажатия кнопки "Изменить" стоимость всех выбранных продуктов изменяется в БД | 0.5 |
| После нажатия кнопки "Изменить" стоимость всех выбранных продуктов обновляется в списке | 0.2 |
| Итого | 1.8 |
Возможность выделения нескольких элементов в ListBox есть, нужно только добавить аттрибут SelectionMode="Multiple". Реализовать событие выбора и сосчитать количество выделенных элементов.
В ListBox нужно добавить название (мы потом будем по нему искать количество выделенных элементов) и обработчик события SelectionChanged
Name="ProductListBox"
SelectionMode="Multiple"
SelectionChanged="ProductListBox_OnSelectionChanged"
Реализуем обработчик ProductListBox_OnSelectionChanged:
public int productsSelectedCount = 0;
private void ProductListBox_OnSelectionChanged(
object? sender,
SelectionChangedEventArgs e)
{
if (ProductListBox != null)
{
productsSelectedCount = ProductListBox. SelectedItems.Count;
}
}
Сначала просто добавляем эту кнопку в разметку (можно в панель управления, можно в левую панель)
<Button
x:Name="CostChangeButton"
IsVisible="True"
Content="Изменить стоимость на..."
/>
Теперь, чтобы видимость кнопки зависела от количества выделенных элементов, мы привязываем атрибут IsVisible к свойству costChangeButtonVisible (дописав аттрибут DataType)
<Button
x:Name="CostChangeButton"
x:DataType="system:Boolean"
IsVisible="{Binding #root.costChangeButtonVisible}"
Content="Изменить стоимость на..."
/>
И реализуем это свойство в коде ОКНА
public string costChangeButtonVisible {
get {
return productsSelectedCount > 1;
}
}
Осталось в обработчик события ProductListBox_OnSelectionChanged вставить вызов метода Invalidate для свойства costChangeButtonVisible
Добавляем кнопке обработчик события клика
<Button
x:Name="CostChangeButton"
x:DataType="system:Boolean"
IsVisible="{Binding #root.costChangeButtonVisible}"
Click="CostChangeButton_OnClick"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Content="Изменить стоимость на..."
/>
Создаем и показываем модальное окно
Вспоминаем, что мы должны соблюдать файловую структуру проекта, т.е. все однотипные объекты распихивать по соответствующим папкам.
Создадим папку Windows и в неё добавим окно:
Название окна должно быть осмысленным и с суффиксом Window. У меня получилось EnterMinCostForAgentWindow
Можно в каталог
Windowsперетащить и главное окно MainWindow.
Опять же, все окна должны иметь нормальные заголовки. В разметке окна поменяйте атрибут Title элемента Window (это надо сделать и для основного окна)
Содержимое у этого окна элементарное (текстовое поле и кнопка):
<StackPanel
Orientation="Vertical" Margin="0,50,0,0">
<TextBox
Name="CostTextBox"
/>
<Button
Content="Изменить"/>
</StackPanel>
В коде этого окна меняем конструктор:
public EnterMinCostForAgentWindow(decimal AvgCost)
^^^^^^^^^^^^^^^
{
InitializeComponent();
CostTextBox.Text = AvgCost.ToString();
}
Во-первых, в конструктор добавляем параметр (средняя цена). Во-вторых, записываем эту цену в текстовое поле.
По условиям задачи мы должны вычислить среднюю цену для выделенных элементов списка (я на вскидку не нашёл как преобразовать IList в IEnumerable, поэтому тупо перебираем список выбранных элементов, считаем сумму и, заодно, собираем список идентификаторов для последующего переопределения цены)
В авалонии окна запускаются асинхронно, поэтому если мы хотим получить результат выполнения диалога (а нам нужна сумма), то нужно использовать конструкцию async/await: метод, в котором нужно ждать выполнения асинхронной задачи должен иметь модификатор async, а перед функцией открытия окна должен быть модификатор await
/*
обработчик события клика по кнопке "Изменить стоимость на..." в главном окне
так как нам нужно дождаться результата ввода, то запускаем асинхронно
*/
async private void CostChangeButton_OnClick(
object? sender,
RoutedEventArgs e)
{
decimal sum = 0;
List<int> idList = new List<int>();
foreach (Product item in ProductListBox.SelectedItems){
sum += item.MinCostForAgent;
idList.Add(item.Id);
}
// создаём окно, передавая ему среднюю цену
var newWindow = new EnterMinCostForAgentWindow(
sum / ProductListBox.SelectedItems.Count);
// показываем МОДАЛЬНОЕ окно и ЖДЕМ результат
var res = await newWindow.ShowDialog<decimal?>(this);
}
Обратите внимание, в угловых скобках после ShowDialog указывается тип результата, возвращаемый окном. Результата может и не быть (хакрыли окно крестиком), поэтому тип нуллабельный.
Проверка на ввод только числового значения.
private void Button_OnClick(
object? sender,
RoutedEventArgs e)
{
try
{
// пробуем сконвертировать в число
decimal Result = Convert.ToDecimal(CostTextBox.Text);
// при присвоении результата свойству DialogResult модальное окно закрывается
Close(Result);
}
catch (Exception)
{
// в авлонии нет MessageBox, поэтому его надо реализовать как обычное окно
(new MessageBox("Стоимость должна быть числом")).ShowDialog(this);
}
}
Запись новой цены в БД и обновление списка.
Редактируем пункт 3, добавляя анализ результата модального окна
...
var res = await newWindow.ShowDialog<decimal?>(this);
if (res != null)
{
using (var context = new esmirnovContext())
{
try
{
// перебираем выделенные продукты
foreach (var id in idList)
{
var product = context.Products.Where(p => p.Id == id).FirstOrDefault();
if (product != null)
{
// меняем сумму для агента
product.MinCostForAgent = res ?? 0;
}
}
// сохраняем изменения
context.SaveChanges();
}
catch (Exception exception)
{
(new MessageBox(exception.Message)).ShowDialog(this);
}
/*
перечитываем список продукции (так как эта операция повторяется,
то лучше её вынести в отдельный метод)
*/
productList = context.Products
.Include(product => product.ProductType)
.Include(product => product.ProductMaterials)
.ToList();
}
}
| Предыдущая лекция | Следующая лекция | |
|---|---|---|
| Пагинация, сортировка, фильтрация, поиск | Содержание | Создание, изменение продукции |