# AlfaToad.CustomControls Библиотека пользовательских контролов для **Windows Forms** (.NET Framework 4.8 и .NET 8). Пакет: [NuGet — AlfaToad.CustomControls](https://www.nuget.org/packages/AlfaToad.CustomControls) ## Компоненты | Контрол | Назначение | |---------|------------| | `ScrollableDetailPanel` | Прокручиваемый список строк с фото, полями товара и скидкой | | `ScrollableOrderPanel` | Прокручиваемый список строк заказа | | `BoundDataGridView` | Таблица с привязкой к `DataSet` и подсветкой столбца | --- ## Установка ```bash dotnet add package AlfaToad.CustomControls --version 4.2.1 ``` ```xml ``` --- ## ScrollableDetailPanel — настраиваемые свойства ### Поля данных (имена столбцов в `DataTable`) | Поле на экране | Свойство | Значение по умолчанию | |----------------|----------|----------------------| | Фото | `ImageField` | `Фото` | | Категория | `CategoryField` | `Категория товара` | | Наименование | `NameField` | `Наименование товара` | | Описание | `DescriptionField` | `Описание товара` | | Производитель | `ManufacturerField` | `Производитель` | | Поставщик | `SupplierField` | `Поставщик` | | Цена | `PriceField` | `Цена` | | Единица измерения | `UnitField` | `Единица измерения` | | Количество на складе | `StockField` | `Кол-во на складе` | | Скидка | `DiscountField` | `Действующая скидка` | ### Подписи на экране | Подпись | Свойство | Значение по умолчанию | |---------|----------|----------------------| | Описание | `DescriptionCaption` | `Описание товара:` | | Производитель | `ManufacturerCaption` | `Производитель:` | | Поставщик | `SupplierCaption` | `Поставщик:` | | Цена | `PriceCaption` | `Цена:` | | Единица измерения | `UnitCaption` | `Единица измерения:` | | Количество на складе | `StockCaption` | `Количество на складе:` | | Заголовок столбца скидки | `DiscountCaption` | `Действующая скидка` | ### Изображения | Свойство | Описание | |----------|----------| | `NoImage` | Картинка при отсутствии файла или пустом поле. По умолчанию **не задана** — область фото остаётся пустой. | | `ImagesFolder` | Папка, из которой загружаются файлы по имени из `ImageField`. | ### Размеры строки | Свойство | По умолчанию | |----------|--------------| | `RowHeight` | `160` | | `ImageColumnWidth` | `120` | | `DiscountColumnWidth` | `110` | | `RowSpacing` | `8` (зона сепаратора между строками) | | `OutOfStockRowColor` | голубой `#ADD8E6` (количество на складе ≤ 0) | | `RowSeparatorColor` | `Silver` (линия между записями) | | `ImageLeftPadding` | `8` (отступ фото от левого края) | | `DataSource` | источник данных в дизайнере (DataSet / DataTable / BindingSource) | | `DataMember` | имя таблицы в DataSet | --- ## ScrollableOrderPanel — настраиваемые свойства ### Поля данных | Поле на экране | Свойство | Значение по умолчанию | |----------------|----------|----------------------| | Артикул заказа | `ArticleField` | `Артикул заказа` | | Статус | `StatusField` | `Статус заказа` | | Адрес пункта выдачи | `AddressField` | `Адрес пункта выдачи` | | Дата заказа | `OrderDateField` | `Дата заказа` | | Дата доставки | `DeliveryDateField` | `Дата доставки` | ### Подписи на экране | Подпись | Свойство | Значение по умолчанию | |---------|----------|----------------------| | Артикул | `ArticleCaption` | `Артикул заказа` | | Статус | `StatusCaption` | `Статус заказа` | | Адрес | `AddressCaption` | `Адрес пункта выдачи` | | Дата заказа | `OrderDateCaption` | `Дата заказа` | | Дата доставки | `DeliveryDateCaption` | `Дата доставки` | ### Размеры строки | Свойство | По умолчанию | |----------|--------------| | `RowHeight` | `110` | | `DeliveryColumnWidth` | `140` | --- ## Добавление, редактирование и удаление (CRUD) Оба списка (`ScrollableDetailPanel`, `ScrollableOrderPanel`) поддерживают кнопки **Добавить / Изменить / Удалить** и клик по строке для редактирования. Формы добавления и редактирования открывает **ваше приложение** в обработчиках событий. ### Права по ролям | Свойство | Описание | По умолчанию | |----------|----------|--------------| | `AllowAdd` | Показать кнопку добавления | `false` | | `AllowEdit` | Показать кнопку изменения и открывать редактирование по клику на строку | `false` | | `AllowDelete` | Показать кнопку удаления | `false` | | `IsEditSessionActive` | Блокирует второе окно редактирования, пока `true` | `false` | | `ValidateBeforeDelete` | Функция проверки перед удалением; верните текст ошибки для запрета | `null` | При `false` соответствующая кнопка **скрывается**. При попытке действия без прав показывается `MessageBox` с пояснением. ### События | Событие | Когда срабатывает | |---------|-------------------| | `RecordAddRequested` | Нажата кнопка «Добавить» | | `RecordEditRequested` | Клик по строке или кнопка «Изменить» | | `RecordDeleting` | Перед удалением; установите `Cancel = true` и `DenyReason` для отмены | | `RecordDeleted` | Строка удалена из `DataTable` (вызовите `Update` в БД) | После закрытия формы редактирования вызовите `EndEditSession()`, чтобы снова можно было открыть другое окно. ### Пример для администратора (товары) ```csharp var panel = new ScrollableDetailPanel { Dock = DockStyle.Fill }; panel.AllowAdd = panel.AllowEdit = panel.AllowDelete = userIsAdmin; panel.ShowQueryPanel = userIsManagerOrAdmin; panel.ValidateBeforeDelete = row => { var article = row["Артикул"]?.ToString(); return ProductExistsInOrders(article) ? "Товар присутствует в заказе и не может быть удалён." : null; }; panel.RecordAddRequested += (_, __) => OpenProductForm(null); panel.RecordEditRequested += (_, e) => OpenProductForm(e.Row); panel.RecordDeleted += (_, e) => { tableAdapter.Update(dataSet); panel.RefreshRows(); }; ``` ### Пример для менеджера (заказы — только просмотр) ```csharp ordersPanel.AllowAdd = ordersPanel.AllowEdit = ordersPanel.AllowDelete = userIsAdmin; // менеджер: все три свойства false — кнопки скрыты, клик только выделяет строку ``` ### Обработка исключительных случаев Библиотека показывает `MessageBox` в ситуациях: - действие недоступно для роли; - не выбрана запись для изменения или удаления; - уже открыто окно редактирования (`IsEditSessionActive`); - `ValidateBeforeDelete` или `RecordDeleting` запретили удаление; - ошибка при удалении строки из `DataTable`. --- ## BoundDataGridView — настраиваемые свойства | Свойство | Описание | По умолчанию | |----------|----------|--------------| | `HighlightColumnName` | Столбец для подсветки ячеек | `Действующая скидка` | | `ApplyRowHighlight` | Включить подсветку | `true` | | `Appearance` | Палитра цветов | см. ниже | --- ## AppearancePalette — настройка цветов и шрифтов Общий объект `Appearance` доступен у всех трёх контролов. ### Редактор в дизайнере Visual Studio 1. Выберите контрол на форме. 2. В окне **Свойства** найдите `Appearance`. 3. **Разверните** узел — каждый цвет редактируется стандартным выбором цвета. 4. Нажмите **«…»** рядом с `Appearance` — откроется окно **«Палитра оформления»** со всеми цветами, шрифтами и порогом скидки; кнопка **«По умолчанию»** сбрасывает значения. Изменения сразу применяются к контролу на форме в режиме дизайна. | Свойство | Назначение | По умолчанию | |----------|------------|--------------| | `MainBackground` | Основной фон | `#FFFFFF` | | `SectionBackground` | Фон заголовков таблицы | `#F5DEB3` | | `AccentBackground` | Линии сетки таблицы | `#DEB887` | | `HighlightBackground` | Подсветка скидки > порога | `#FFDEAD` | | `SelectedRowBackground` | Фон выбранной строки списка | `#E8EEF8` | | `PrimaryText` | Цвет текста | `Black` | | `SeparatorText` | Цвет разделителя `\|` | `Gray` | | `BorderColor` | Рамки строк списка | `Black` | | `RowSeparatorColor` | Линия между записями | `Silver` | | `OutOfStockBackground` | Фон строки без товара на складе | `#ADD8E6` | | `StrikethroughPriceColor` | Перечёркнутая цена при скидке | `Red` | | `FinalPriceColor` | Итоговая цена при скидке | `Black` | | `FontFamily` | Шрифт | `Arial` | | `BaseFontSize` | Размер обычного текста | `9` | | `TitleFontSize` | Размер заголовка строки | `10` | | `HighlightThresholdPercent` | Порог подсветки скидки (%) | `17` | --- ## Алгоритм расчёта скидки ### Назначение Вычисление **итоговой цены товара** с учётом действующей скидки и определение **способа отображения** цены на экране (`ScrollableDetailPanel`, `BoundDataGridView`). ### Входные данные | Обозначение | Описание | Источник | |-------------|----------|----------| | `Ц` | Исходная цена товара | столбец `Цена` (`PriceField`) | | `С` | Действующая скидка, % | столбец `Действующая скидка` (`DiscountField`) | | `П` | Порог подсветки скидки, % | `Appearance.HighlightThresholdPercent` (по умолчанию 17) | ### Выходные данные | Обозначение | Описание | |-------------|----------| | `Ц_итог` | Итоговая цена (руб., 2 знака после запятой) | | `Режим` | Способ отображения: «только цена» или «перечёркнутая + итоговая» | | `Подсветка` | Применять ли фон подсветки для ячейки/блока скидки | ### Формула Если `Ц > 0` и `С > 0`: ``` Ц_итог = ОКРУГЛ( Ц × (1 − С / 100) ; 2 ) ``` Иначе: ``` Ц_итог = Ц ``` Округление — **математическое**, до 2 знаков (`MidpointRounding.AwayFromZero`). ### Обозначения блок-схемы (ГОСТ 19.701-90) | Символ | Наименование по ГОСТ | Назначение в схеме | |--------|----------------------|--------------------| | ◯ (овал) | **Терминатор** | Начало / конец алгоритма | | ▱ (параллелограмм) | **Данные** | Ввод исходных значений `Ц`, `С`, `П` | | ▭ (прямоугольник) | **Процесс** | Вычисление, присваивание, форматирование | | ◇ (ромб) | **Решение** | Проверка условия (да / нет) | ### Блок-схема алгоритма (ГОСТ 19.701-90) В диаграмме Mermaid: овал — терминатор, параллелограмм — ввод/вывод, прямоугольник — процесс, ромб — решение. ```mermaid flowchart TD A([НАЧАЛО]) --> B[/Ввод: Ц — цена, С — скидка %, П — порог подсветки/] B --> C{Ц ≤ 0
ИЛИ
С ≤ 0?} C -- ДА --> D[Ц_итог := Ц] C -- НЕТ --> E[Ц_итог := ОКРУГЛ Ц × 1 − С / 100 ; 2] D --> F{С > 0
И
Ц > 0?} E --> F F -- ДА --> G[Режим := перечёркнутая Ц + итоговая Ц_итог] F -- НЕТ --> H[Режим := отображать только Ц] G --> I{С > П?} H --> I I -- ДА --> J[Подсветка := включить HighlightBackground] I -- НЕТ --> K[Подсветка := обычный фон] J --> L[/Вывод: Ц_итог, Режим, Подсветка/] K --> L L --> M([КОНЕЦ]) ``` ### Блок-схема в текстовом виде (ГОСТ 19.701-90) ``` ┌─────────────────────────────────┐ │ НАЧАЛО │ ← терминатор (овал) └───────────────┬─────────────────┘ ▼ ╔═════════════════════════════════╗ ║ Ввод: Ц, С, П ║ ← данные (параллелограмм) ╚═════════════════════════════════╝ ▼ ┌───────────────┐ │ Ц ≤ 0 ИЛИ │ │ С ≤ 0 ? │ ← решение (ромб) └───┬───────┬───┘ ДА │ │ НЕТ ▼ ▼ ┌───────────────┐ ┌───────────────────────────────┐ │ Ц_итог := Ц │ │ Ц_итог := ОКРУГЛ(Ц×(1−С/100);2)│ ← процесс └───────┬───────┘ └───────────────┬───────────────┘ └───────────┬───────────────┘ ▼ ┌───────────────┐ │ С > 0 И │ │ Ц > 0 ? │ ← решение (ромб) └───┬───────┬───┘ ДА │ │ НЕТ ▼ ▼ ┌───────────────────────┐ ┌─────────────────────────┐ │ Режим := перечёркнутая│ │ Режим := только Ц │ │ цена + Ц_итог │ └───────────┬─────────────┘ └───────────┬───────────┘ │ └───────────┬───────────────┘ ▼ ┌───────────────┐ │ С > П ? │ ← решение (ромб) └───┬───────┬───┘ ДА │ │ НЕТ ▼ ▼ ┌───────────────────────┐ ┌─────────────────────────┐ │ Подсветка := ДА │ │ Подсветка := НЕТ │ └───────────┬───────────┘ └───────────┬─────────────┘ └───────────┬───────────────┘ ▼ ╔═════════════════════════════════╗ ║ Вывод: Ц_итог, Режим, ║ ← данные (параллелограмм) ║ Подсветка ║ ╚═════════════════════════════════╝ ▼ ┌─────────────────────────────────┐ │ КОНЕЦ │ ← терминатор (овал) └─────────────────────────────────┘ ``` ### Таблица решений (ветвления) | № | Условие | Действие | |---|---------|----------| | 1 | `Ц ≤ 0` **или** `С ≤ 0` | `Ц_итог = Ц` (скидка не применяется) | | 2 | `Ц > 0` **и** `С > 0` | `Ц_итог = ОКРУГЛ(Ц × (1 − С/100); 2)` | | 3 | `С > 0` **и** `Ц > 0` | Показать старую цену **красной перечёркнутой** и рядом **итоговую чёрную** | | 4 | иначе | Показать только `Ц` | | 5 | `С > П` | Подсветить ячейку/блок скидки цветом `HighlightBackground` | | 6 | `С ≤ П` | Обычный фон | ### Примеры расчёта | Ц, руб. | С, % | Ц_итог, руб. | Отображение | |---------|------|--------------|-------------| | 1000 | 0 | 1000,00 | `1000,00` | | 1000 | 15 | 850,00 | ~~1000,00~~ **850,00** | | 1000 | 20 | 800,00 | ~~1000,00~~ **800,00** + подсветка (20 > 17) | | 0 | 10 | 0,00 | `0,00` (скидка не считается) | | 500 | −5 | 500,00 | `500,00` (отрицательная скидка игнорируется) | ### Соответствие коду библиотеки | Шаг алгоритма | Реализация | |---------------|------------| | Расчёт `Ц_итог` | `ScrollableDetailPanel.CalculateFinalPrice` | | Отображение цены в строке | `ProductListRow` → `PriceLinePanel` | | Подсветка скидки в списке | `Appearance.HighlightThresholdPercent` | | Подсветка в таблице | `BoundDataGridView.OnCellFormatting` | --- ## Примеры использования ### Список товаров с привязкой к DataSet ```csharp using System.Data; using System.Drawing; using AlfaToad.CustomControls.Controls; using AlfaToad.CustomControls.Theme; var panel = new ScrollableDetailPanel { Dock = DockStyle.Fill }; panel.NoImage = Image.FromFile(@"C:\images\no-photo.png"); panel.ImagesFolder = @"C:\images\products"; panel.CategoryField = "CategoryName"; panel.NameField = "ProductName"; panel.DiscountField = "DiscountPercent"; panel.Appearance = new AppearancePalette { MainBackground = Color.White, HighlightBackground = Color.FromArgb(255, 222, 173), BorderColor = Color.Black, HighlightThresholdPercent = 17m }; DataSet dataSet = LoadProducts(); panel.BindDataSet(dataSet, "Products"); ``` **Поведение цены:** если `Действующая скидка` > 0, основная цена отображается красной и перечёркнутой, рядом — итоговая цена чёрным (`цена × (1 − скидка/100)`). **Нет на складе:** если `Кол-во на складе` ≤ 0, вся строка подсвечивается цветом `OutOfStockRowColor`. ### Поиск, фильтрация и сортировка (`ScrollableDetailPanel`) Панель включена по умолчанию (`ShowQueryPanel = true`). Всё применяется **в реальном времени** без кнопки «Найти». | Элемент | Поведение | |---------|-----------| | **Поиск** | Подстрока по всем текстовым столбцам (`string`), одновременно по нескольким полям (логика OR) | | **Сортировка** | Цена ↑/↓, кол-во на складе ↑/↓; выбор сохраняется при поиске и фильтре | | **Поставщик** | Выпадающий список; первый пункт `Все поставщики` сбрасывает фильтр | | Свойство | Описание | |----------|----------| | `ShowQueryPanel` | Показать панель (по умолчанию `true`) | | `AllSuppliersCaption` | Текст сброса фильтра (`Все поставщики`) | | `SearchFields` | Ограничить столбцы поиска (пусто = все текстовые) | | `SupplierField` | Столбец для фильтра поставщика (`Поставщик`) | | `PriceField` / `StockField` | Столбцы для сортировки | ```csharp panel.ShowQueryPanel = userIsManagerOrAdmin; ``` После `TableAdapter.Fill(...)` список и список поставщиков обновляются автоматически. ### Список заказов ```csharp var orders = new ScrollableOrderPanel { Dock = DockStyle.Fill }; orders.Appearance = AppearancePalette.CreateDefault(); orders.Appearance.AccentBackground = Color.BurlyWood; orders.BindDataSet(dataSet, "Orders"); ``` ### Таблица с подсветкой скидки ```csharp var grid = new BoundDataGridView { Dock = DockStyle.Fill }; grid.Appearance = AppearancePalette.CreateDefault(); grid.HighlightColumnName = "Действующая скидка"; grid.ApplyDefaultStyle(); grid.BindDataSet(dataSet, "Products"); ``` ### Смена цветов после создания ```csharp panel.Appearance.MainBackground = ColorTranslator.FromHtml("#FAFAFA"); panel.Appearance.HighlightBackground = ColorTranslator.FromHtml("#FFDEAD"); panel.Appearance.BorderColor = Color.DimGray; panel.ApplyAppearance(); ```