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