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

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

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

Понимание различий между серверным и клиентским деревом

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

  1. На сервере: Когда мы работаем с данными на сервере, мы оперируем объектом ДеревоЗначений и его дочерними элементами СтрокаДереваЗначений. Это полноценные программные объекты платформы 1С, которые могут хранить любые данные и имеют обширный набор методов для манипуляции ими (например, НайтиСтроки(), Добавить() и т.д.).
  2. На клиенте: Когда же мы переносим данные в реквизит формы для отображения на клиенте, ДеревоЗначений трансформируется в ДанныеФормыДерево, а его строки — в ДанныеФормыЭлементДерева. Эти клиентские объекты являются лишь «проекцией» серверных данных и имеют ограниченный функционал. Они не являются теми же самыми объектами, что и СтрокаДереваЗначений.

Почему возникает ошибка?

Именно из-за этого различия мы не можем просто так найти СтрокаДереваЗначений на сервере и напрямую присвоить ее свойству Элементы.Дерево.ТекущаяСтрока на клиенте. Попытка сделать это приведет к ошибке: "Отсутствует отображение для типа 'СтрокаДереваЗначений'". Платформа просто не знает, как отобразить серверный тип данных на клиентском элементе управления, поскольку они несовместимы впрямую.

Решение: Использование идентификаторов

Чтобы установить текущую строку на клиенте, нам нужен некий «мост» между серверной и клиентской частью. Таким мостом служит уникальный идентификатор клиентской строки. У каждого объекта ДанныеФормыЭлементДерева есть метод ПолучитьИдентификатор(), который возвращает строковое значение, уникально идентифицирующее эту строку в контексте конкретного элемента формы. Это именно то, что нам нужно присваивать свойству Элементы.Дерево.ТекущаяСтрока.

Основной подход: поиск строки на клиенте и использование идентификатора

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

  1. Найдем нужную строку на сервере: На этапе формирования данных дерева (например, в процедуре, вызываемой с сервера при открытии формы или обновлении), мы должны найти ту самую бригаду, которую требуется выделить. Мы можем использовать запрос или метод ДеревоЗначений.НайтиСтроки() для этого.
  2. Передадим ключевое значение на клиент: Вместо всей серверной строки, нам нужно передать на клиент только уникальное ключевое значение, по которому мы сможем идентифицировать нужную бригаду. Это может быть ссылка на элемент справочника "Бригады", уникальный идентификатор (GUID) или любой другой уникальный атрибут. Передавать его можно, например, через параметр формы или через реквизит формы простого типа (например, Строка или УникальныйИдентификатор).
  3. Найдем соответствующую клиентскую строку: После того как дерево формы заполнится данными и будет доступно на клиенте, мы должны обойти его элементы (ДанныеФормыДерево и ДанныеФормыЭлементДерева), сравнивая значение колонки с переданным ключевым значением.
  4. Получим идентификатор клиентской строки: Как только мы найдем нужный ДанныеФормыЭлементДерева на клиенте, вызовем у него метод ПолучитьИдентификатор().
  5. Установим текущую строку: Присвоим полученный идентификатор свойству Элементы.Дерево.ТекущаяСтрока.

Практический пример для двухуровневого дерева

Рассмотрим конкретный сценарий, описанный автором: дерево состоит из двух уровней — верхний уровень это "статус" (свободна/занята), а нижний — сама "бригада". Предположим, что при открытии формы нам передается ссылка на справочник "Бригада", которую нужно выделить.

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

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


&НаКлиенте
Процедура УстановитьТекущуюБригаду(ИскомаяБригада)
    // ИскомаяБригада - это ссылка на элемент справочника "Бригады", 
    // переданная с сервера.
    
    // Переменная для хранения найденной строки на клиенте
    ПеременнаяНайденнаяСтрокаБригады = Неопределено;
    
    // Обходим верхний уровень дерева (статусы)
    Для Каждого СтрокаСтатуса Из Элементы.Бригады.ПолучитьЭлементы() Цикл
        // Обходим дочерние элементы текущей строки статуса (сами бригады)
        Для Каждого СтрокаБригадыИзДерева Из СтрокаСтатуса.ПолучитьЭлементы() Цикл
            // Предполагаем, что у строки дерева есть колонка 'Бригада',
            // содержащая ссылку на справочник
            Если СтрокаБригадыИзДерева.Бригада = ИскомаяБригада Тогда
                ПеременнаяНайденнаяСтрокаБригады = СтрокаБригадыИзДерева;
                Прервать; // Нашли нужную бригаду, выходим из внутреннего цикла
            КонецЕсли;
        КонецЦикла;
        
        Если ПеременнаяНайденнаяСтрокаБригады <> Неопределено Тогда
            Прервать; // Нашли нужную бригаду, выходим из внешнего цикла
        КонецЕсли;
    КонецЦикла;
    
    // Если бригада найдена, устанавливаем ее текущей
    Если ПеременнаяНайденнаяСтрокаБригады <> Неопределено Тогда
        Элементы.Бригады.ТекущаяСтрока = ПеременнаяНайденнаяСтрокаБригады.ПолучитьИдентификатор();
    КонецЕсли;
КонецПроцедуры

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

Важно: Убедитесь, что колонка Бригада (или любая другая колонка, по которой вы ищете) действительно существует в реквизите формы Бригады и содержит ссылки на справочник.

Универсальный подход с использованием пути строки

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

Этот подход состоит из трех основных шагов:

  1. Найти на сервере нужную СтрокаДереваЗначений: Мы используем стандартные методы ДеревоЗначений, например, НайтиСтроки(), для получения серверной строки.
  2. Преобразовать серверную строку в строковый путь: Разработаем функцию, которая рекурсивно поднимается от найденной серверной строки к корню дерева, собирая уникальные идентификаторы каждого уровня (например, значения определенной колонки или индексы строк) в строковый путь. Этот путь будет уникален для данной строки в дереве. Полученный строковый путь передадим на клиент. Для корректной передачи пути, содержащего различные символы, можем использовать XMLСтрока() для сериализации.
  3. Найти клиентскую строку по пути и установить текущую: На клиенте, после заполнения реквизита формы ДанныеФормыДерево, мы разработаем обратную функцию, которая будет разбирать переданный строковый путь и пошагово спускаться по клиентскому дереву (ДанныеФормыДерево -> ДанныеФормыЭлементДерева), пока не достигнет нужного элемента. Затем, как и в предыдущем примере, получим его идентификатор и присвоим свойству Элементы.Дерево.ТекущаяСтрока.

Посмотрим на примеры функций для работы с путем (адаптированные из сообщения 14):


// Серверный или Клиентский контекст (с учетом экранирования для тонкого клиента)
// Параметры:
//   СтрокаДерева - СтрокаДереваЗначений | ДанныеФормыЭлементДерева - строка, для которой нужно получить путь.
//   ИмяКолонки - Строка - Имя колонки, значение которой используется как часть пути. 
//                           Если пустое значение, используется индекс строки.
//   ИгнорироватьПростойПервыйУровень - Булево - Если на первом уровне только одна строка, 
//                                                 то ее путь будет игнорироваться.
//   Разделитель - Строка - Символ-разделитель в пути.
// Возвращает: Строка - XML-сериализованный путь строки в дереве.
Функция Дерево_ПутьСтрокойЛкс(СтрокаДерева, ИмяКолонки = "Имя", ИгнорироватьПростойПервыйУровень = Ложь, Разделитель = ".", Дерево = Неопределено, Исключения = Неопределено) Экспорт

    Перем Результат;
    Если Не ЗначениеЗаполнено(ИмяКолонки) Тогда
        // Определение родителя для получения индекса
        Перем Родитель;
        Попытка
            Родитель = СтрокаДерева.Родитель;
        Исключение
            // Для ДанныеФормыЭлементДерева Родитель недоступен напрямую, используем ПолучитьРодителя
            Родитель = СтрокаДерева.ПолучитьРодителя();
        КонецПопытки;

        Если ТипЗнч(Родитель) = Тип("ДеревоЗначений") Или ТипЗнч(Родитель) = Тип("СтрокаДереваЗначений") Тогда
            Результат = Родитель.Строки.Индекс(СтрокаДерева);
        ИначеЕсли ТипЗнч(Родитель) = Тип("ДанныеФормыДерево") Или ТипЗнч(Родитель) = Тип("ДанныеФормыЭлементДерева") Тогда
            Результат = Родитель.ПолучитьЭлементы().Индекс(СтрокаДерева);
        Иначе
            ВызватьИсключение "Неподдерживаемый тип элемента дерева (для определения индекса) - " + ТипЗнч(Родитель);
        КонецЕсли; 
    Иначе
        // Если задано имя колонки, используем значение колонки
        Результат = СтрокаДерева[ИмяКолонки];
    КонецЕсли; 

    // Рекурсивный подъем по родителям
    Перем РодительТекущейСтроки;
    Попытка
        РодительТекущейСтроки = СтрокаДерева.Родитель;
    Исключение
        // Для ДанныеФормыЭлементДерева Родитель недоступен напрямую
        РодительТекущейСтроки = СтрокаДерева.ПолучитьРодителя();
    КонецПопытки; 

    Если РодительТекущейСтроки = Неопределено Тогда 
        // Если это строка верхнего уровня
        Перем Владелец;
        Попытка
            Владелец = СтрокаДерева.Владелец();
        Исключение
            // Для ДанныеФормыЭлементДерева владелец недоступен напрямую, 
            // предполагаем что это корень, если нет родителя.
            // Или если это первый уровень ДанныеФормыДерево
            Если ТипЗнч(СтрокаДерева) = Тип("ДанныеФормыЭлементДерева") Тогда
                // Здесь нужно как-то определить, если это корень, 
                // можно передать ДеревоФормы как параметр для проверки количества элементов.
                // В данном случае, если это корневой элемент и нет родителя, считаем Владельцем само дерево формы
                Если Дерево = Неопределено Тогда
                    // Предполагаем, что ДеревоФормы (реквизит формы) было передано как параметр Дерево
                    // Если нет, то для ДанныеФормыЭлементДерева это сложнее определить без рекурсивного поиска
                    // Если мы ищем от корня (ДеревоФормы), то СтрокаДерева - это один из элементов верхнего уровня
                    // И тогда Владелец - это ДеревоФормы
                    Если ТипЗнч(СтрокаДерева) = Тип("ДанныеФормыДерево") Тогда
                        Владелец = СтрокаДерева;
                    Иначе
                        // Если это элемент дерева, у которого нет родителя, 
                        // и мы не можем получить его Владелец, 
                        // это значит что это один из элементов верхнего уровня формы.
                        // Здесь нужно более сложное определение Владельца.
                        // Для упрощения примера, будем считать, что СтрокаДерева - это ДФЭД верхнего уровня.
                        Владелец = Неопределено; // Не можем определить. Пропускаем проверку.
                    КонецЕсли;
                КонецЕсли;
            КонецЕсли;
        КонецПопытки;
        
        Если Истина И ИгнорироватьПростойПервыйУровень И Владелец <> Неопределено Тогда
            Если ТипЗнч(Владелец) = Тип("ДеревоЗначений") И Владелец.Строки.Количество() = 1 Тогда
                 Результат = Неопределено;
            ИначеЕсли ТипЗнч(Владелец) = Тип("ДанныеФормыДерево") И Владелец.ПолучитьЭлементы().Количество() = 1 Тогда
                Результат = Неопределено;
            КонецЕсли;
        КонецЕсли; 
    Иначе
        // Рекурсивный вызов для родителя
        РезультатСверху = Дерево_ПутьСтрокойЛкс(РодительТекущейСтроки, ИмяКолонки, ИгнорироватьПростойПервыйУровень, Разделитель, Дерево, Исключения);
        Если РезультатСверху <> Неопределено Тогда
            Если Результат <> Неопределено Тогда
                Результат = РезультатСверху + Разделитель + Результат;
            Иначе
                Результат = РезультатСверху; // Если текущий результат Неопределено из-за игнорирования.
            КонецЕсли;
        КонецЕсли; 
    КонецЕсли;
    
    // Возвращаем XML-сериализованный результат для безопасной передачи
    Возврат XMLСтрока(Результат);
КонецФункции

// Клиентский или Серверный контекст (с учетом экранирования для тонкого клиента)
// Параметры:
//   КореньДерева - ДанныеФормыДерево | ДеревоЗначений | ДанныеФормыЭлементДерева - объект дерева или его строки, 
//                                                                          от которого начинаем поиск.
//   ИмяКолонки - Строка - Имя колонки, значение которой используется для поиска. 
//                           Если пустое значение, используется индекс строки.
//   Путь - Строка - XML-сериализованный путь к строке.
//   ИгнорироватьПростойПервыйУровень - Булево - Если на первом уровне только одна строка, 
//                                                 то ее путь будет игнорироваться при поиске.
// Возвращает: СтрокаДереваЗначений | ДанныеФормыЭлементДерева - найденная строка дерева, или Неопределено.
Функция Дерево_НайтиПоПутиСтрокойЛкс(КореньДерева, ИмяКолонки = "Имя", Путь, ИгнорироватьПростойПервыйУровень = Ложь) Экспорт

    // Десериализуем путь из XML
    Перем ДесериализованныйПуть;
    Попытка
        ДесериализованныйПуть = XMLЗначение("Строка", Путь);
    Исключение
        // Если путь не XML-сериализован или некорректен
        ДесериализованныйПуть = Путь; 
    КонецПопытки;

    // Функция для получения первого фрагмента пути до разделителя
    Перем ПервыйФрагментПути;
    Перем ПозицияРазделителя = СтрНайти(ДесериализованныйПуть, "."); // Предполагаем '.' как разделитель
    Если ПозицияРазделителя > 0 Тогда
        ПервыйФрагментПути = Лев(ДесериализованныйПуть, ПозицияРазделителя - 1);
    Иначе
        ПервыйФрагментПути = ДесериализованныйПуть;
    КонецЕсли;

    Если Истина
        И ИгнорироватьПростойПервыйУровень
    Тогда
        Перем КолЭлементов;
        Если ТипЗнч(КореньДерева) = Тип("ДеревоЗначений") Тогда
            КолЭлементов = КореньДерева.Строки.Количество();
        ИначеЕсли ТипЗнч(КореньДерева) = Тип("ДанныеФормыДерево") Тогда
            КолЭлементов = КореньДерева.ПолучитьЭлементы().Количество();
        Иначе
            КолЭлементов = 0; // Для ДанныеФормыЭлементДерева или СтрокаДереваЗначений, 
                               // не имеет смысла игнорировать первый уровень.
        КонецЕсли;

        Если КолЭлементов = 1 Тогда
            // Если игнорируем первый уровень и он единственный
            Перем ЕдинственныйЭлемент;
            Если ТипЗнч(КореньДерева) = Тип("ДеревоЗначений") Тогда
                ЕдинственныйЭлемент = КореньДерева.Строки[0];
            ИначеЕсли ТипЗнч(КореньДерева) = Тип("ДанныеФормыДерево") Тогда
                ЕдинственныйЭлемент = КореньДерева.ПолучитьЭлементы()[0];
            КонецЕсли;

            Если ЕдинственныйЭлемент <> Неопределено Тогда
                // Рекурсивный вызов с оставшимся путем
                Перем ОстальнойПуть = "";
                Если ПозицияРазделителя > 0 Тогда
                    ОстальнойПуть = Сред(ДесериализованныйПуть, ПозицияРазделителя + 1);
                КонецЕсли;
                Возврат Дерево_НайтиПоПутиСтрокойЛкс(ЕдинственныйЭлемент, ИмяКолонки, XMLСтрока(ОстальнойПуть));
            КонецЕсли;
        КонецЕсли;
    КонецЕсли; 

    Если Не ЗначениеЗаполнено(ПервыйФрагментПути) Тогда
        Возврат КореньДерева; // Путь закончился, текущий КореньДерева и есть искомый элемент
    КонецЕсли; 

    Перем ОстальнойПуть = "";
    Если ПозицияРазделителя > 0 Тогда
        ОстальнойПуть = Сред(ДесериализованныйПуть, ПозицияРазделителя + 1);
    КонецЕсли;
    
    Перем ТекущаяСтрока = Неопределено;

    Если Не ЗначениеЗаполнено(ИмяКолонки) Тогда
        // Поиск по индексу
        Перем ЗначениеИндекса = Число(ПервыйФрагментПути);
        Если ТипЗнч(КореньДерева) = Тип("ДеревоЗначений") Или ТипЗнч(КореньДерева) = Тип("СтрокаДереваЗначений") Тогда
            Если КореньДерева.Строки.Количество() > ЗначениеИндекса Тогда
                ТекущаяСтрока = КореньДерева.Строки[ЗначениеИндекса];
            КонецЕсли; 
        ИначеЕсли ТипЗнч(КореньДерева) = Тип("ДанныеФормыДерево") Или ТипЗнч(КореньДерева) = Тип("ДанныеФормыЭлементДерева") Тогда
            Если КореньДерева.ПолучитьЭлементы().Количество() > ЗначениеИндекса Тогда
                ТекущаяСтрока = КореньДерева.ПолучитьЭлементы()[ЗначениеИндекса];
            КонецЕсли; 
        Иначе
            ВызватьИсключение "Неподдерживаемый тип элемента дерева (для поиска по индексу) - " + ТипЗнч(КореньДерева);
        КонецЕсли; 
    Иначе
        // Поиск по значению колонки
        Если ТипЗнч(КореньДерева) = Тип("ДанныеФормыДерево") Или ТипЗнч(КореньДерева) = Тип("ДанныеФормыЭлементДерева") Тогда
            Перем ДочерниеЭлементы = КореньДерева.ПолучитьЭлементы();
            Для Каждого ДочернийЭлемент Из ДочерниеЭлементы Цикл
                Если ДочернийЭлемент[ИмяКолонки] = ПервыйФрагментПути Тогда
                    ТекущаяСтрока = ДочернийЭлемент;
                    Прервать;
                КонецЕсли; 
            КонецЦикла;
        ИначеЕсли ТипЗнч(КореньДерева) = Тип("ДеревоЗначений") Или ТипЗнч(КореньДерева) = Тип("СтрокаДереваЗначений") Тогда
            ТекущаяСтрока = КореньДерева.Строки.Найти(ПервыйФрагментПути, ИмяКолонки);
        Иначе
            ВызватьИсключение "Неподдерживаемый тип элемента дерева (для поиска по колонке) - " + ТипЗнч(КореньДерева);
        КонецЕсли; 
    КонецЕсли; 

    Если ТекущаяСтрока <> Неопределено Тогда
        Возврат Дерево_НайтиПоПутиСтрокойЛкс(ТекущаяСтрока, ИмяКолонки, XMLСтрока(ОстальнойПуть));
    Иначе 
        Возврат Неопределено; // Строка не найдена по пути
    КонецЕсли;
КонецФункции

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

Важные моменты и рекомендации

При работе с установкой текущей строки в дереве управляемой формы, всегда помните о следующих важных аспектах:

  1. Разделение контекстов: Это ключевой момент. СтрокаДереваЗначений существует только на сервере, а ДанныеФормыЭлементДерева — на клиенте. Прямое присвоение невозможно.
  2. Передача данных: Передавайте с сервера на клиент только простые типы данных или ссылки (например, УникальныйИдентификатор, Строка, СправочникСсылка), которые могут быть использованы для поиска на клиенте. Избегайте передачи сложных объектов вроде СтрокаДереваЗначений через параметры или реквизиты формы.
  3. Метод ПолучитьИдентификатор(): Этот метод является вашим основным инструментом для работы со свойством Элементы.Дерево.ТекущаяСтрока. Он доступен только у объектов ДанныеФормыЭлементДерева.
  4. Эффективность поиска: Для больших деревьев рекурсивный обход на клиенте может быть относительно медленным. Если есть возможность, оптимизируйте поиск. Если у вас есть уникальный ключ, который отображается в колонке дерева, можно сначала попытаться использовать более быстрые методы поиска, если они доступны для ДанныеФормыДерево (хотя обычно приходится обходить). Присвоение значения через ПолучитьИдентификатор(), как правило, очень эффективно, так как платформа оптимизирует поиск по этому внутреннему идентификатору.
  5. Актуальность данных: Убедитесь, что дерево на форме уже полностью заполнено и актуально на момент попытки установки текущей строки. Вызов процедур установки текущей строки следует производить после того, как данные дерева гарантированно загружены (например, в обработчике события формы ПриСозданииНаСервере после выгрузки данных или в ПослеЗагрузкиДанныхНаСервере, а затем вызов клиентской процедуры).
  6. Обработка неопределенных значений: Всегда предусматривайте сценарии, когда искомая строка может быть не найдена (например, из-за изменения данных или некорректно переданного параметра). Проверяйте переменные на Неопределено перед попыткой использования их свойств или методов, чтобы избежать ошибок.

Следуя этим рекомендациям и используя предложенные подходы, вы сможете надежно и корректно устанавливать текущую строку в элементах управления «Дерево» в ваших управляемых формах 1С.

← На главную