Как программно активировать строку Дерева значений на управляемых формах и не потерять позиционирование при пересчете

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

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

Анализируем причину проблемы: почему ТекущаяСтрока становится Неопределено?

Рассмотрим стандартную ситуацию: пользователь вводит данные в ячейку дерева, и нам нужно пересчитать итоги по родительским веткам. Многие разработчики используют привычный метод:

  1. Преобразуют реквизит формы в объект ДеревоЗначений через РеквизитФормыВЗначение.
  2. Выполняют расчеты (например, используя метод Итог()).
  3. Загружают данные обратно через ЗначениеВРеквизитФормы.

Именно в этот момент происходит «катастрофа» для интерфейса. Метод ЗначениеВРеквизитФормы полностью пересоздает коллекцию строк в памяти. Даже если данные внутри идентичны, внутренние идентификаторы строк (целочисленные ID, которые платформа присваивает автоматически) генерируются заново. В итоге свойство Элементы.Дерево.ТекущаяСтрока, которое хранило старый ID, ссылается на несуществующий объект и сбрасывается в Неопределено. Дерево при этом визуально сворачивается, так как для платформы это совершенно новый набор данных.

Способ 1: Пересчет итогов без полной перезаписи дерева

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

Рассмотрим пример кода, который реализует эту стратегию. Сначала создадим рекурсивную процедуру для расчета итогов по всем уровням вложенности:


&НаСервере
Процедура РассчитатьИтогиДерева(СтрокиДерева, СписокКолонок) Экспорт
    МассивКолонок = СтрРазделить(СписокКолонок, ",", Ложь);
    
    Для Каждого Строка Из СтрокиДерева Цикл
        // Если есть подчиненные строки, уходим вглубь (рекурсия)
        Если Строка.Строки.Количество() > 0 Тогда
            РассчитатьИтогиДерева(Строка.Строки, СписокКолонок);
            
            // После расчета подчиненных, считаем итог для текущей строки
            Для Каждого ИмяКолонки Из МассивКолонок Цикл
                ИмяКолонки = СокрЛП(ИмяКолонки);
                Строка[ИмяКолонки] = Строка.Строки.Итог(ИмяКолонки);
            КонецЦикла;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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


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

Способ 2: Позиционирование по «бизнес-ключу»

Если специфика вашей задачи всё же требует полного обновления дерева через ЗначениеВРеквизитФормы (например, при изменении структуры веток), нам придется реализовать алгоритм восстановления позиции. Выясним, как это сделать правильно.

Шаг 1: Сохранение ключа. Перед серверным вызовом сохраните значение какого-либо уникального поля текущей строки (например, ссылку на справочник или внутренний уникальный идентификатор строки).

Шаг 2: Восстановление. После обновления данных выполните поиск строки в реквизите формы по этому ключу, получите её новый идентификатор и установите его в качестве текущей строки.


// Сохраняем ключ (на клиенте)
КлючСтроки = Элементы.Дерево.ТекущиеДанные.Номенклатура;

// Выполняем обновление дерева на сервере
ОбновитьДеревоНаСервере(); 

// Разворачиваем корень (если нужно)
Элементы.Дерево.Развернуть(Дерево.ПолучитьЭлементы()[0].ПолучитьИдентификатор(), Истина);

// Ищем строку по ключу и позиционируемся
ПараметрыОтбора = Новый Структура("Номенклатура", КлючСтроки);
НайденныеСтроки = Дерево.НайтиСтроки(ПараметрыОтбора, Истина);

Если НайденныеСтроки.Количество() > 0 Тогда
    Элементы.Дерево.ТекущаяСтрока = НайденныеСтроки[0].ПолучитьИдентификатор();
КонецЕсли;

Важные нюансы визуализации и прокрутки

Просто установить свойство ТекущаяСтрока иногда бывает недостаточно для комфортной работы пользователя. Рассмотрим дополнительные инструменты:

  1. Развертывание родителей: Если целевая строка находится глубоко в иерархии, а её родители после обновления свернуты, строка останется невидимой, хотя технически будет «текущей». Пройдите циклом по всем родителям найденной строки от корня вниз и примените метод Элементы.Дерево.Развернуть(Идентификатор).
  2. Метод ВыделитьСтроку: Чтобы платформа принудительно прокрутила список к нужной строке (Scroll To), используйте метод Элементы.Дерево.ВыделитьСтроку(Идентификатор). Это гарантирует, что строка попадет в видимую область экрана.
  3. Оптимизация на клиенте: Если расчеты не требуют обращения к базе данных (простое суммирование), попробуйте реализовать их на клиенте. Вы можете обходить дерево, используя метод ПолучитьРодителя() и ПолучитьЭлементы(). Это избавит интерфейс от «фризов», связанных с передачей данных на сервер и обратно.

Подведем итог: наиболее грамотным подходом является точечное обновление данных в реквизите формы. Это позволяет избежать проблем с идентификаторами строк и сохраняет состояние интерфейса (развернутые ветки, положение прокрутки) в первозданном виде. Если же перезапись неизбежна, всегда используйте поиск по уникальному бизнес-ключу для восстановления фокуса пользователя.

← На главную