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