Как обновить данные родительской управляемой формы при закрытии открытой из нее дочерней формы в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки интерфейсов на управляемых формах 1С программисты регулярно сталкиваются с классической задачей: из основной (родительской) формы открывается вспомогательная (дочерняя) форма для создания или редактирования данных. После того как пользователь совершил необходимые действия в дочернем окне и закрыл его, родительская форма должна оперативно отреагировать — обновить динамические списки, пересчитать итоги или перечитать измененные реквизиты. В этой статье мы подробно разберем, как правильно организовать общение между управляемыми формами, рассмотрим стандарты разработки фирмы «1С» и изучим различные практические способы обновления данных.

Почему метод ПолучитьФорму() находится под строгим запретом стандартов 1С

Начнем с разбора типичной ошибки, которую часто совершают начинающие разработчики. Речь идет об использовании конструкции ПолучитьФорму() с последующим вызовом метода Открыть(). Давайте проанализируем, почему стандарты разработки (в частности, стандарт std404) запрещают такое решение:

  1. Нарушение инкапсуляции интерфейса. Получая объект формы напрямую через ПолучитьФорму(), программист получает соблазн напрямую обращаться к ее реквизитам, элементам и методам из внешней формы (например, напрямую заполнять реквизиты дочерней формы из родительской). Это создает сильную связность кода, делает формы зависимыми друг от друга и существенно усложняет дальнейшую поддержку конфигурации.
  2. Избыточные серверные вызовы. Платформенный метод ОткрытьФорму() оптимизирован для минимизации клиент-серверного трафика. Он выполняет инициализацию формы на сервере (процедуру ПриСозданииНаСервере) за один серверный вызов непосредственно в момент открытия. При работе по цепочке ПолучитьФорму() -> Открыть() платформа может совершать лишние неявные обращения к серверу еще до того, как форма реально отобразится на экране пользователя.
  3. Игнорирование декларативного подхода. Согласно стандартам 1С, любая параметризация открываемой формы должна выполняться строго через передачу структуры параметров в метод ОткрытьФорму(). Это позволяет форме-приемнику самостоятельно обрабатывать входящие данные в процедуре ПриСозданииНаСервере, сохраняя свою автономность.

Поэтому правильным решением всегда является использование метода ОткрытьФорму().

Решение №1: Использование метода ОткрытьФорму() и ОписаниеОповещения

Рассмотрим самый надежный, явный и рекомендуемый платформенный механизм возврата управления — использование объекта ОписаниеОповещения. Этот способ идеально подходит, когда нам нужно выполнить конкретный алгоритм в родительской форме сразу после закрытия дочерней.

Разберем по шагам, как выглядит эта схема в коде. Сначала в клиентском коде родительской формы мы подготавливаем описание оповещения и открываем дочернюю форму:


&НаКлиенте
Процедура ОткрытьДочернююФорму(Команда)

    ПараметрыОткрытия = Новый Структура;
    ПараметрыОткрытия.Вставить("Ключ", Объект.СсылкаНаОбъект); // Передаем ссылку, если нужно открыть существующий
    
    // Создаем описание оповещения, указывая имя процедуры-обработчика и модуль, где она расположена
    ОповещениеОЗакрытии = Новый ОписаниеОповещения("ПослеЗакрытияДочернейФормы", ЭтаФорма);
    
    // Открываем форму, передавая описание оповещения седьмым параметром
    ОткрытьФорму(
        "Справочник.Номенклатура.Форма.ФормаЭлемента", 
        ПараметрыОткрытия, 
        ЭтаФорма, 
        УникальныйИдентификатор, 
        , 
        , 
        ОповещениеОЗакрытии
    );

КонецПроцедуры

Теперь в модуле этой же родительской формы нам необходимо написать экспортную процедуру-обработчик, которая примет управление после закрытия дочернего окна. Обратите внимание на ее сигнатуру:


&НаКлиенте
Процедура ПослеЗакрытияДочернейФормы(РезультатЗакрытия, ДополнительныеПараметры) Экспорт

    // Здесь пишем код обновления данных родительской формы
    // Например, если на форме расположен динамический список:
    Элементы.СписокДокументов.Обновить();
    
    // Если нужно обновить данные объектного реквизита:
    // ЭтаФорма.Прочитать();

КонецПроцедуры

В самой дочерней форме при закрытии (например, по нажатию на специальную кнопку) мы можем передать обратно любые данные, используя метод Закрыть():


&НаКлиенте
Процедура КнопкаГотово(Команда)
    
    СтруктураРезультата = Новый Структура;
    СтруктураРезультата.Вставить("ОперацияВыполнена", Истина);
    СтруктураРезультата.Вставить("НовоеЗначение", Объект.Ссылка);
    
    // Передаем структуру в процедуру закрытия. Она попадет в параметр "РезультатЗакрытия" родительской формы
    Закрыть(СтруктураРезультата);
    
КонецПроцедуры

Решение №2: Использование глобального метода Оповестить()

Бывают ситуации, когда дочерняя форма изменяет данные в базе (например, записывает новый элемент справочника), и нам необходимо уведомить об этом не одну конкретную форму, а вообще все открытые в данный момент формы интерфейса. Для этого применяется широковещательное оповещение.

Посмотрим на пример реализации. В дочерней форме после успешной записи объекта мы вызываем глобальный метод Оповестить():


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    
    // Оповещаем всю систему об изменении данных. 
    // Передаем имя события, параметр события и источник.
    Оповестить("ЗаписанЭлементСправочника", Объект.Ссылка, "ФормаЭлементаНоменклатуры");
    
КонецПроцедуры

В родительской форме (или в любой другой форме, которая должна среагировать на это событие) мы должны определить стандартный обработчик события управляемой формы ОбработкаОповещения:


&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    
    // Проверяем, то ли это событие, которое мы ждем
    Если ИмяСобытия = "ЗаписанЭлементСправочника" Тогда
        // Выполняем обновление данных формы
        Элементы.СписокНоменклатуры.Обновить();
    КонецЕсли;
    
КонецПроцедуры

Этот метод очень удобен, однако используйте его осмотрительно: если в системе открыто много форм с тяжелыми алгоритмами в ОбработкаОповещения, массовая рассылка сигналов может вызвать кратковременное зависание интерфейса.

Решение №3: Автоматическое обновление через ОповеститьОбИзменении()

Выясним, как обновить интерфейс вообще без написания лишнего кода. В платформе «1С:Предприятие 8» заложен мощный механизм автоматического отслеживания изменений данных. Если на вашей родительской форме расположен динамический список (например, список договоров контрагента) с заполненным свойством «Основная таблица», то платформа может обновить его самостоятельно.

Для этого в дочерней форме после записи объекта достаточно выполнить всего одну строчку кода на клиенте:


&НаКлиенте
Процедура ПослеЗаписиНаКлиенте(ПараметрыЗаписи)
    
    // Оповещаем платформу об изменении конкретной ссылки
    ОповеститьОбИзменении(Объект.Ссылка);
    
КонецПроцедуры

Платформа самостоятельно проанализирует все открытые формы, найдет динамические списки, отображающие данный тип данных (в нашем примере — справочник договоров), и инициирует их немедленное обновление. При этом разработчику не требуется перехватывать это событие в родительской форме — все происходит на уровне платформы.

Решение №4: Использование механизмов Библиотеки Стандартных Подсистем (БСП)

Если ваша конфигурация разработана на базе современных типовых решений фирмы «1С» (таких как ERP, Комплексная автоматизация, Управление торговлей или Бухгалтерия предприятия), то вы можете задействовать готовые механизмы интеграции БСП. В общем модуле ОбщегоНазначенияКлиент содержится специализированный метод для оповещения системы об изменениях объектов.

Рассмотрим пример его использования в дочерней форме:


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    
    // Метод принимает как одиночную ссылку, так и массив измененных ссылок
    ОбщегоНазначенияКлиент.ОповеститьОбИзмененииОбъектов(Объект.Ссылка);
    
КонецПроцедуры

Преимущество этого метода перед стандартным ОповеститьОбИзменении() заключается в том, что БСП берет на себя дополнительный контроль транзакций, оптимизирует групповые обновления и снижает вероятность возникновения ошибок блокировок в многопользовательской среде.

Решение №5: Передача данных через Владельца формы (ОбработкаВыбора)

Для сценариев, когда дочерняя форма открывается специально для того, чтобы пользователь выбрал там какое-то значение или настроил структуру данных, используется классическая концепция «Владелец формы».

Проанализируем этот подход на практике. При открытии дочерней формы мы передаем родительскую форму в качестве владельца:


&НаКлиенте
Процедура ВыбратьСпецификацию(Команда)
    
    ПараметрыВыбора = Новый Структура;
    // Указываем владельца формы третьим параметром метода ОткрытьФорму
    ОткрытьФорму("Справочник.Спецификации.Форма.ФормаВыбора", ПараметрыВыбора, ЭтаФорма);
    
КонецПроцедуры

В дочерней форме выбора при окончании работы мы вызываем метод ОповеститьОВыборе():


&НаКлиенте
Процедура ТаблицаВыбораВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    // Передаем выбранную ссылку владельцу формы
    ОповеститьОВыборе(ВыбраннаяСтрока);
    
КонецПроцедуры

В родительской форме перехват данных осуществляется в стандартном обработчике события ОбработкаВыбора:


&НаКлиенте
Процедура ОбработкаВыбора(ВыбранноеЗначение, Источник)
    
    // Проверяем тип данных или источник и обновляем реквизиты формы
    Если ТипЗнч(ВыбранноеЗначение) = Тип("СправочникСсылка.Спецификации") Тогда
        Объект.Спецификация = ВыбранноеЗначение;
        // Дополнительно обновляем связанные элементы интерфейса
        ЭтаФорма.ОбновитьОтображениеДанных();
    КонецЕсли;
    
КонецПроцедуры

Как передавать большие объемы данных между формами

Иногда возникает необходимость передать из дочерней формы обратно в родительскую не просто одну ссылку или короткую структуру параметров, а тяжелые данные (например, большую заполненную таблицу значений). Передавать такие таблицы напрямую через клиентские параметры категорически не рекомендуется, поскольку это создает колоссальную нагрузку на каналы связи между клиентом и сервером.

В таких случаях мы рекомендуем использовать механизм Временного хранилища. Давайте разберем этот процесс по шагам:

  1. В родительской форме перед открытием дочернего окна мы генерируем уникальный адрес во временном хранилище:
    
    &НаКлиенте
    Процедура ОткрытьФормуРедактированияТаблицы(Команда)
    
        // Создаем адрес в хранилище, привязав его к уникальному идентификатору формы
        АдресДанных = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
        
        ПараметрыФормы = Новый Структура;
        ПараметрыФормы.Вставить("АдресДанных", АдресДанных);
        
        Оповещение = Новый ОписаниеОповещения("ПослеРедактированияТаблицы", ЭтаФорма, АдресДанных);
        ОткрытьФорму("Обработка.РедакторТаблиц.Форма", ПараметрыФормы, ЭтаФорма, , , , Оповещение);
    
    КонецПроцедуры
    
  2. В дочерней форме в процедуре ПриСозданииНаСервере мы считываем адрес из параметров, сохраняем его в реквизит формы, получаем данные, обрабатываем их, а перед закрытием — помещаем измененную таблицу обратно в то же самое хранилище на сервере:
    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        
        // Сохраняем адрес во временном реквизите формы для последующего использования
        АдресХранилища = Параметры.АдресДанных;
        
        // Извлекаем переданные данные, если они там были помещены ранее
        ТаблицаДляРедактирования = ПолучитьИзВременногоХранилища(АдресХранилища);
        
    КонецПроцедуры
    
    &НаСервере
    Процедура СохранитьТаблицуВХранилище()
        
        // Помещаем измененную таблицу обратно в хранилище перед закрытием
        ПоместитьВоВременноеХранилище(Объект.НашаТаблица.Выгрузить(), АдресХранилища);
        
    КонецПроцедуры
    
  3. При закрытии дочерней формы мы просто сообщаем родительской форме о завершении процесса. Родительская форма в обработчике оповещения считывает обновленные данные из хранилища на сервере и выводит их на экран.

Три основных метода обновления данных в родительской форме

Когда родительская форма успешно перехватила событие закрытия дочернего окна, перед разработчиком встает задача непосредственно обновить отображение данных на экране. Платформа 1С предлагает для этого три различных метода в зависимости от архитектуры данных:

  1. Элементы.ИмяСписка.Обновить(); — применяется, если на форме отображается динамический список (произвольный или с основной таблицей). Это самый легкий и быстрый метод. Он повторно обращается к базе данных на сервере только для того, чтобы актуализировать строки списка, отображаемые на экране в текущий момент.
  2. ЭтаФорма.Прочитать(); — применяется в формах объектов (справочников, документов). Если в дочерней форме вы изменили текущий редактируемый объект родительской формы в базе данных, этот метод заставит форму перечитать все свои реквизиты с сервера заново. Обратите внимание, что несохраненные данные пользователя на форме при этом будут перезаписаны актуальными данными из СУБД.
  3. ЭтаФорма.ОбновитьОтображениеДанных(); — применяется, если данные в базе не менялись, но вам необходимо принудительно обновить связи между реквизитами формы и элементами интерфейса. Например, чтобы пересчиталось условное оформление динамического списка, изменилась доступность кнопок или видимость панелей после проведенных расчетов на клиенте. Этот метод выполняется без обращения к серверу базы данных, что делает его крайне производительным.

Мы надеемся, что этот подробный разбор взаимодействия управляемых форм поможет вам проектировать стабильные, быстрые и соответствующие стандартам «1С» интерфейсы. Удачи в программировании!

← На главную