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