| Вывод данных согласно макету (ListView, Image) | Содержание | Подсветка элементов по условию. Дополнительные выборки. |
| Критерий | Баллы |
|---|---|
| Данные выводятся постранично | 1 |
| Выводится по 20 записей на странице | 0.2 |
| Выводится список номеров страниц | 0.5 |
| Реализован переход на выбранную в списке страницу | 0.3 |
| Присутсвует возможность перемещаться на предыдущую и следующую страницу | 0.5 |
| Итого | 2.5 |
Тут всё просто. Нам в любом случае придется делать геттер для фильтрованного списка продукции. Сразу в этом геттере и сделаем выборку данных порциями. Для этого используются LINQ-запросы Skip(N) (пропустить) и Take(N) (получить), где N - количество пропускаемых и выбираемых элементов соответсвенно.
// тут у нас будет храниться полный список продукции
private IEnumerable<Product> _ProductList;
// тут мы храним текущую страницу
private int _CurrentPage = 0;
// при смене текущей страницы мы должны перерисовать список (вспоминайте пр INotifyPropertyChanged)
private int CurrentPage {
get {
return _CurrentPage;
}
set {
_CurrentPage = value;
Invalidate();
}
}
// и реализуем геттер и сеттер списка продукции
public IEnumerable<Product> ProductList {
get {
return _ProductList.Skip(20 * CurrentPage).Take(20);
}
set {
_ProductList = value;
Invalidate();
}
}
В принципе можно руками в разметке нарисовать эти элементы, и может даже эксперты не обратят на это внимания. Но рассмотрим всё-таки правильный вариант.
В разметку страницы в правую сетку под ListView добавьте пустой именованный StackPanel (горизонтальный с выравниванием по правому краю)
<StackPanel
x:Name="Paginator"
Margin="5"
Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal"/>
Теперь в сеттере списка продукции динамически создадим текстовые блоки
Таким образом, при любом изменении списка продукции будут перерисовываться и номера страниц
set {
// это остаётся как есть
_ProductList = value;
// очищаем содержимое пагинатора
Paginator.Children.Clear();
// добавляем переход на предыдущую страницу
Paginator.Children.Add(new TextBlock { Text = " < " });
// в цикле добавляем страницы
for (int i = 1; i < _ProductList.Count()/20; i++)
Paginator.Children.Add(
new TextBlock { Text = " "+i.ToString()+" " });
// добавляем переход на следующую страницу
Paginator.Children.Add(new TextBlock { Text = " > " });
// проходимся в цикле по всем сохданным элементам и задаем им обработчик PreviewMouseDown
foreach (TextBlock tb in Paginator.Children)
tb.PreviewMouseDown += PrevPage_PreviewMouseDown;
}
Можно в разметке временно создать один TextBlock, создать для него обработчик нажатия мыши и потом его и использовать.
И реализуем обработчик нажатия мыши на номерах страниц
private void PrevPage_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
switch ((sender as TextBlock).Text)
{
case " < ":
// переход на предыдущую страницу с проверкой счётчика
if (CurrentPage > 0) CurrentPage--;
return;
case " > ":
// переход на следующую страницу с проверкой счётчика
if (CurrentPage < _ProductList.Count() / 20) CurrentPage++;
return;
default:
// в остальных элементах просто номер странцы
// учитываем, что надо обрезать пробелы (Trim)
// и то, что номера страниц начинаются с 0
CurrentPage = Convert.ToInt32(
(sender as TextBlock).Text.Trim() )-1;
return;
}
}
Пользователь должен иметь возможность отсортировать продукцию (по возрастанию и убыванию) по следующим параметрам: наименование, номер производственного цеха и минимальная стоимость для агента. Выбор сортировки должен быть реализован с помощью выпадающего списка.
| Критерий | Баллы |
|---|---|
| Реализована сортировка по названию продукции | 0.2 |
| Реализована сортировка по номеру цеха | 0.2 |
| Реализована сортировка по минимальной стоимости для агента | 0.2 |
| Выбор сортировки реализован с помощью выпадающего списка | 0.5 |
| Сортировка работает в реальном времени | 0.2 |
| Итого | 1.3 |
В прошлом году мы делали выбор сортировки радио-кнопками, но такой вариант подходит только если сортировка по одному критерию, а у нас их три...
Создаем массив со списком типов сортировок
public string[] SortList { get; set; } = {
"Без сортировки",
"название по убыванию",
"название по возрастанию",
"номер цеха по убыванию",
"номер цеха по возрастанию",
"цена по убыванию",
"цена по возрастанию" };
В разметке добавляем элемент выпадающий список (ComboBox)
<WrapPanel
Orientation="Horizontal"
ItemHeight="50">
<Label
Content="Сортировка: "
Margin="10,0,0,0"
VerticalAlignment="Center"/>
<ComboBox
Name="SortTypeComboBox"
SelectedIndex="0"
VerticalContentAlignment="Center"
MinWidth="200"
SelectionChanged="SortTypeComboBox_SelectionChanged"
ItemsSource="{Binding SortList}"/>
</WrapPanel>
Реализуем обработчик выбора из списка
Запоминаем ИНДЕКС выбранного элемента
private int SortType = 0;
private void SortTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SortType = SortTypeComboBox.SelectedIndex;
Invalidate();
}
И дорабатываем геттер списка продукции
get {
var Result = _ProductList;
switch (SortType)
{
// сортировка по названию продукции
case 1:
Result = Result.OrderBy(p => p.Title);
break;
case 2:
Result = Result.OrderByDescending(p => p.Title);
break;
// остальные сортировки реализуйте сами
}
return Result.Skip(20 * CurrentPage).Take(20);
}
Кроме этого, пользователь должен иметь возможность отфильтровать данные по типу продукта. Все типы из базы данных должны быть выведены в выпадающий список для фильтрации. Первым элементом в выпадающем списке должен быть “Все типы”, при выборе которого настройки фильтра сбрасываются.
| Критерий | Баллы |
|---|---|
| Для фильтрации используется выпадающий список с типами продукции | 0.3 |
| Первый элемент в списке "Все типы" | 0.2 |
| Реализована фильтрация | 0.4 |
| Фильтрация работает в реальном времени | 0.2 |
| Итого | 1.1 |
Список типов продукции мы должны загрузить из базы данных. Если у вас ещё нет модели ProductType, то реализуем её и метод для получения этого списка в интерфейсе IDataProvider и поставщике данных (здесь я это расписывать не буду, мы это уже рассматривали в одной из предыдущих лекций, когда обсуждали вариант с получением сводных с помощью LINQ-запросов)
Создаем список продукции, заполняем его данными из базы и добавляем в начало пункт "Всети типы"
public List<ProductType> ProductTypeList { get; set; }
...
ProductTypeList = Globals.DataProvider.GetProductTypes().ToList();
ProductTypeList.Insert(0, new ProductType { Title = "Все типы" });
В разметке в верхнюю панель добавляем выпадающий список
<Label Content="Тип продукции" VerticalAlignment="Center"/>
<ComboBox
Width="150"
x:Name="ProductTypeFilter"
VerticalAlignment="Center"
SelectedIndex="0"
SelectionChanged="ProductTypeFilter_SelectionChanged"
ItemsSource="{Binding ProductTypeList}">
</ComboBox>
Элементами списка являются не строки, а объекты. В прошлом году я показывал как делать шаблон элемента списка, но как мне кажеться шаблон здесь излишен (его имеет смысл использовать если кроме названия выводится ещё что-то)
Для преобразования объекта в строку есть метод Object.ToString(), и так как все объекты являются потомками класса Object, то нам достаточно в модели ProductType перегрузить это метод:
public override string ToString() {
return Title;
}
Реализуем обработчик выбора элемента фильтра
private int ProductTypeFilterId = 0;
private void ProductTypeFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// запоминаем ID выбранного типа
ProductTypeFilterId = (ProductTypeFilter.SelectedItem as ProductType).ID;
Invalidate();
}
И опять дорабатываем геттер списка продукции
...
var Result = _ProductList;
if (ProductTypeFilterId > 0)
Result = Result.Where(
p => p.ProductTypeID == ProductTypeFilterId);
switch (SortType)
...
Пользователь должен иметь возможность искать конкретную продукцию, используя поисковую строку.
Поиск должен осуществляться по наименованию и описанию продукта. Поиск, сортировка и фильтрация должны происходить в реальном времени, без необходимости нажатия кнопки “найти”/”отфильтровать” и т.п. Фильтрация и поиск должны применяться совместно. Параметры сортировки, выбранные ранее пользователем, должны сохраняться и во время фильтрации с поиском.
| Критерий | Баллы |
|---|---|
| Реализован поиск | 0.3 |
| Поиск работает одновременно по нескольким атрибутам | 0.2 |
| Фильтрация и поиск работают одновременно | 0.3 |
| Сортировка работает во время поиска и фильтрации | 0.3 |
| Итого | 1.1 |
В верхнюю панель добавляем текстовое поле для ввода строки поиска
<Label
Content="Поиск"
VerticalAlignment="Center"/>
<TextBox
Width="200"
VerticalAlignment="Center"
x:Name="SearchFilterTextBox"
KeyUp="SearchFilterTextBox_KeyUp"/>
В коде окна запоминаем вводимую строку
private string SearchFilter="";
private void SearchFilterTextBox_KeyUp(object sender, KeyEventArgs e)
{
SearchFilter = SearchFilterTextBox.Text;
Invalidate();
}
И снова правим геттер списка продукции
// ищем вхождение строки фильтра в названии и описании объекта без учета регистра
if (SearchFilter != "")
Result = Result.Where(
p => p.Title.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
p.Description.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0
);
В принципе всё работает, но список страниц формируется по полному списку продукции и не учитывает фильтр и поиск - перенесём этот код из сеттера в геттер.
Ещё одна засада в том, что если мы находимся на последней странице и включаем фильтр, то сдвиг уходит за границы массива и отображается пустой список - т.е. перед выводом списка надо проверять валидность старицы
Итоговый список продукции должен выглядеть примерно так:
public IEnumerable<Product> ProductList {
get {
var Result = _ProductList;
if (ProductTypeFilterId > 0)
Result = Result.Where(i => i.ProductTypeID == ProductTypeFilterId);
switch (SortType)
{
// сортировка по названию продукции
case 1:
Result = Result.OrderBy(p => p.Title);
break;
case 2:
Result = Result.OrderByDescending(p => p.Title);
break;
// остальные сортировки реализуйте сами
}
// ищем вхождение строки фильтра в названии и описании объекта без учета регистра
if (SearchFilter != "")
Result = Result.Where(
p => p.Title.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
p.Description.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0
);
Paginator.Children.Clear();
Paginator.Children.Add(new TextBlock { Text = " < " });
for (int i = 1; i <= (Result.Count() / 20)+1; i++)
Paginator.Children.Add(new TextBlock { Text = " " + i.ToString() + " " });
Paginator.Children.Add(new TextBlock { Text = " > " });
foreach (TextBlock tb in Paginator.Children)
tb.PreviewMouseDown += PrevPage_PreviewMouseDown;
if (CurrentPage > Result.Count() / 20)
CurrentPage = Result.Count() / 20;
return Result.Skip(20 * CurrentPage).Take(20);
}
set {
_ProductList = value;
Invalidate();
}
}
| Вывод данных согласно макету (ListView, Image) | Содержание | Подсветка элементов по условию. Дополнительные выборки. |