Редагування форм у .NET MAUI: чернетки, валідація та захист незбережених змін

Як відокремити чернетку форми від збережених даних, визначати незбережені зміни та обробляти помилки без втрати введеного. Практична схема станів для екранів редагування у .NET MAUI.

.NET MAUI: чернетки форм і незбережені зміни
On this page
  1. Відокремте чернетку від збереженого об’єкта
  2. Визначайте зміни за значеннями
  3. Перевіряйте введення без передчасних помилок
  4. Побудуйте збереження як перехід стану
  5. Контролюйте вихід і відновлення чернетки

Щоб форма у .NET MAUI не втрачала введені дані й не змінювала збережений об’єкт до натискання «Зберегти», редагуйте окрему чернетку, порівнюйте її з початковим знімком і явно керуйте переходами між станами. Прив’язування полів безпосередньо до об’єкта зі списку виглядає простіше, але тоді кнопка «Скасувати» вже потребує відновлення даних, а інші екрани можуть побачити незавершені правки.

Розгляньмо редактор контактної особи з іменем, електронною адресою та приміткою. Та сама схема підходить для налаштувань, адрес доставки й карток обладнання. Найважливіше тут — визначити, що саме означають «змінено», «збережено» та «скасовано», перш ніж пов’язувати кнопки з командами ViewModel.

Відокремте чернетку від збереженого об’єкта

Редактору потрібні три різні представлення даних: останній підтверджений об’єкт, початковий знімок поточного сеансу та змінювана чернетка. Підтверджений об’єкт належить прикладній моделі. Знімок потрібний для порівняння. Чернетка містить те, що користувач зараз бачить у полях, включно з неповними або тимчасово некоректними значеннями.

Це особливо помітно на числовому полі. Порожній рядок або один знак мінуса ще не є готовим числом, але може бути нормальним проміжним введенням. Якщо чернетка одразу вимагає коректне доменне значення, редактор починає боротися з користувачем. Зберігайте текстове представлення там, де воно потрібне для введення, а перетворення виконуйте на визначеній межі.

Копія має бути незалежною

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

Початковий знімок, навпаки, має залишатися незмінним протягом редагування. Простий варіант для текстових полів — окремий тип значень:

public sealed record ContactValues(
    string Name,
    string Email,
    string Note);

public sealed class ContactDraft
{
    public string Name { get; set; } = "";
    public string Email { get; set; } = "";
    public string Note { get; set; } = "";

    public ContactValues ToValues() => new(
        Name.Trim(),
        Email.Trim(),
        Note);
}

Це модель даних, а не готова реалізація сповіщень для XAML. Властивості, до яких прив’язаний інтерфейс, повинні повідомляти про зміни через механізм сповіщень ViewModel. Генерування таких властивостей розглянуто в матеріалі про CommunityToolkit.Mvvm. Незалежно від способу сповіщення, початковий об’єкт не повинен змінюватися під час набору тексту.

Визначайте зміни за значеннями

Поширена помилка — встановити IsDirty = true після першого редагування й більше його не скидати. Якщо користувач змінив ім’я, а потім повернув попереднє, незбережених змін уже немає. Прапорець взаємодії з полем може бути корисним для валідації, але він відповідає на інше запитання.

Для наведеної моделі перевірка означає порівняння draft.ToValues() із початковим ContactValues. У цьому випадку record порівнює значення трьох рядків. Початковий знімок потрібно створювати за тими самими правилами нормалізації. Інакше форма може вважатися зміненою одразу після відкриття.

Нормалізація є продуктовим рішенням. У прикладі крайні пробіли імені та адреси не зберігаються, а примітка залишається точною. Не варто автоматично обрізати кожен рядок або змінювати регістр усієї електронної адреси. Спочатку визначте допустимі перетворення для конкретного поля, потім використовуйте їх узгоджено.

Колекції потребують власного порівняння

Якщо знімок містить список телефонів, самого record недостатньо для бажаного порівняння вмісту: рівність вкладеного значення залежить від його типу. Визначте, чи важливий порядок елементів, як ідентифікується кожен телефон і чи відрізняються дублікати. Список пріоритетних контактів і невпорядкований набір міток мають різну семантику.

Для невеликої форми порівняння після кожної зміни зазвичай просте й прозоре. Для великого редактора можна обчислювати зміни за окремими групами полів. Проте обраний спосіб має правильно обробляти повернення до попереднього значення, додавання та подальше видалення нового елемента, а також відновлення початкового порядку.

Після зміни поля ViewModel повинна оновити не лише його відображення, а й залежні властивості та доступність команд. Сповіщення про Name саме собою не повідомляє інтерфейсу, що обчислювана властивість IsDirty змінилася. Це потрібно передбачити у вибраному механізмі прив’язування.

Перевіряйте введення без передчасних помилок

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

Для обов’язкового імені достатньо перевірити непорожнє нормалізоване значення. Для адреси електронної пошти локальна перевірка може виявити очевидну помилку формату, але не підтверджує існування скриньки чи її належність користувачеві. Повідомлення має відповідати тому, що справді перевірено.

Відокремте наявність помилки від її показу

Порожнє обов’язкове поле може бути некоректним від моменту відкриття нової форми. Це не означає, що його одразу потрібно позначати як помилку. Поширена схема — показувати повідомлення після завершення взаємодії з полем або після спроби зберегти. Для цього стан «поле відвідано» зберігають окремо від стану «дані змінено».

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

Асинхронна перевірка, наприклад доступності ідентифікатора, потребує захисту від застарілих відповідей. Результат для попереднього тексту не повинен перекривати результат для нового. Пов’язуйте відповідь із конкретним значенням або номером перевірки. Навіть успішна відповідь не резервує значення: остаточне рішення все одно приймається під час збереження.

Побудуйте збереження як перехід стану

Обробник «Зберегти» повинен працювати з конкретним знімком чернетки. Послідовність така: створити нормалізовані значення, перевірити їх, зафіксувати початок операції, виконати збереження та обробити результат. Прапорець зайнятості потрібно встановити до першого очікування асинхронної операції, щоб повторне натискання не запустило ще одну операцію.

СтанПоведінка редактора
Без змінВихід не потребує попередження про втрату правок.
Є зміниДоступне збереження або показ причин, які йому заважають.
Збереження триваєПовторний запуск заблокований; діють визначені правила редагування та виходу.
Збереження не вдалосяЧернетка залишається доступною разом із повідомленням про помилку.
Збереження підтвердженоПідтверджений результат стає новою основою для порівняння.

Визначте поведінку під час очікування

Найпростіший варіант — тимчасово заборонити редагування полів до завершення збереження. Тоді підтверджений результат можна застосувати до чернетки й початкового знімка одночасно. Якщо поля залишаються доступними, користувач може встигнути зробити нові правки. У такій схемі не можна безумовно замінити чернетку відповіддю сервісу: потрібне окреме узгодження надісланого знімка, відповіді та поточних значень.

Підтверджений результат важливіший за копію запиту. Сервіс може нормалізувати дані, призначити ідентифікатор або повернути нову ревізію. Саме результат успішної операції має оновити спільну модель і початковий знімок. Якщо форма закривається після збереження, екран списку також повинен отримати підтверджені значення.

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

Якщо один запис редагують із кількох пристроїв, разом із початковими даними варто зберігати їхню ревізію. Сервіс може відхилити оновлення застарілої версії. У такому разі редактор має зберегти локальну чернетку й запропонувати перегляд актуальних даних. Автоматичне повторення старого запиту не розв’язує конфлікт.

Контролюйте вихід і відновлення чернетки

Вихід із редактора може починатися кнопкою «Назад», системною дією, переходом до іншого розділу або програмною навігацією. Для всіх контрольованих шляхів потрібне однакове рішення: якщо змін немає, дозволити перехід; якщо вони є, запропонувати зберегти, відкинути або продовжити редагування. Конкретні точки підключення залежать від навігації застосунку; їхню структуру описує посібник із маршрутів .NET MAUI Shell.

Варіант «Зберегти й вийти» має чекати на підтверджений успіх. Помилка валідації або збереження залишає користувача в редакторі. Варіант «Відкинути» видаляє чернетку, а не намагається виконати зворотні зміни у спільному об’єкті. Під час відкритого діалогу повторні спроби виходу не повинні створювати нові діалоги.

Попередження не замінює відновлення

Операційна система може завершити процес без можливості показати діалог або завершити запис у момент закриття. Для довгих форм потрібне окреме збереження чернетки під час роботи. Його можна виконувати після паузи у введенні та на змістовних межах редагування. Варто уникати залежності від єдиного завершального callback.

Локальна чернетка має містити ідентифікатор запису, облікового запису, версію власного формату та, за потреби, ревізію початкових даних. Це дозволяє відрізнити відновлювані правки від залишку іншого сеансу. Чернетка не повинна автоматично ставати підтвердженим записом або з’являтися в сеансі іншого користувача.

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

Перевірте переходи, а не лише поля

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

Поширені запитання

Чи потрібна окрема чернетка для форми з двома полями?

Так, якщо зміни мають застосовуватися лише після підтвердження. Чернеткою можуть бути дві властивості ViewModel та початковий знімок; окрема складна система для цього не потрібна.

Чи означає IsDirty, що форму можна зберегти?

Ні. Ця ознака означає лише відмінність від початкових значень. Допустимість збереження залежить також від валідації, поточної операції та прикладних правил.

Чи скасовує скасування запиту вже виконане збереження?

Ні. Припинення очікування або запит на скасування не є відкатом підтверджених змін. Якщо результат невідомий, його потрібно з’ясувати через сервіс, перш ніж трактувати операцію як невиконану.

Чим автозбереження чернетки відрізняється від автозбереження запису?

Збереження чернетки забезпечує відновлення незавершеного введення. Збереження запису змінює підтверджені прикладні дані. Інтерфейс має чітко повідомляти, яка саме дія відбулася.

Article changelog (1)
  • — Content revised