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