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