При переходе с обычных форм на управляемые разработчики часто сталкиваются со сложностями при интерактивной проверке данных. В обычном приложении достаточно последовательно выполнить проверки в одной процедуре ПередЗаписью(), прерывая выполнение модальными вопросами через Вопрос() или ВвестиСтроку(). В управляемом интерфейсе вызовы диалогов являются асинхронными (ПоказатьВопрос(), ПоказатьВводСтроки()), что требует использования объектов ОписаниеОповещения. Если проверять несколько реквизитов по очереди, код быстро превращается в трудноподдерживаемый каскад вложенных процедур-обработчиков.
Разберем, как правильно организовать архитектуру проверки данных в управляемых формах, избежать каскадов процедур и предотвратить нежелательное закрытие формы при нажатии кнопки «Записать».
Проанализируем типовую ситуацию: в обработчике ПередЗаписью(Отказ, ПараметрыЗаписи) проверяется реквизит. При обнаружении ошибки мы отменяем стандартную запись (Отказ = Истина) и вызываем немодальный диалог через ПоказатьВопрос(). Проверка следующего реквизита не может продолжаться синхронно, так как выполнение текущей процедуры завершается до того, как пользователь нажмет кнопку в диалоге.
В результате проверку второго реквизита приходится дублировать в процедуре завершения первого вопроса. Для трех и более реквизитов это приводит к лавинообразному росту количества процедур вида ПередЗаписьюПредложитьИсправить2йРеквизитВопросЗавершение().
Рассмотрим основные способы решения этой задачи.
Чтобы исключить жесткую связку между проверками, применим шаблон очереди. Выделим логику формирования списка ошибок и отдельную процедуру-диспетчер, которая последовательно извлекает задачи из очереди и открывает соответствующие диалоги.
Посмотрим на пример реализации данного подхода:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Если проверки уже пройдены, не уходим на повторный круг
Если ЭтотОбъект.Свойства.Свойство("ПроверкиПройдены") И ЭтотОбъект.ПроверкиПройдены Тогда
Возврат;
КонецЕсли;
ОчередьПроверок = СформироватьОчередьПроверок();
Если ОчередьПроверок.Количество() > 0 Тогда
Отказ = Истина;
ПараметрыКонтекста = Новый Структура;
ПараметрыКонтекста.Вставить("ОчередьПроверок", ОчередьПроверок);
ПараметрыКонтекста.Вставить("ПараметрыЗаписи", ПараметрыЗаписи);
ВыполнитьСледующуюПроверку(ПараметрыКонтекста);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция СформироватьОчередьПроверок()
Очередь = Новый Массив;
// Проверка артикула
Если Не ПроверитьКорректностьСимволов(Объект.Артикул) Тогда
Очередь.Добавить(Новый Структура("Тип,ИмяРеквизита,Текст", "Вопрос", "Артикул", "В артикуле найдены смешанные символы. Исправить?"));
КонецЕсли;
// Проверка наименования
Если Не ПроверитьКорректностьСимволов(Объект.Наименование) Тогда
Очередь.Добавить(Новый Структура("Тип,ИмяРеквизита,Текст", "Вопрос", "Наименование", "В наименовании найдены смешанные символы. Исправить?"));
КонецЕсли;
Возврат Очередь;
КонецФункции
&НаКлиенте
Процедура ВыполнитьСледующуюПроверку(ПараметрыКонтекста)
Если ПараметрыКонтекста.ОчередьПроверок.Количество() = 0 Тогда
// Все вопросы согласованы, перезапускаем запись объекта
ЭтотОбъект.ПроверкиПройдены = Истина;
ЭтотОбъект.Записать(ПараметрыКонтекста.ПараметрыЗаписи);
ЭтотОбъект.ПроверкиПройдены = Ложь;
Возврат;
КонецЕсли;
ТекущаяПроверка = ПараметрыКонтекста.ОчередьПроверок[0];
ПараметрыКонтекста.ОчередьПроверок.Удалить(0);
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопросПроверки", ЭтотОбъект, ПараметрыКонтекста);
ПоказатьВопрос(Оповещение, ТекущаяПроверка.Текст, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеОтветаНаВопросПроверки(Результат, ПараметрыКонтекста) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
// Выполняем автоисправление или применяем правила
ИсправитьОшибкуВРеквизите(ПараметрыКонтекста);
КонецЕсли;
// Переходим к следующему элементу очереди
ВыполнитьСледующуюПроверку(ПараметрыКонтекста);
КонецПроцедуры
Начиная с версии платформы 8.3.18, в платформе 1С реализованы асинхронные вызовы через ключевые слова Асинх и Ждать. Это позволяет избавиться от объектов ОписаниеОповещения и писать код последовательно в циклах.
Выясним важную особенность: обработчик события формы ПередЗаписью не может быть объявлен с директивой Асинх. Поэтому для применения линейного стиля выносим проверку в отдельную асинхронную клиентскую процедуру:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
Если Не ЭтотОбъект.ПроверкиПройдены Тогда
Отказ = Истина;
ПроверитьРеквизитыИЗаписатьАсинх(ПараметрыЗаписи);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Асинх Процедура ПроверитьРеквизитыИЗаписатьАсинх(ПараметрыЗаписи)
СписокРеквизитов = Новый Массив;
СписокРеквизитов.Добавить("Артикул");
СписокРеквизитов.Добавить("Наименование");
СписокРеквизитов.Добавить("НаименованиеПолное");
Для Каждого ИмяРеквизита Из СписокРеквизитов Цикл
Если СодержитНекорректныеСимволы(Объект[ИмяРеквизита]) Тогда
ТекстВопроса = СтрШаблон("В поле ""%1"" обнаружены ошибки. Исправить автоматически?", ИмяРеквизита);
Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
Объект[ИмяРеквизита] = ИсправитьСимволы(Объект[ИмяРеквизита]);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Устанавливаем флаг, чтобы не блокировать повторный вызов записи
ЭтотОбъект.ПроверкиПройдены = Истина;
ЭтотОбъект.Записать(ПараметрыЗаписи);
ЭтотОбъект.ПроверкиПройдены = Ложь;
КонецПроцедуры
Если логика проверки выносится в общий клиентский модуль, важно правильно инициализировать ОписаниеОповещения. В форме в качестве контекста передается ЭтотОбъект, а при размещении процедур обработки в общем модуле вторым параметром передается имя самого общего модуля:
&НаКлиенте
Процедура ЗапроситьОписаниеФайла(ПараметрыФайла)
ДополнительныеПараметры = Новый Структура("Файл", ПараметрыФайла);
// Передаем ссылку на общий клиентский модуль вместо формы
Оповещение = Новый ОписаниеОповещения(
"ПослеВводаОписанияФайла",
РаботаСФайламиКлиент,
ДополнительныеПараметры
);
ПоказатьВводСтроки(Оповещение, "", "Введите описание файла");
КонецПроцедуры
Процедура в общем модуле должна быть обязательно объявлена с директивой &НаКлиенте и ключевым словом Экспорт:
&НаКлиенте
Процедура ПослеВводаОписанияФайла(Результат, ДополнительныеПараметры) Экспорт
Если Результат <> Неопределено Тогда
// Обработка полученной строки
КонецЕсли;
КонецПроцедуры
При программном вызове метода Записать() из процедур завершения оповещений разработчики часто сталкиваются с ситуацией, когда форма закрывается даже при обычном сохранении (по кнопке с дискетой или «Записать»).
Разберем причины и способы устранения такого поведения:
ПередЗаписью(Отказ, ПараметрыЗаписи) параметр ПараметрыЗаписи содержит служебные свойства выполнения команды. В типовых конфигурациях на БСП за закрытие формы отвечает свойство ПараметрыЗаписи.Закрыть. Если мы передаем сохраненную структуру обратно в метод Записать(ПараметрыКонтекста.ПараметрыЗаписи), форма сохраняет поведение, задуманное пользователем.
Применение единого диспетчера проверок или асинхронных конструкций Ждать в сочетании с корректной передачей ПараметрыЗаписи позволяет выстроить понятный, надежный и легко масштабируемый код контроля данных в любых управляемых формах.