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

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

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

Выясняем причину проблемы: разница типов на клиенте и сервере

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

Чтобы выполнить упорядочивание, нам придется временно перевести данные в контекст сервера, выполнить преобразование в полноценный прикладной объект ДеревоЗначений, применить к нему встроенные методы сортировки и вернуть обновленную структуру обратно на форму.

Базовый алгоритм серверной сортировки

Основой решения является связка двух платформенных методов: ДанныеФормыВЗначение() и ЗначениеВДанныеФормы(). Рассмотрим базовый пример процедуры, выполняющей сортировку дерева на сервере:


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

Обратите внимание на важнейший параметр метода Сортировать() — второй параметр (булево). Передавая значение Истина, мы принуждаем платформу выполнить рекурсивную сортировку. Это значит, что система упорядочит не только корневые узлы дерева, но и абсолютно все подчиненные ветки на любом уровне вложенности. Если передать Ложь, отсортируется только самый первый (верхний) уровень иерархии.

Шаг 1. Настройка интерактивной сортировки по клику на заголовок

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

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


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

Шаг 2. Хранение состояния и переключение направления (Альт/Деск)

Полноценная сортировка предполагает, что при первом нажатии данные упорядочиваются по возрастанию, а при повторном — по убыванию. Поскольку объект ДанныеФормыДерево не хранит историю сортировок, нам необходимо добавить на саму форму два скрытых вспомогательных реквизита:

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


&НаСервере
Процедура ОпределитьНаправлениеИСортировать(ИмяКолонки)
    
    // Определяем нужное направление
    Если ТекущаяКолонкаСортировки = ИмяКолонки Тогда
        // Если кликнули по той же колонке, меняем направление на противоположное
        НовоеНаправление = ?(НаправлениеСортировки = "Возр", "Убыв", "Возр");
    Иначе
        // Если кликнули по новой колонке, по умолчанию сортируем по возрастанию
        НовоеНаправление = "Возр";
    КонецЕсли;
    
    // Сохраняем состояние в реквизитах формы
    ТекущаяКолонкаСортировки = ИмяКолонки;
    НаправлениеСортировки = НовоеНаправление;
    
    // Вызываем основной метод сортировки данных
    СортироватьДеревоНаСервере(ИмяКолонки, НовоеНаправление);
    
КонецПроцедуры

Шаг 3. Решение проблемы «схлопывания» веток дерева

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

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

Поскольку состояние «развернутости» строки доступно только через элемент управления на клиенте, алгоритм будет координироваться клиентской процедурой:


&НаКлиенте
Процедура ВыполнитьСортировкуДерева(ИмяКолонки)
    
    // Создаем массив для хранения уникальных ключей развернутых строк
    МассивРазвернутых = Новый Массив;
    
    // Запоминаем текущее состояние развернутых веток дерева
    ЗафиксироватьСостояниеРазворота(Элементы.ДеревоТаблица.ПолучитьЭлементы(), МассивРазвернутых);
    
    // Выполняем сортировку на сервере
    ОпределитьНаправлениеИСортировать(ИмяКолонки);
    
    // Восстанавливаем разворот строк на клиенте
    ВосстановитьСостояниеРазворота(Элементы.ДеревоТаблица.ПолучитьЭлементы(), МассивРазвернутых);
    
КонецПроцедуры

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

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

Важное примечание: Чтобы данный алгоритм работал стабильно после обновления данных, у строк дерева должен быть реквизит с гарантированно уникальным значением (например, УникальныйКлючСтроки типа УникальныйИдентификатор или уникальное ключевое поле бизнес-сущности). Если ориентироваться только на стандартный метод ПолучитьИдентификатор(), то после перегенерации данных формы на сервере индексы идентификаторов могут сместиться, что приведет к некорректному развороту чужих веток.

Шаг 4. Визуальная индикация сортировки в шапке

Пользователю важно видеть, по какому полю в данный момент упорядочена таблица. Так как платформа автоматически не рисует стрелки сортировки на таблицах типа ДеревоЗначений, мы добавим их программно, изменяя текст заголовка колонки. Применим символы стрелок «▲» (возрастание) и «▼» (убывание):


&НаСервере
Процедура ОбновитьВизуальныеЗаголовки(ИмяКолонкиСортировки, Направление)
    
    Для Каждого ЭлементКолонка Из Элементы.ДеревоТаблица.ПодчиненныеЭлементы Цикл
        
        // Проверяем, является ли элемент колонкой таблицы
        Если ТипЗнч(ЭлементКолонка) = Тип("ПолеФормы") И ЭлементКолонка.Вид = ВидПоляФормы.ПолеВвода Тогда
            
            // Очищаем предыдущие символы стрелок из заголовков всех колонок
            ТекущийЗаголовок = ЭлементКолонка.Заголовок;
            ТекущийЗаголовок = СтрЗаменить(ТекущийЗаголовок, " ▲", "");
            ТекущийЗаголовок = СтрЗаменить(ТекущийЗаголовок, " ▼", "");
            
            // Если это колонка, по которой сейчас произведена сортировка, добавляем нужный символ
            Если Найти(ЭлементКолонка.ПутьКДанным, "." + ИмяКолонкиСортировки) > 0 Тогда
                СимволНаправления = ?(Направление = "Возр", " ▲", " ▼");
                ЭлементКолонка.Заголовок = ТекущийЗаголовок + СимволНаправления;
            Иначе
                // Возвращаем чистый заголовок для остальных колонок
                ЭлементКолонка.Заголовок = ТекущийЗаголовок;
            EndЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Альтернативное решение: когда лучше использовать Динамический список?

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

В таких сценариях гораздо эффективнее использовать элемент управления Динамический список с включенным свойством Отображение в значении Дерево. Динамические списки обладают следующими преимуществами «из коробки»:

  1. Автоматическая интерактивная сортировка по клику на любую доступную колонку без написания кода.
  2. Корректное отображение стандартных стрелок сортировки в заголовках.
  3. Оптимальная производительность за счет порционного считывания данных с сервера (СУБД возвращает только те строки, которые видны на экране).

Однако если бизнес-логика требует ручного расчета и интерактивного изменения строк непосредственно в дереве на форме, то описанная в статье серверная сортировка через преобразование в ДеревоЗначений остается единственным надежным программным решением.

← На главную