| Пагинация, сортировка, фильтрация, поиск | Содержание | Создание, изменение продукции |
| Критерий | Баллы |
|---|---|
| Реализовано выделение (любым образом) продуктов, которые не продавались агентами в последний месяц | 1.5 |
| Выделение реализовано в виде светло-красной подсветки элемента продукции | 0.5 |
| Итого | 2 |
В самом списке продукции данных о продажах нет. Судя по названиям таблиц данные эти должны быть в таблице ProductSale. Судя по связям этой таблицы, мы должны заполнить ещё таблицы Agent и AgentType.
На демо-экзамене до этого вы вряд-ли дойдёте (хотя критерий достаточно жирный), но в рамках курсовой/дипломной работы реализовать дополнительный функционал надо.
Добавляем типы агентов
Исходных данных для этой и последующих таблиц нет, поэтому вы можете писать туда что угодно (но близко к предметной области). Для типов агентов подойдут "индивидуальный предприниматель" и "Общество с ограниченной ответственностью" (для не обязательных таблиц много данных придумывать не надо, достаточно одной-двух записей)
И так как исходных данных нет, то добавляем данные прямо в таблицы.
Добавляем агента
Тут заполняем только обязательные поля. AgentTypeID смотрим в таблице AgentType.
Создание продаж продукции
Добавляем несколько записей. Поля 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 ksmirnovContext())
{
// ищем количество продаж, совершённых позже указанной даты
// фильтруя продажи по 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 |
Возможность выделения нескольких элементов в ListView есть, специально её реализовывать не надо. Надо лишь поймать это событие и сосчитать количество выделенных элементов.
В ListView нужно добавить название (мы потом будем по нему искать количество выделенных элементов) и обработчик события SelectionChanged
x:Name="ProductListView"
SelectionChanged="ListView_SelectionChanged"
Реализуем обработчик ListView_SelectionChanged:
public int ProductsSelectedCount = 0;
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ProductsSelectedCount = ProductListView.SelectedItems.Count;
}
Сначала просто добавляем эту кнопку в разметку
<Button
x:Name="CostChangeButton"
Visibility=""
Content="Изменить стоимость на..."
/>
И запоминаем какие значения может прнимать атрибут Visibility
Теперь, чтобы видимость кнопки зависела от количества выделенных элементов, мы привязываем атрибут Visibility к свойству CostChangeButtonVisible
<Button
x:Name="CostChangeButton"
Visibility="{Binding CostChangeButtonVisible}"
Content="Изменить стоимость на..."
/>
И реализуем это свойство в коде ОКНА
public string CostChangeButtonVisible {
get {
if (ProductsSelectedCount > 1) return "Visible";
return "Collapsed";
}
}
Осталось в обработчик события ListView_SelectionChanged вставить вызов метода PropertyChanged для свойства CostChangeButtonVisible
Чтобы не плодить кучу одинаковых методов, я добавил в параметры метода Invalidate имя свойства, которое изменилось
private void Invalidate(string ComponentName = "ProductList") {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(ComponentName));
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ProductsSelectedCount = ProductListView.SelectedItems.Count;
Invalidate("CostChangeButtonVisible");
}
Добавляем кнопке обработчик события клика
<Button
x:Name="CostChangeButton"
Visibility="{Binding CostChangeButtonVisible}"
Click="CostChangeButton_Click"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Content="Изменить стоимость на..."
/>
Создаем и показываем модальное окно
Во-первых, вспоминаем, что мы должны соблюдать файловую структуру проекта, т.е. все однотипные объекты распихивать по соответствующим папкам.
Создадим папку Windows и в неё добавим окно (WPF)
Название окна должно быть осмысленным и с суффиксом Window. У меня получилось EnterMinCostForAgentWindow
Можно в каталог
Windowsперетащить и главное окно MainWindow. Только в этом случае надо в разметке приложения (App.xaml) добавить название каталога:><Application x:Class="mysql.App" > xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" > xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > xmlns:local="clr-namespace:mysql" > StartupUri="Windows/MainWindow.xaml"> > ^^^^^^^^ >... Опять же, все окна должны иметь нормальные заголовки. В разметке окна поменяйте атрибут *Title* элемента **Window** (это надо сделать и для основного окна) Содержимое у этого окна элементарное (текстовое поле и кнопка):xml
<StackPanel Orientation="Vertical" Margin="0,50,0,0"><TextBox Name="CostTextBox" /> <Button Content="Изменить"/> </StackPanel>
В коде этого окна меняем конструктор:
cs public EnterMinCostForAgentWindow(decimal AvgCost)
^^^^^^^^^^^^^^^
{
InitializeComponent();
CostTextBox.Text = AvgCost.ToString();
} ```
Во-первых, в конструктор добавляем параметр (средняя цена). Во-вторых, записываем эту цену в текстовое поле.
По условиям задачи мы должны вычислить среднюю цену для выделенных элементов списка (я на вскидку не нашёл как преобразовать IList в IEnumerable, поэтому тупо перебираем список выбранных элементов, считаем сумму и, заодно, собираем список идентификаторов)
private void CostChangeButton_Click(object sender, RoutedEventArgs e)
{
decimal Sum = 0;
List<int> idList = new List<int>();
foreach (Product item in ProductListView.SelectedItems){
Sum += item.MinCostForAgent;
idList.Add(item.ID);
}
// создаём окно, передавая ему среднюю цену
var NewWindow = new EnterMinCostForAgentWindow(
AvgSum / ProductListView.SelectedItems.Count);
// показываем МОДАЛЬНОЕ окно
NewWindow.ShowDialog();
}
Проверка на ввод только числового значения.
// объявляем публичную переменную из которой
// будем считывать результат
public decimal Result;
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// пробуем сконвертировать в число
Result = Convert.ToDecimal(CostTextBox.Text);
// при присвоении результата свойству DialogResult модальное окно закрывается
DialogResult = true;
}
catch (Exception)
{
MessageBox.Show("Стоимость должна быть числом");
}
}
Запись новой цены в БД и обновление списка.
редактируем пункт 3, добавляя анализ результата
if((bool)NewWindow.ShowDialog())
{
// вся работа с БД у нас в DataProvider-e,
// передаем список идентификаторов и новую цену
Globals.DataProvider.SetAverageCostForAgent(idList, NewWindow.Result);
// для перерисовки списка продукции просто перечитываем его
ProductList = Globals.DataProvider.GetProducts();
}
Описываем метод SetAverageCostForAgent в интерфейсе IDataProvider:
void SetAverageCostForAgent(List<int> ProductIds, decimal NewCost);
И реализуем его в MySQLDataProvider-e:
public void SetAverageCostForAgent(List<int> ProductIds, decimal NewCost)
{
try
{
Connection.Open();
try
{
// вместо значений записываем шаблоны
string Query = @"UPDATE
Product
SET MinCostForAgent=@MinCostForAgent
WHERE ID=@ID";
// перебираем список идентификаторов
foreach (int item in ProductIds)
{
MySqlCommand Command = new MySqlCommand(Query, Connection);
// заменяем шаблоны параметрами
Command.Parameters.AddWithValue("@MinCostForAgent", NewCost);
Command.Parameters.AddWithValue("@ID", item);
// и выполняем запрос
Command.ExecuteNonQuery();
}
}
finally
{
Connection.Close();
}
}
catch (Exception)
{
}
}
| Пагинация, сортировка, фильтрация, поиск | Содержание | Создание, изменение продукции |