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