Переглянути джерело

пагинация и т д

Евгений Колесников 3 роки тому
батько
коміт
3eb5fbf586
3 змінених файлів з 427 додано та 6 видалено
  1. 2 2
      articles/cs_layout2.md
  2. 413 0
      articles/cs_pagination2.md
  3. 12 4
      readme.md

+ 2 - 2
articles/cs_layout2.md

@@ -3,7 +3,7 @@
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
+<a href="../articles/cs_pagination2.md">Пагинация, сортировка, фильтрация, поиск
 </a></td><tr></table>
 
 # Вывод данных согласно макету (ListView, Image).
@@ -335,5 +335,5 @@ foreach (var item in ProductMaterials)
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
+<a href="../articles/cs_pagination2.md">Пагинация, сортировка, фильтрация, поиск
 </a></td><tr></table>

+ 413 - 0
articles/cs_pagination2.md

@@ -0,0 +1,413 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_layout2.md">Вывод данных согласно макету (ListView, Image)
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/cs_coloring.md">Подсветка элементов по условию. Дополнительные выборки.
+</a></td><tr></table>
+
+Продолжаем реализовывать макет
+
+* [Пагинация](#пагинация)
+* [Сортировка](#сортировка)
+* [Фильтрация](#фильтрация)
+* [Поиск](#поиск)
+
+# Пагинация
+
+>В случае если в базе более 20 продуктов, то вывод должен осуществляться постранично (по 20 продуктов на страницу). Для удобства навигации по страницам необходимо вывести список их номеров (как на макете) с возможностью перехода к выбранной странице, а также предусмотреть переходы к предыдущей и следующей страницам.
+>
+>![](../img/product_list_layout.jpg)
+
+Критерий | Баллы
+---------|:---:
+Данные выводятся постранично | 1
+Выводится по 20 записей на странице | 0.2
+Выводится список номеров страниц | 0.5
+Реализован переход на выбранную в списке страницу | 0.3
+Присутсвует возможность перемещаться на предыдущую и следующую страницу | 0.5 
+**Итого** | **2.5**
+
+## Постраничный вывод данных
+
+Тут всё просто. Нам в любом случае придется делать геттер для фильтрованного списка продукции. Сразу в этом геттере и сделаем выборку данных порциями. Для этого используются LINQ-запросы **Skip(N)** (пропустить) и **Take(N)** (получить), где N - количество пропускаемых и выбираемых элементов соответсвенно.
+
+```cs
+// КЛАСС ГЛАВНОГО ОКНА
+
+// тут у нас будет храниться полный список продукции
+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();
+    }
+}
+```
+
+## Динамический вывод номеров страниц 
+
+В принципе можно руками в разметке нарисовать эти элементы, и может даже эксперты не обратят на это внимания. Но рассмотрим всё-таки правильный вариант.
+
+1. В разметку страницы под **ListView** добавьте **пустой** именованный **StackPanel** (горизонтальный с выравниванием по правому краю)
+
+    ```xml
+    <StackPanel 
+        x:Name="Paginator"
+        Margin="5"
+        Grid.Row="2" 
+        Grid.Column="1"
+        HorizontalAlignment="Right" 
+        Orientation="Horizontal"/>
+    ```
+
+2. Теперь в сеттере списка продукции динамически создадим текстовые блоки
+
+    Таким образом, при любом изменении списка продукции будут перерисовываться и номера страниц
+
+    ```cs
+    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;
+    }
+    ```
+
+3. И реализуем обработчик нажатия мыши на номерах страниц
+
+    ```cs
+    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**
+
+В прошлом году мы делали выбор сортировки радио-кнопками, но такой вариант подходит только если сортировка по одному критерию, а у нас их три...
+
+1. Создаем массив со списком типов сортировок
+
+    ```cs
+    public string[] SortList { get; set; } = {
+        "Без сортировки",
+        "название по убыванию",
+        "название по возрастанию",
+        "номер цеха по убыванию",
+        "номер цеха по возрастанию",
+        "цена по убыванию",
+        "цена по возрастанию" };
+    ```
+
+2. В разметке (основной Grid) добавляем контейнер для управляющих элементов (WrapPanel) и в него выпадающий список (**ComboBox**)
+
+    ```xml
+    <WrapPanel
+        Grid.Column="1" 
+        Orientation="Horizontal" 
+        ItemHeight="50">
+        
+        <ComboBox
+            Name="SortTypeComboBox"
+            SelectedIndex="0"
+            VerticalContentAlignment="Center"
+            MinWidth="200"
+            SelectionChanged="SortTypeComboBox_SelectionChanged"
+            ItemsSource="{Binding SortList}"/>
+    </WrapPanel>    
+    ```
+
+3. Реализуем обработчик выбора из списка
+
+    Запоминаем ИНДЕКС выбранного элемента
+
+    ```cs
+
+    private int SortType = 0;
+
+    private void SortTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        SortType = SortTypeComboBox.SelectedIndex;
+        Invalidate();
+    }
+    ```
+
+4. И дорабатываем геттер списка продукции
+
+    ```cs
+    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**
+
+Список типов продукции мы можем получить из базы данных.
+
+1. Создаем список продукции, заполняем его данными из базы (в конструкторе главного окна, там же где получали список продукции) и добавляем в начало пункт "Все типы продукции"
+
+    ```cs
+    public List<ProductType> ProductTypeList { get; set; }
+
+    ...
+
+    ProductTypeList = context.ProductTypes.ToList();
+    ProductTypeList.Insert(0, new ProductType { Title = "Все типы продукции" });
+    ```
+
+2. В разметке в панель элементов управления (WrapPanel) добавляем выпадающий список
+
+    ```xml
+    <ComboBox
+        Width="150"
+        x:Name="ProductTypeFilter"
+        SelectedIndex="0"
+        SelectionChanged="ProductTypeFilter_SelectionChanged"
+        ItemsSource="{Binding ProductTypeList}">
+    </ComboBox>
+    ```
+
+    Элементами списка являются не строки, а объекты. В прошлом году я показывал как делать шаблон элемента списка, но как мне кажеться шаблон здесь излишен (его имеет смысл использовать если кроме названия выводится ещё что-то)
+
+    Для преобразования объекта в строку есть метод *Object.ToString()*, и так как все объекты являются потомками класса **Object**, то нам достаточно в модели **ProductType** перегрузить это метод:
+
+    ```cs
+    public override string ToString() {
+        return Title;
+    }
+    ```
+
+3. Реализуем обработчик выбора элемента фильтра    
+
+    ```cs
+    private int ProductTypeFilterId = 0;
+
+    private void ProductTypeFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        // запоминаем ID выбранного типа
+        ProductTypeFilterId = (ProductTypeFilter.SelectedItem as ProductType).ID;
+        Invalidate();
+    }
+    ```
+
+4. И опять дорабатываем геттер списка продукции
+
+    ```cs
+    ...
+    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**
+
+1. В разметке в панель элементов управления (WrapPanel)  добавляем текстовое поле для ввода строки поиска
+
+    ```xml
+    <TextBox
+        Width="200"
+        VerticalAlignment="Center"
+        x:Name="SearchFilterTextBox" 
+        KeyUp="SearchFilterTextBox_KeyUp"/>
+    ```
+
+2. В коде окна запоминаем вводимую строку
+
+    ```cs
+    private string SearchFilter="";
+    private void SearchFilterTextBox_KeyUp(object sender, KeyEventArgs e)
+    {
+        SearchFilter = SearchFilterTextBox.Text;
+        Invalidate();
+    }
+    ```
+3. И снова правим геттер списка продукции
+
+    ```cs
+    // ищем вхождение строки фильтра в названии и описании объекта без учета регистра
+    if (SearchFilter != "")
+        Result = Result.Where(
+            p => p.Title.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
+                p.Description?.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0
+        );
+    ```
+
+---
+
+В принципе всё работает, но список страниц формируется по полному списку продукции и не учитывает фильтр и поиск - перенесём этот код из сеттера в геттер. 
+
+Ещё одна засада в том, что если мы находимся на последней странице и включаем фильтр, то сдвиг уходит за границы массива и отображается пустой список - т.е. перед выводом списка надо проверять валидность старицы
+
+Итоговый список продукции должен выглядеть примерно так:
+
+```cs
+public IEnumerable<Product> ProductList {
+    get {
+        var Result = _ProductList;
+
+        // фильтр по типу продукции
+        if (ProductTypeFilterId > 0)
+            Result = Result.Where(i => i.ProductTypeID == ProductTypeFilterId);
+
+        // поиск
+        // ищем вхождение строки фильтра в названии и описании объекта без учета регистра
+        if (SearchFilter != "")
+            Result = Result.Where(
+                p => p.Title.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
+                        p.Description?.IndexOf(SearchFilter, StringComparison.OrdinalIgnoreCase) >= 0
+            );
+
+        // сортировка
+        switch (SortType)
+        {
+            // сортировка по названию продукции
+            case 1:
+                Result = Result.OrderBy(p => p.Title);
+                break;
+            case 2:
+                Result = Result.OrderByDescending(p => p.Title);
+                break;
+                // остальные сортировки реализуйте сами
+
+        }
+
+        // пагинатор
+        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();
+    }
+}
+```
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_layout2.md">Вывод данных согласно макету (ListView, Image)
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/cs_coloring.md">Подсветка элементов по условию. Дополнительные выборки.
+</a></td><tr></table>

+ 12 - 4
readme.md

@@ -170,7 +170,9 @@ https://office-menu.ru/uroki-sql Уроки SQL
 1. ~~Практическая работа «Обоснование выбора средств проектирования информационной системы»~~
 1. ~~Практическая работа «Описание бизнес-процессов заданной предметной области»~~
 
-## ~~Тема 5.1.2. Система обеспечения качества информационных систем~~
+<details>
+
+~~<summary><h2>Тема 5.1.2. Система обеспечения качества информационных систем</h2></summary>~~
 
 ### ~~Лекции~~
 
@@ -195,7 +197,11 @@ https://office-menu.ru/uroki-sql Уроки SQL
 3. ~~Практическая работа «Разработка требований безопасности информационной системы»~~
 4. ~~Практическая работа «Реинжиниринг бизнес-процессов методом горизонтального и/или вертикального сжатия»~~
 
-## ~~Тема 5.1.3. Разработка документации информационных систем~~
+</details>
+
+<details>
+
+~~<summary><h2>Тема 5.1.3. Разработка документации информационных систем</h2></summary>~~
 
 ### ~~Лекции~~
 
@@ -220,13 +226,15 @@ https://office-menu.ru/uroki-sql Уроки SQL
 1. ~~Практическая работа «Разработка руководства пользователя программного средства по индивидуальному заданию»~~
 1. ~~Лабораторная работа «Изучение средств автоматизированного документирования»~~
 
+</details>
+
 ## Тема 5.1.4. C# и MySQL.
 
 1. [Создание подключения к БД MySQL. Получение данных с сервера.](./articles/cs_mysql_connection3.md)
 
-1. [Вывод данных согласно макету (ListView, Image). Вывод данных плиткой.](./articles/cs_layout2.md)<!-- сюда же добавить разметку "плиткой" -->
+1. [Вывод данных согласно макету (ListView, Image). Вывод данных плиткой.](./articles/cs_layout2.md)
 
-1. [Пагинация, сортировка, фильтрация, поиск](./articles/cs_pagination.md)<!-- datepicker -->
+1. [Пагинация, сортировка, фильтрация, поиск](./articles/cs_pagination2.md)<!-- datepicker -->
 
 1. [Подсветка элементов по условию. Дополнительные выборки. Массовая смена цены продукции.](./articles/cs_coloring.md)