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