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