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

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

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

В этой статье мы подробно разберем, почему стандартные методы могут давать сбой, и проанализируем правильные подходы к решению этой задачи, исключающие блокировки базы данных и «размножение» одинаковых окон.

Разбор типичной ошибки: почему форма всегда считается закрытой

Рассмотрим ситуацию, с которой столкнулся автор темы. В обработке проведения документа выполняется следующий код:


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

Проанализируем, почему этот код не работает так, как ожидает разработчик. Основная причина кроется в методе СоздатьДокумент(). Каждый раз, когда вызывается этот метод, в оперативной памяти создается новый экземпляр объекта. Для системы 1С каждый такой экземпляр — это абсолютно новый, еще не существующий в базе данных объект.

Когда мы вызываем ПолучитьФорму с ключом уникальности для нового объекта, платформа ищет открытую форму именно для этого конкретного экземпляра. Поскольку экземпляр только что создан, его форма априори не может быть открыта. Таким образом, свойство ФормаДМ.Открыта() всегда будет возвращать Ложь, а ключ уникальности "Уникальность" будет применяться только к текущему сеансу работы с этим конкретным (новым) объектом в памяти.

Проблема транзакций и клиент-серверной архитектуры

Выясним еще одну критически важную деталь: автор пытается открыть форму внутри процедуры ОбработкаПроведения. Это является грубой архитектурной ошибкой по нескольким причинам:

  1. Транзакция базы данных. Проведение документа всегда выполняется внутри транзакции. Если в этот момент программно открыть окно и передать управление пользователю, транзакция останется открытой (незавершенной) до тех пор, пока код не продолжит выполнение. Это приведет к жестким блокировкам таблиц базы данных. Другие пользователи не смогут записывать или проводить документы, пока наше окно открыто.
  2. Контекст выполнения. В клиент-серверном варианте код ОбработкаПроведения исполняется на сервере. На сервере 1С не существует понятия «Форма» или «Окно». Попытка вызвать ПолучитьФорму() в серверном контексте приведет к ошибке выполнения.

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

Решение №1: Правильное использование Ключа Уникальности и ссылки

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

  1. Перенесем логику открытия формы из ОбработкаПроведения в процедуру ПослеЗаписи(), которая выполняется на клиенте после завершения транзакции.
  2. Перед созданием нового документа проверим, не создавался ли он ранее на основании текущего.
  3. Если документ уже существует, будем получать форму его ссылки.

Посмотрим на пример реализации этого подхода:


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

Решение №2: Использование глобальной коллекции «Окна»

Если по каким-то причинам использование ключа уникальности через ссылку невозможно (например, документ еще не записан и не должен записываться до внесения правок пользователем), мы можем проанализировать список всех открытых окон приложения. В обычном приложении 1С это можно сделать через перебор коллекции открытых форм.

Рассмотрим, как это реализовать:


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

Этот способ позволяет найти уже открытую форму, даже если объект в ней еще не записан в базу. Если функция вернула форму, мы просто вызываем Форма.Активизировать(), вместо того чтобы создавать новую.

Дополнительные рекомендации

Проанализируем еще несколько важных моментов, которые помогут сделать работу интерфейса более стабильной:

Подводя итог, можно сказать: чтобы проверка «открыта форма или нет» работала корректно, всегда опирайтесь на ссылку объекта и ключ уникальности, связанный с этой ссылкой, и никогда не выполняйте интерфейсные операции внутри транзакций проведения.

← На главную