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