Как правильно установить динамические параметры выбора для поля составного типа в 1С:Предприятии 8.3?

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

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

Изначальная ситуация, описанная пользователем, такова: есть поле ввода составного типа, например, оно может хранить ссылку на документ ЗаявкаНаРасходованиеДенежныхСредств или ПриобретениеТоваровУслуг. Цель состоит в том, чтобы после того, как пользователь выберет один из этих типов, для последующего выбора конкретного документа был установлен отбор по поставщику (либо по Контрагенту, либо по Партнеру, в зависимости от выбранного типа документа). Для этого используется обработчик ОбработкаВыбора, который вызывает серверную процедуру для установки СвязиПараметровВыбора.

Суть проблемы:

В первой попытке выбора документа:

  1. Пользователь нажимает на кнопку выбора поля ввода.
  2. Открывается окно выбора типа.
  3. Пользователь выбирает, например, ЗаявкаНаРасходованиеДенежныхСредств.
  4. Срабатывает клиентская процедура ОбработкаВыбора, которая вызывает серверную процедуру.
  5. На сервере устанавливается СвязьПараметровВыбора (например, по Контрагенту).
  6. Проблема: Вместо того чтобы открыть форму выбора конкретных документов с установленным отбором, поле ввода остаётся пустым или не инициирует второй этап выбора, и мы получаем пустую ссылку.

При последующих попытках выбора (втором и далее):

  1. Пользователь снова нажимает на кнопку выбора.
  2. Обработчик ОбработкаВыбора срабатывает сначала при выборе типа, затем при выборе конкретного документа, и все работает корректно.

Такое поведение приводит к недоумению, так как непонятно, почему первый раз выбор не доходит до конца, а последующие работают без сбоев.

Причина некорректной работы обработчика ОбработкаВыбора

Мы выясним причину, почему исходный подход не работает должным образом. Ключевая проблема заключается в вызове серверного метода, изменяющего свойства формы, непосредственно из клиентского обработчика ОбработкаВыбора.

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

Именно это происходило в исходном коде: клиентский обработчик Документ1СОбработкаВыбора вызывал серверную процедуру Документ1СОбработкаВыбораНаСервере, которая в свою очередь изменяла свойство поля формы Элементы.Документы1СДокумент1С.СвязиПараметровВыбора. Изменение данного свойства является серверным действием. Когда платформа обнаруживает такой вызов внутри ОбработкаВыбора, она прерывает дальнейший процесс выбора для предотвращения непредсказуемого поведения, особенно когда дело доходит до второго этапа выбора значения.


// Исходный код пользователя, который приводит к ошибке:
&НаКлиенте
Процедура Документ1СОбработкаВыбора(Элемент, ВыбранноеЗначение, ДополнительныеДанные, СтандартнаяОбработка)
    // Этот вызов серверной процедуры внутри ОбработкаВыбора запрещен,
    // если серверная процедура меняет свойства формы
    Документ1СОбработкаВыбораНаСервере(ВыбранноеЗначение); 
КонецПроцедуры

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

Когда мы выбираем тип в первый раз, ВыбранноеЗначение является объектом типа (например, Тип("ДокументСсылка.ЗаявкаНаРасходованиеДенежныхСредств")). Серверный вызов происходит, связи устанавливаются, но процесс выбора прерывается, и поле остается пустым. При последующих попытках, возможно, внутренние механизмы платформы "помнят" уже выбранный тип или состояние, что позволяет второму этапу выбора пройти корректно.

Оптимальные подходы к решению проблемы

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

1. Использование события ПриИзменении

Событие ПриИзменении является одним из наиболее подходящих для данной задачи. В отличие от ОбработкаВыбора, оно не имеет таких строгих ограничений на серверные вызовы, что позволяет нам динамически изменять параметры выбора формы.

Принцип работы:

  1. Событие ПриИзменении срабатывает каждый раз, когда значение в поле ввода изменяется. Для полей составного типа это произойдет дважды:
    1. После того, как пользователь выберет тип документа (ВыбранноеЗначение или текущее значение поля будет иметь выбранный тип).
    2. После того, как пользователь выберет конкретное значение (ссылку на документ).
  2. В обработчике ПриИзменении мы сможем определить текущий тип значения поля ввода и, если это необходимо, выполнить серверный вызов для установки СвязиПараметровВыбора.
  3. Чтобы обеспечить корректное открытие формы выбора после определения типа, нам может потребоваться программно очистить текущее значение поля и установить ему пустую ссылку того типа, который был выбран. Это "подскажет" платформе, какой тип объектов следует открывать для выбора.

Пошаговая реализация:

  1. Создадим обработчик события ПриИзменении для нашего поля ввода (например, Документ1С).
  2. Внутри этого обработчика на клиенте определим текущий тип значения поля.
  3. Вызовем серверную процедуру, передав ей выбранный тип.
  4. На сервере установим СвязиПараметровВыбора для поля.
  5. Важный шаг: Если на первом этапе выбора (только тип) поле уже имеет значение, или мы хотим переинициировать выбор с новыми параметрами, мы должны очистить его и присвоить пустую ссылку нужного типа.

Давайте адаптируем ваш код для использования ПриИзменении:


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

    // Определяем тип текущего значения
    ТипЗначения = ТипЗнч(ТекущееЗначение);

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

    КонецЕсли;
КонецПроцедуры

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

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

2. Использование методов Оповестить и ОбработкаОповещения

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

Принцип работы:

  1. В клиентской процедуре ОбработкаВыбора мы определяем выбранный тип.
  2. Вместо прямого серверного вызова мы используем метод Оповестить (или ОповеститьОВыборе), передавая выбранный тип в качестве параметра.
  3. Метод Оповестить инициирует вызов процедуры ОбработкаОповещения в текущей форме (или в форме-владельце, если он указан).
  4. В процедуре ОбработкаОповещения мы можем выполнить серверный вызов, так как эта процедура не имеет таких строгих ограничений, как ОбработкаВыбора, и на сервере установить СвязиПараметровВыбора.
  5. После установки связей мы можем либо просто завершить процесс, ожидая, что пользователь снова нажмет на кнопку выбора, либо программно инициировать открытие формы выбора.

Пошаговая реализация:

  1. Оставим ваш клиентский обработчик Документ1СОбработкаВыбора, но заменим прямой вызов сервера на Оповестить.
  2. Создадим процедуру ОбработкаОповещения на клиенте.
  3. В ОбработкаОповещения выполним серверный вызов для установки СвязиПараметровВыбора.

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

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

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

        // Вызываем серверную процедуру для установки связей
        УстановитьСвязиПараметровВыбораНаСервере(ВыбранныйТип, ЭлементПоляВвода);

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

        // Открываем форму выбора для поля, если необходимо,
        // но чаще достаточно установки пустой ссылки и повторного клика пользователя.
        // ЭлементПоляВвода.Выбрать(); // Может не работать, если нужно, чтобы это делал пользователь
                                   // или если платформа сама откроет форму после изменения значения.
    КонецЕсли;
КонецПроцедуры

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

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

3. Реализация отдельной формы выбора (авторское решение)

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

Принцип работы:

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

Преимущества и недостатки:

Пример вызова такой формы из обработчика НачалоВыбора:


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

    // Открываем нашу кастомную форму выбора
    ОткрытьФорму("ОбщаяФорма.ФормаВыбораДокументовСОтбором", , Элемент);
КонецПроцедуры

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

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

Этот подход позволяет создать полностью уникальный пользовательский опыт, но требует значительных трудозатрат.

Рекомендации и важные моменты

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

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

← На главную