Как реализовать подсветку неактивированных полей ввода в 1С с сохранением состояния при закрытии формы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки прикладных решений на платформе 1С:Предприятие 8.3 мы часто сталкиваемся с нестандартными требованиями пользователей к интерфейсу. Одно из таких требований — визуальный контроль заполнения или проверки полей формы. Рассмотрим классический бизнес-кейс: новый элемент справочника создается в несколько этапов по мере поступления информации. Пользователи хотят, чтобы все доступные поля ввода изначально были подсвечены ярким фоном (например, светло-желтым), сигнализируя о том, что их еще не просматривали. Как только пользователь активирует поле (ставит в него курсор), его фон должен навсегда вернуться к стандартному белому цвету. При этом состояние «просмотрено/не просмотрено» должно сохраняться в базе данных, чтобы при повторном открытии элемента подсвечивались только те поля, которые пользователь действительно пропустил.

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

Анализ базовых вариантов реализации и их недостатков

На первый взгляд задача кажется тривиальной, но при попытке спроектировать решение «в лоб» разработчики часто совершают архитектурные ошибки. Давайте разберем основные подходы, которые обычно предлагают на форумах, и выясним их слабые стороны.

Вариант 1. Создание отдельного булевого реквизита в метаданных для каждого поля

Если на форме расположено 20 контролируемых полей ввода, то в структуру справочника добавляется еще 20 реквизитов типа Булево (например, Проверено_Реквизит1, Проверено_Реквизит2 и так далее). При активации поля на клиенте соответствующий флаг устанавливается в значение Истина.

Вариант 2. Использование подчиненной табличной части

Вместо отдельных колонок создается табличная часть (или подчиненный регистр сведений), где хранятся строки с именами активированных полей.

Вариант 3. Использование обработчика ожидания для отслеживания фокуса

Поскольку в управляемых формах нет прямого стандартного события «ПриПолученииФокуса» для каждого поля ввода, разработчики часто прибегают к использованию метода ПодключитьОбработчикОжидания() с интервалом в 1 секунду. Процедура-таймер каждую секунду проверяет текущий активный элемент формы через свойство ЭтаФорма.ТекущийЭлемент.

Выяснив недостатки стандартных идей, перейдем к проектированию профессионального решения, лишенного этих проблем.

Оптимальная архитектура: избавляемся от таймеров и раздувания БД

Чтобы реализовать задачу максимально эффективно, мы объединим четыре передовых механизма платформы 1С:Предприятие:

  1. Событие ОбновлениеОтображения() — для мгновенного отслеживания фокуса ввода без использования таймеров.
  2. Сериализация в JSON (один строковый реквизит) — для компактного хранения состояния всех полей в одной строке неограниченной длины.
  3. Условное оформление формы — для быстрого и плавного изменения цвета полей силами самой платформы без мерцания интерфейса.
  4. Универсальные реквизиты формы — для динамической работы с флагами отображения на клиенте.

Рассмотрим подробнее каждый элемент этой архитектуры.

Шаг 1. Хранение состояния полей в одном реквизите (JSON)

Вместо создания десятков булевых колонок в метаданных справочника, мы добавим всего один реквизит объекта с именем ПосещенныеПоля (тип Строка, длина не ограничена). В этом реквизите мы будем хранить сериализованный массив имен полей, которые пользователь уже активировал. Пример содержимого строки в базе данных:

["Реквизит1", "Реквизит3", "Организация"]

Это исключает замусоривание структуры базы данных и позволяет легко расширять список отслеживаемых полей без изменения структуры таблиц.

Шаг 2. Отслеживание фокуса через событие ОбновлениеОтображения

Событие формы ОбновлениеОтображения() вызывается платформой автоматически при любом изменении состояния интерфейса, включая интерактивный переход пользователя между элементами формы. Это позволяет нам мгновенно и без задержек определять, на каком поле сейчас находится фокус ввода, считывая свойство ЭтаФорма.ТекущийЭлемент.Имя.

Шаг 3. Использование Условного Оформления для управления цветом

Прямое изменение цвета фона элементов через свойство Элементы.ИмяПоля.ЦветФона в коде усложняет логику и может приводить к графическим артефактам. Мы настроим правила Условного оформления формы. Для каждого отслеживаемого реквизита на форме мы динамически создадим вспомогательный булевый реквизит (например, с префиксом Флаг_Подсветки_). Если этот флаг равен Истина, платформа сама покрасит поле в нужный цвет.

Пошаговый разбор практической реализации

Перейдем к написанию кода управляемой формы. Разберем по шагам, как связать все элементы воедино.

Шаг 1. Инициализация формы и создание динамических реквизитов

При создании формы на сервере нам необходимо программно создать булевые реквизиты для управления условным оформлением, а также прочитать сохраненное в формате JSON состояние из реквизита объекта.

Разберем код процедуры ПриСозданииНаСервере:


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

Шаг 2. Программная настройка Условного Оформления

Для того чтобы не настраивать правила вручную в конфигураторе для каждого поля, мы напишем универсальную процедуру, которая свяжет созданные динамические флаги с фоном элементов управления.


&НаСервере
Процедура НастроитьУсловноеОформлениеПолей(МассивКонтролируемыхПолей)
    
    Для Каждого ИмяПоля Из МассивКонтролируемыхПолей Цикл
        
        // Создаем новый элемент условного оформления
        ЭлементОформления = УсловноеОформление.Элементы.Добавить();
        
        // 1. Задаем оформление: устанавливаем светло-желтый цвет фона (или любой другой по вкусу)
        ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветФона", WebЦвета.СветлоЖелтый);
        
        // 2. Задаем условие: "Флаг_Подсветки_[ИмяПоля] Равно Истина"
        ЭлементУсловия = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементУсловия.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Флаг_Подсветки_" + ИмяПоля);
        ЭлементУсловия.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
        ЭлементУсловия.ПравоеЗначение = Истина;
        
        // 3. Указываем оформляемое поле на форме
        ОформляемоеПоле = ЭлементОформления.Поля.Элементы.Добавить();
        ОформляемоеПоле.Поле = Новый ПолеКомпоновкиДанных(ИмяПоля);
        
    КонецЦикла;
    
КонецПроцедуры

Шаг 3. Реализация мгновенного снятия подсветки при фокусе

Теперь реализуем отслеживание фокуса на клиенте. Мы задействуем обработчик события формы ОбновлениеОтображения(). Обратите внимание, что мы используем булевую переменную-флаг на клиенте, чтобы избежать циклических обращений и лишней перезаписи значений.


&НаКлиенте
Перем ТекущийАктивныйЭлементИмя;

&НаКлиенте
Процедура ОбновлениеОтображения()
    
    // Получаем текущий элемент, на который установлен фокус ввода
    АктивныйЭлемент = ЭтаФорма.ТекущийЭлемент;
    Если АктивныйЭлемент = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Чтобы не выполнять код при каждом движении мыши, проверяем, изменился ли действительно фокус
    Если ТекущийАктивныйЭлементИмя = АктивныйЭлемент.Имя Тогда
        Возврат;
    КонецЕсли;
    
    ТекущийАктивныйЭлементИмя = АктивныйЭлемент.Имя;
    
    // Проверим, входит ли активированный элемент в список отслеживаемых
    ПараметрыФормы = ПолучитьПараметрыФормыИзХранилища();
    Если ПараметрыФормы = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    МассивКонтролируемыхПолей = ПараметрыФормы.МассивКонтролируемыхПолей;
    ИндексПоля = МассивКонтролируемыхПолей.Найти(ТекущийАктивныйЭлементИмя);
    
    Если ИндексПоля <> Неопределено Тогда
        // Проверяем, активна ли еще подсветка для данного поля
        ИмяФлага = "Флаг_Подсветки_" + ТекущийАктивныйЭлементИмя;
        Если ЭтаФорма[ИмяФлага] = Истина Тогда
            // Снимаем подсветку
            ЭтаФорма[ИмяФлага] = Ложь;
            // Помечаем форму как измененную, чтобы платформа предложила сохранить изменения
            ЭтаФорма.Модифицированность = Истина;
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Функция ПолучитьПараметрыФормыИзХранилища()
    
    Возврат ПолучитьИзВременногоХранилища(АдресХранилищаКонтролируемыхПолей);
    
КонецФункции

Шаг 4. Сохранение состояния полей перед записью объекта

Перед тем как объект будет сохранен в базу данных, нам нужно проанализировать текущее состояние наших динамических реквизитов формы, собрать имена всех полей, у которых флаг подсветки равен Ложь (то есть пользователь их уже посетил), упаковать их в JSON-строку и записать в реквизит ПосещенныеПоля.

Для этого мы переопределим обработчик события ПередЗаписьюНаСервере:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    ПараметрыФормы = ПолучитьИзВременногоХранилища(АдресХранилищаКонтролируемыхПолей);
    Если ПараметрыФормы = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    МассивКонтролируемыхПолей = ПараметрыФормы.МассивКонтролируемыхПолей;
    ПосещенныеПоляМассив = Новый Массив;
    
    // Обходим все контролируемые поля и проверяем их флаги условного оформления
    Для Каждого ИмяПоля Из МассивКонтролируемыхПолей Цикл
        Если ЭтаФорма["Флаг_Подсветки_" + ИмяПоля] = Ложь Тогда
            // Если подсветка выключена - значит пользователь посетил это поле
            ПосещенныеПоляМассив.Добавить(ИмяПоля);
        КонецЕсли;
    КонецЦикла;
    
    // Сериализуем полученный массив в JSON-строку
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, ПосещенныеПоляМассив);
    СтрокаJSON = ЗаписьJSON.Закрыть();
    
    // Записываем полученную строку в реквизит объекта
    ТекущийОбъект.ПосещенныеПоля = СтрокаJSON;
    
КонецПроцедуры

Преимущества разработанного архитектурного решения

Давайте проанализируем, почему предложенная схема значительно превосходит решения, описанные пользователями на форуме:

  1. Минимальная нагрузка на СУБД: Мы добавили всего одну строковую колонку в таблицу базы данных для хранения всего массива состояний, вместо заведения десятков физических полей.
  2. Высокое быстродействие интерфейса: Использование платформенного события ОбновлениеОтображения() работает в разы быстрее любых обработчиков ожидания. Платформа самостоятельно оптимизирует вызовы этой процедуры, исключая «зависания» клиента. Подсветка снимается мгновенно даже при очень быстром переключении полей.
  3. Отсутствие визуального шума: Благодаря механизму Условного оформления, изменение цвета полей происходит плавно на уровне платформы, без перезагрузки графических элементов формы и без мерцания экрана.
  4. Масштабируемость и простота поддержки: Если в будущем бизнес-требования изменятся и потребуется добавить новые поля в контур отслеживания, разработчику достаточно будет просто добавить имя поля в МассивКонтролируемыхПолей в процедуре ПриСозданииНаСервере. Вся остальная магия (создание флагов, настройка условного оформления и запись в JSON) выполнится полностью в автоматическом режиме!

Таким образом, мы успешно решили сложную задачу кастомизации интерфейса управляемых форм 1С, обеспечив максимальное удобство для пользователей, сохранив при этом производительность системы и чистоту архитектуры метаданных.

← На главную