Як зробити власний складовий елемент управління (composite control) в winforms, записна

Завдання: зробити нестандартний елемент управління (control) для Windows Froms. Який саме? Є кілька різних варіантів, темою цієї статті буде власний DateTimePicker, здатний працювати з порожніми датами. Як порожніх дат буде активно використовуватися описаний раніше клас DatePlus. Що ми хочемо? По суті справи нам потрібен Masked TextBox під введення дати, кнопка для виклику спливаючого календаря і ряд службових функцій - упаковані в одну просту оболонку. Плюс на кнопці повинна бути стрілка, що розгортається вгору або вниз в залежності від наявності календаря.

Добрий MSDN каже, що нестандартні управлятори бувають трьох типів:

- складові (composite), що поєднують кілька вже існуючих, робляться наследованеім від UserControl
- розширені, додають щось нове в існуючий control, робляться успадкуванням від соотвествующего класу
- нестандартні (custom), в яких переробляється отрисовка на екран

Очевидно, що нам потрібен складовий елемент управління. Останній шматочок теорії перед переходом до коду - з точки зору операційної системи кожен елемент Windows Forms є окреме вікно. Тобто у нас є головне вікно - форма, всередині якого сидять вікна поменше, всередині яких можуть сидіти ще більш дрібні вікна (наприклад GroupBox і кілька кнопок всередині).

У студії ми можемо просто додати в проект "Призначений для користувача елемент управління" і отримати дизайнер, дуже схожий на дизайнер звичайної форми і кинути на нього MaskedTextBox, кнопку і календар. По кліку на кнопці повинен з'являтися і зникати календар - домогтися цього можна простим зміною розміру control'а.

Тут з'являється перша проблема - стандартний календар не виходить за межі батьківської форми. Тобто якщо ми розташуємо наш чудовий датавзятор поруч з нижнім або правим краєм форми, календар буде обрізане. Хочеться, щоб він красиво ширяв у віртуальному повітрі подібно спливаючого меню. Кількома на кнопку - з'являється, натискаємо на кнопку або поза нею - зникає. Ось в цих співах собака і зарита.

На Stackoverflow нам радять успадковуватися від базового елементу спливаючого меню ToolStripDropDown. Все чудово спливає і вилазить, але виникає маса проблем з автоматично закриттям спливаючого меню - воно заховано десь дуже глибоко в надрах системи. Відстежувати його треба щоб коректно відпрацьовувати ручне відкриття і закриття при кліці на кнопку. Автозакритіем поводиться дуже дивно і нелогічно, останньою краплею для мене стало те, що кліці на кнопку воно відбувається то раніше то пізніше власне події кліка на кнопку. У підсумку я плюнув на збочений код обходу всіх цих глюків і намалював в дизайнера міні-форму з календарем всередині, яку вже і показую / закриваю по кліку.

Як зробити власний складовий елемент управління (composite control) в winforms, записна

Але куди саме це форму виводити? Адже розташування нашого управлятора ми заздалегідь не знаємо. Координати для виведення форми доведеться перераховувати з відносних кординат всередині батьківського вікна для контрола і назад.

Автозакритіем імітується дуже просто - вішаємо виклик Hide на подію Deactivate

Так само на варто забувати, що в MonthCalendar за замовчуванням вибирається діапазон дат, а нам потрібна тільки одна - при виборі дати діапазон треба схлопивается. Зручніше за все робити це зсередини форми.

Відмінно. Все не просто спливає і вилазить, але ще і відкривається-закривається. Починаємо тестувати з різним розміром шрифту на формі і стикаємося з ще одним цікавим глюком - MaskedTextBox масштабується непропорційно їх вмісту. Простіше кажучи при збільшенні розміру шрифту рядок вмісту перестає в нього влазити і обрізається (можна звичайно прокрутити до кінця курсором). Лікується цей глюк і зовсім несподіваним способом - встановленням прозорого фонового кольору загального UserControl-контейнера.

Ще один глюк можна буде побачити прямо в дизайнера - при копіюванні елементу через Ctrl + перетягування мишею базовий контейнер нашого елементу управління буде розтягуватися. Крім того його складно позиціонувати щодо інших елементів. Лікуємо наступним чином:

1. Обнуляємо в внутрішніх елементів властивість Margin редагуємо Location щоб поставити їх впритул до кордонів і підтискаємо сам UserControl

2. Ставимо AutoScaleMode в Front, AutoSize -> true, AutoSizeMode -> GrowAndShrink

Останній глюк пов'язаний з тим, що календар у нас буде перебувати в окремій формі і розмір шрифту не буде передаватися, відповідно розмір календаря не буде змінюватися услід за розміром самого елемента управління і базової форми. Задати його в конструкторі ми не зможемо, тому що в наш управлятор розмір шрифту прийде пізніше. Саме просто - задавати його при виведенні календаря на екран.

Що нам залишилося? Стрілка на кнопці! Доведеться міняти картинку під час виконання програми і найпростіше це зробити, додавши в елемент управління ImageList через дизайнер і там же завантажити картинки. Код для зміни тривіальний.

Чим більше свободи, тим більше проблем. Якщо користувач вводить дату текстом, то він може ввести її неправильно! Причому встає окреме питання про те, що треба вважати правильним - наприклад чи можна користувачу вводити дату з майбутнього? Іноді можна іноді не можна. Тому має сенс ввести окремий параметр для регулювання введення дати з майбутнього і вивести його в панель властивостей дизайнера VisualStudio

Додати функцію для перевірки дати на правильність.

І нарешті коректно обробити проставлення дати до календаря, враховуючи що діапазон дат календаря теж обмежений і якщо дата вилазить за його межі, то в календар її ставити не треба

Введення неправильного значення треба відстежити і розстріляти переконати користувача не робити таких гидот. Щодо надійне відстеження виходу користувача з MaskedTextBox мені вдалося отримати відстеженням подій Validated і MouseLeave. Вони нерідко проісхолдя один за одним, так що видимість вікна з повідомленням про помилку теж треба відстежувати, щоб не виводити вікно два рази.

Якщо ж неправильна рядок все-таки введена вольовим рішенням віддається порожня дата.

Щоб зібрати всі разом приведу тестовий проект WinForms з усіма вищеописаними класами, зібраними в окремий dll і тестової формою

Як зробити власний складовий елемент управління (composite control) в winforms, записна

Схожі статті