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