В процессе разработки в среде обычных форм (например, в конфигурациях УТ 10.3, УПП 1.3) часто возникает задача изменить данные одного документа из формы другого окна или подбора. Типичная ситуация: из документа "Внутренний заказ" открывается вспомогательная форма (например, форма остатков), в которой пользователь меняет количество. После записи изменений вспомогательная форма должна "сказать" основной форме документа, что данные изменились и их нужно обновить.
Однако разработчики часто сталкиваются с проблемой: при попытке программно получить форму документа метод Открыта() возвращает Ложь, хотя форма явно видна на экране. Давайте разберем подробно, почему так происходит, как правильно получить доступ к открытой форме и какой архитектурный подход является наиболее верным.
Разберем, как платформа 1С управляет формами. Когда мы пишем код для получения формы, например:
ФормаДокумента = Документы.ВнутреннийЗаказ.ПолучитьФорму("ФормаДокумента");
Если ФормаДокумента.Открыта() Тогда
// Код обновления
КонецЕсли;
В этот момент система не ищет уже открытое окно на экране пользователя. Метод ПолучитьФорму() без дополнительных параметров создает в оперативной памяти **новый экземпляр** программного объекта формы. Этот новый объект действительно еще не открыт (так как мы не вызывали для него метод Открыть()), поэтому метод Открыта() закономерно возвращает Ложь.
В результате у нас есть две формы:
1. Та, которую видит пользователь (открытая).
2. Та, которую мы создали программно (невидимая и закрытая).
Обновление второй формы никак не повлияет на первую. Чтобы обратиться к **уже открытому** экземпляру, необходимо использовать механизм ключей уникальности.
Каждая форма в 1С (особенно формы объектов, таких как справочники и документы) имеет ключ уникальности. Это идентификатор, который позволяет платформе понять: нужно ли открыть новое окно или активировать уже существующее.
Для форм существующих документов ключом уникальности по умолчанию является **Ссылка** на этот документ.
Чтобы получить программный доступ к уже открытой форме, в метод ПолучитьФорму необходимо вторым параметром передать этот ключ.
// СсылкаНаДокумент - это ссылка на тот документ, форму которого мы ищем.
// Обычно она доступна в контексте или передается как параметр.
КлючУникальности = СсылкаНаДокумент;
ФормаДокумента = Документы.ВнутреннийЗаказ.ПолучитьФорму("ФормаДокумента", КлючУникальности);
Если ФормаДокумента.Открыта() Тогда
// Теперь мы работаем с тем самым окном, которое видит пользователь
ФормаДокумента.Обновить(); // Или другой метод обновления
КонецЕсли;
В теме на форуме автор пытался использовать в качестве ключа уникальности номер документа, что является ошибкой. Номер — это строка или число, а платформе для идентификации окна объекта нужна именно Ссылка.
Важный нюанс: Если документ новый (еще не записан в базу данных), у него нет ссылки. В этом случае ключом уникальности может выступать специальный объект, сгенерированный при создании формы, но получить к нему доступ "снаружи" сложнее. Поэтому для новых документов этот метод подходит плохо.
Более правильным архитектурным решением, на которое указывали опытные участники обсуждения, является установка связи между формами через параметр ВладелецФормы.
Когда мы открываем вспомогательную форму (например, форму подбора или остатков) из основной формы документа, мы должны явно указать, кто является её владельцем.
**Шаг 1. Открытие вспомогательной формы из документа**
В коде основной формы (например, при нажатии кнопки "Подбор") пишем:
// Параметр ЭтаФорма передается как владелец открываемой формы
ОткрытьФорму("Обработка.ПодборТоваров.Форма", , ЭтаФорма);
Или, если используется метод ПолучитьФорму:
ФормаПодбора = Обработки.ПодборТоваров.ПолучитьФорму("Форма", , ЭтаФорма);
ФормаПодбора.Открыть();
Теперь вспомогательная форма "знает", кто её открыл. У неё заполняется свойство ВладелецФормы.
**Шаг 2. Обращение к родительской форме**
Находясь во вспомогательной форме, мы можем обратиться к методам и данным родителя через ЭтаФорма.ВладелецФормы.
// Код во вспомогательной форме после выполнения действий
// Проверяем, что владелец вообще есть и это форма
Если ТипЗнч(ЭтаФорма.ВладелецФормы) = Тип("Форма") Тогда
// Вызываем экспортный метод родительской формы или стандартный метод
ЭтаФорма.ВладелецФормы.Обновить();
КонецЕсли;
Однако прямые вызовы методов владельца создают жесткую связность кода ("спагетти-код"). Если вы захотите открыть этот подбор из другого документа, где нет нужного метода, возникнет ошибка. Поэтому лучше использовать механизм оповещений.
Это самый надежный и универсальный способ обмена сигналами между формами в 1С. Он позволяет формам общаться, не зная деталей реализации друг друга. Логика работы следующая: 1. Вспомогательная форма после изменения данных "кричит" на всю систему: "Я изменила данные по такому-то документу!". 2. Основная форма "слушает" эфир. Если она слышит сигнал, касающийся её документа, она обновляется. Рассмотрим реализацию по шагам. **Шаг 1. Отправка оповещения из вспомогательной формы** После того как пользователь изменил количество и данные записались в базу (или были подготовлены к передаче), выполняем код:
// 1-й параметр: Имя события (строка-идентификатор)
// 2-й параметр: Данные (обычно Ссылка на измененный объект или структура параметров)
// 3-й параметр: Источник (необязательно, можно передать саму форму)
Оповестить("ИзмененоКоличествоВЗаказе", СсылкаНаЗаказ, ЭтаФорма);
**Шаг 2. Прием оповещения в основной форме документа**
В модуле формы документа (Внутренний заказ) необходимо найти или создать предопределенную процедуру ОбработкаОповещения.
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
// Фильтруем события. Нас интересует только наше.
Если ИмяСобытия = "ИзмененоКоличествоВЗаказе" Тогда
// Проверяем, касается ли это текущего документа
// (вдруг открыто 5 разных заказов, обновлять нужно только свой)
Если Параметр = Ссылка Тогда
// Выполняем действия по обновлению
// Вариант А: Перечитать данные из базы (если вспомогательная форма записала их в БД)
ЭтотОбъект.Прочитать();
// Вариант Б: Просто обновить отображение
ЭтаФорма.Обновить();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот метод хорош тем, что он работает независимо от того, как была получена форма, и не требует передачи сложных параметров при открытии.
В обсуждении была затронута важная тема: изменение данных документа в базе, пока его форма открыта у пользователя.
Если вы во вспомогательной форме получаете объект документа, меняете табличную часть и делаете Объект.Записать(), то версия документа в базе данных меняется.
В это время в основной форме у пользователя открыта **старая** версия объекта (прочитанная до записи). Если после этого пользователь попытается нажать "Записать" или "ОК" в основной форме, он получит сообщение об ошибке:
* *"Операция не может быть выполнена. Данные были изменены или удалены другим пользователем"*.
**Как избежать этой ошибки?**
Если вы используете механизм с записью в базе из вспомогательной формы, то в процедуре ОбработкаОповещения в основной форме обязательно нужно вызывать метод Прочитать().
// В модуле формы документа
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ДанныеЗаписаныВнешнейОбработкой" И Параметр = Ссылка Тогда
// Перечитываем объект из базы, чтобы актуализировать версию
// ВНИМАНИЕ: Это затрет несохраненные изменения пользователя в текущей форме!
ЭтотОбъект.Прочитать();
КонецЕсли;
КонецПроцедуры
**Более безопасный подход:** Не записывать документ во вспомогательной форме. Вместо этого передавать измененные данные (например, структуру или таблицу значений) через оповещение в основную форму, и уже основная форма сама изменит свою табличную часть, не обращаясь к базе данных. Пользователь сам нажмет кнопку "Записать", когда посчитает нужным. **Пример безопасной передачи данных:** Вспомогательная форма:
ПараметрыОбмена = Новый Структура;
ПараметрыОбмена.Вставить("Товар", ТекущийТовар);
ПараметрыОбмена.Вставить("НовоеКоличество", 10);
Оповестить("ОбновитьСтрокуЗаказа", ПараметрыОбмена);
Основная форма:
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ОбновитьСтрокуЗаказа" Тогда
// Ищем строку в ТЧ и меняем её в памяти, без записи в БД
НайденныеСтроки = Товары.НайтиСтроки(Новый Структура("Номенклатура", Параметр.Товар));
Для Каждого Стр Из НайденныеСтроки Цикл
Стр.Количество = Параметр.НовоеКоличество;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Такой подход исключает конфликты блокировок и делает интерфейс более предсказуемым для пользователя.