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