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