Как оптимизировать отображение связанных таблиц мастер-деталь при множественном выделении строк в 1С

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

При разработке сложных аналитических интерфейсов часто возникает задача отображения связанных списков в режиме «мастер — деталь»: верхняя таблица показывает свернутые агрегированные данные, а нижняя должна динамически отображать детальные строки, соответствующие выбранным позициям в верхней таблице. Если в верхней таблице пользователь выделяет сразу несколько строк через Shift или Ctrl, стандартный линейный перебор строк на клиенте начинает вызывать заметные задержки интерфейса.

Разберем причины падения производительности, ограничения стандартных механизмов платформы 1С и эффективные способы решения этой задачи.

Почему лобовой перебор строк приводит к зависаниям интерфейса

Рассмотрим типовую ошибку при реализации такой связи. Данные исходной большой таблицы (например, 1000 строк) хранятся в реквизите формы, а в событии ПриАктивизацииСтроки свернутой таблицы запускается цикл вида «для каждой выделенной строки проверить все строки исходной таблицы». При таком подходе вычислительная сложность алгоритма составляет O(N * M), где N — общее количество строк, а M — количество выделенных строк.

Ситуация усугубляется следующими факторами:

  1. Частота вызова события: Обработчик ПриАктивизацииСтроки вызывается при каждом перемещении курсора, смене фокуса и при выделении диапазона строк. Расчет запускается многократно еще до того, как пользователь закончил выбор.
  2. Особенности клиентских коллекций: Данные таблиц на клиенте представлены типом ДанныеФормыКоллекция. Обращение к свойствам элементов этой коллекции на стороне клиента выполняется медленнее, чем аналогичные операции с объектом ТаблицаЗначений на сервере.

Ограничения стандартного свойства ОтборСтрок

Попытка использовать стандартное свойство элемента формы ОтборСтрок для подчиненной таблицы сталкивается с ограничением платформы: свойство принимает только тип ФиксированнаяСтруктура. Это позволяет устанавливать отбор исключительно по жесткому равенству полей по схеме «И» (например, Поле1 = Значение1 И Поле2 = Значение2). Задать условие типа «ИЛИ» или использовать оператор «В Списке» через ОтборСтрок невозможно, поэтому для нескольких выделенных строк данный механизм не подходит.

Решение 1. Использование объекта Соответствие и составных ключей

Проанализируем способ снижения вычислительной сложности алгоритма на клиенте с квадратичной O(N * M) до линейной O(N + M) с помощью объекта Соответствие.

Разберем по шагам реализацию этого механизма:

  1. При первичном формировании исходной таблицы добавим в нее строковую колонку КлючСвязи, объединяющую значения группировочных колонок через разделитель.
  2. При активизации строк собираем ключи выделенных строк верхней таблицы в Соответствие, где ключом выступает составная строка, а значением — Истина.
  3. Проходим по исходной таблице один раз и проверяем наличие ключа в соответствии через обращение по индексу (поиск в хэш-таблице выполняется практически мгновенно).

Посмотрим на пример клиентского кода:


&НаКлиенте
Процедура ТаблицаШапкаПриАктивизацииСтроки(Элемент)
    
    // Собираем ключи выделенных строк в коллекцию Соответствие
    СоответствиеКлючей = Новый Соответствие;
    Для Каждого ИдентСтроки Из Элементы.ТаблицаШапка.ВыделенныеСтроки Цикл
        ТекСтрока = ТаблицаШапка.НайтиПоИдентификатору(ИдентСтроки);
        Если ТекСтрока <> Неопределено Тогда
            Ключ = XMLСтрока(ТекСтрока.Ссылка1) + "_" + XMLСтрока(ТекСтрока.Ссылка2);
            СоответствиеКлючей.Вставить(Ключ, Истина);
        КонецЕсли;
    КонецЦикла;
    
    // Перезаполняем подчиненную таблицу детализации
    ТаблицаДетализация.Очистить();
    
    Для Каждого СтрИсходная Из ТаблицаИсходная Цикл
        Если СоответствиеКлючей[СтрИсходная.КлючСвязи] <> Неопределено Тогда
            НоваяСтр = ТаблицаДетализация.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтр, СтрИсходная);
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Решение 2. Индексация колонок в ТаблицеЗначений на сервере

Если детальная таблица содержит десятки тысяч строк, операции по ее фильтрации эффективнее перенести на сторону сервера с использованием механизма индексации объекта ТаблицаЗначений.

Выясним, как работает этот подход:

  1. На сервере создается индекс по группировочным колонкам с помощью метода Индексы.Добавить().
  2. Поиск строк выполняется методом НайтиСтроки(), передавая структуру отбора. При наличии индекса платформа 1С осуществляет бинарный поиск мгновенно.
  3. Поиск вызывается только по количеству выделенных строк, а не по всей таблице.

Посмотрим на серверную реализацию:


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

Решение 3. Иерархическое отображение через ДеревоЗначений

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

Разберем преимущества этого подхода:

  1. Запрос формирует иерархическую структуру сразу с итогами по Ссылка1, Ссылка2 на верхнем уровне и детальными записями по Ссылка3 на подчиненном уровне.
  2. Пользователь видит агрегированные суммы в узлах дерева и может раскрывать нужные группировки для просмотра детальных строк.
  3. Отпадает необходимость программно отслеживать выделение строк и выполнять повторную выборку данных.

Оптимизация частоты вызовов с помощью обработчика ожидания

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

В обработчике ПриАктивизацииСтроки вызывается метод ПодключитьОбработчикОжидания("ОбновитьДетализацию", 0.1, Истина) с параметром однократного вызова. Если пользователь продолжает перемещаться по списку, предыдущий таймер сбрасывается и переподключается, а расчет запускается только после остановки курсора.

← На главную