В разработке на платформе 1С:Предприятие 8.3 часто возникает задача вычислить разницу (дельту) между текущей и предыдущей строкой в списке. Однако, если мы работаем с объектом ДинамическийСписок в интерфейсе "Такси", мы сталкиваемся с архитектурной особенностью: этот объект не является таблицей в памяти. Это механизм порционной подгрузки данных, у которого нет индексов строк (0, 1, 2...). В этой статье мы подробно разберем, как обойти это ограничение и получить доступ к данным "соседней" строки.
Разберем типичную ошибку начинающих разработчиков. Попытка получить предыдущую строку через Элементы.Список.ТекущаяСтрока - 1 обречена на провал. В динамическом списке идентификатором строки является либо Ссылка (для справочников и документов), либо КлючЗаписи (для регистров). Эти значения не являются последовательными числами. Более того, пользователь может применить произвольную сортировку или отбор, что полностью меняет порядок следования записей. Поэтому нам необходимо использовать более надежные методы.
Проанализируем самый универсальный способ. Суть его заключается в том, чтобы программно выполнить тот же запрос, который выполняет динамический список в данный момент, учитывая все пользовательские настройки (отборы, сортировки, поиск). Рассмотрим реализацию этого метода:
// 1. Получаем исполняемую схему и настройки из текущего списка на форме
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
НастройкиСхемы = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
// 2. Инициализируем компоновщик макета
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(Схема));
КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСхемы);
// 3. Выполняем компоновку макета
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновкиДанных = КомпоновщикМакета.Выполнить(Схема, КомпоновщикНастроек.ПолучитьНастройки(), , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
// 4. Прогоняем данные через процессор компоновки
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновкиДанных);
// 5. Выгружаем результат в таблицу значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
РезультатТаблица = ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
// 6. Ищем текущую строку в полученной таблице и берем предыдущую
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
СтрокаВТаблице = РезультатТаблица.Найти(ТекущиеДанные.Ссылка, "Ссылка");
Индекс = РезультатТаблица.Индекс(СтрокаВТаблице);
Если Индекс > 0 Тогда
ПредыдущаяСтрока = РезультатТаблица[Индекс - 1];
// Теперь у нас есть доступ к полям: ПредыдущаяСтрока.НужноеПоле
КонецЕсли;
КонецЕсли;
Этот метод гарантирует точность, так как полностью повторяет логику отображения данных на экране. Однако стоит помнить о производительности: если в списке десятки тысяч записей, полная выгрузка может занять время. Для оптимизации рекомендуется программно добавлять в настройки компоновки отборы, ограничивающие выборку только необходимым диапазоном данных.
Если наша задача — показывать дельты (разницы) для каждой строки прямо в списке, мы можем воспользоваться обработчиком ПриПолученииДанныхНаСервере. Выясним причину, почему это удобно: этот метод работает только с теми строками, которые в данный момент отрисовываются на экране пользователя.
Рассмотрим алгоритм реализации:
NULL Как ДельтаРесурса).
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Получаем ключи строк, которые сейчас отображаются на экране
КлючиСтрок = Строки.ПолучитьКлючи();
// Выполняем запрос к базе данных для получения значений предыдущих записей
// В запросе используем соединение таблицы самой с собой или аналитические функции
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ..."; // Здесь пишем логику получения данных
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
ДанныеСтроки = Строки.Получить(РезультатЗапроса.Ключ);
Если ДанныеСтроки <> Неопределено Тогда
ДанныеСтроки.Данные.ДельтаРесурса = РезультатЗапроса.ЗначениеДельты;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важный нюанс: Обработчик ПриПолученииДанныхНаСервере видит только "порцию" данных. Если текущая строка — первая в порции, а "предыдущая" находится в предыдущей порции (выше на экране), вам придется выполнить дополнительный запрос к БД, чтобы найти ту самую единственную запись, которая предшествует первой строке в текущем наборе.
Если расчет нужен не для всех строк, а только по требованию (по нажатию кнопки), проанализируем ситуацию с точки зрения формирования быстрого запроса. Нам нужно знать текущее значение полей сортировки. Например, если список отсортирован по дате.
Разберем логику: нам нужно найти ПЕРВУЮ 1 запись, у которой Дата < ТекущаяДата, при этом сортировка в запросе должна быть УПОРЯДОЧИТЬ ПО Дата УБЫВ. Это вернет нам ближайшую запись "сверху".
Для реализации этого метода программно считываем Элементы.Список.КомпоновщикНастроек.Настройки.Порядок, динамически формируем текст запроса ВЫБРАТЬ ПЕРВЫЕ 1... и подставляем значения из Элементы.Список.ТекущиеДанные в параметры запроса.
Выбор метода зависит от ваших целей. Рассмотрим рекомендации:
Помните, что динамический список в 1С спроектирован для работы в режиме stateless (сервер не хранит состояние выборки). Поэтому каждое обращение к "соседней" строке — это всегда новый поиск в базе данных. Проектируйте свои интерфейсы с учетом этой особенности, чтобы обеспечить комфортную работу пользователей.