Когда мы разрабатываем формы в 1С, часто возникает необходимость предоставить пользователю возможность выбора значений из динамически формируемого списка. Особенно это актуально для полей ввода с механизмом автоподбора, где данные могут зависеть от внешних сервисов или требовать значительного времени на формирование. Стандартный механизм автоподбора в 1С позволяет управлять списком выбора через событие АвтоПодбор, но возникают сложности, если формирование этого списка занимает много времени или требует асинхронной обработки. В этой статье мы подробно рассмотрим, как программно управлять отображением списка выбора, обходя стандартные ограничения и используя различные методы.
Исходная проблема, с которой сталкиваются разработчики, заключается в том, что стандартный вызов списка выбора происходит сразу после завершения ввода текста, тогда как асинхронная загрузка данных (например, через обработчик ожидания или веб-сервисы) может завершиться позже. Это приводит к тому, что пользователю показывается либо старый, либо пустой список, а актуальные данные появляются с задержкой, либо вообще не отображаются автоматически. Мы разберем подходы, которые позволяют нам управлять этим процессом.
Давайте сначала проанализируем, как работает стандартный механизм автоподбора в 1С. Когда мы назначаем полю ввода свойство РежимВыбораИзСписка = ВыборИзСписка.Авто, платформа ожидает, что мы предоставим данные для выбора либо через свойство СписокВыбора, либо через обработчик события АвтоПодбор.
В событии АвтоПодбор мы можем заполнить параметр ДанныеВыбора коллекцией значений. Платформа после выполнения нашего кода отобразит этот список. Однако, если генерация списка значений требует обращения к внешним сервисам или выполнения длительных запросов, мы сталкиваемся с проблемой. Платформа будет "подвисать", ожидая завершения выполнения нашего кода в АвтоПодборе, что неприемлемо для пользовательского опыта.
Попытки вынести длительную операцию в ПодключитьОбработчикОжидания сталкиваются с тем, что стандартный список выбора уже отображен до того, как наш асинхронный обработчик завершится. И после его завершения платформа не обновляет список выбора автоматически. Более того, как мы выяснили в дискуссии, даже использование ОбработчикаОжидания для тяжелых операций в итоге приводит к "подвисаниям", так как код все равно выполняется в основном потоке.
Одним из наиболее гибких способов управления списком является прямое заполнение свойства СписокВыбора элемента поля ввода. Если этот список уже заполнен, платформа автоматически фильтрует его при вводе текста. Однако, чтобы список отобразился "под" полем ввода сразу после программного изменения, иногда нам требуется принудительно его открыть.
Мы можем эмулировать нажатие горячих клавиш, которые обычно открывают выпадающий список (например, Alt + Стрелка вниз или просто Стрелка вниз). Это заставит платформу развернуть список выбора, привязанный к полю, при этом не теряя фокуса и не стирая введенный текст. Это более "чистый" подход по сравнению с удалением и возвратом символа, как было предложено в одном из сообщений.
Разберем пример кода для эмуляции нажатия клавиш:
// В обработчике, который завершил асинхронную загрузку данных
// и заполнил свойство СписокВыбора
// Получаем COM-объект Wscript.shell для эмуляции нажатий клавиш
Попытка
WshShell = Новый COMОбъект("Wscript.shell");
// Эмулируем нажатие Alt + СтрелкаВниз, чтобы открыть список выбора
WshShell.SendKeys("%{DOWN}");
Исключение
// Обработка ошибки, если COM-объект недоступен (например, в веб-клиенте)
Сообщить("Не удалось эмулировать нажатие клавиш: " + ОписаниеОшибки());
КонецПопытки;
Важный момент: помните, что методы с COMОбъектом("Wscript.shell") не работают в веб-клиенте (браузере). Для веб-клиента единственным надежным способом программно показать список без потери фокуса остается заполнение свойства СписокВыбора и ожидание естественного срабатывания механизма платформы, либо использование других, более сложных, клиентских расширений.
Для сложных операций, которые занимают значительное время (например, запросы к внешним веб-сервисам), мы настоятельно рекомендуем использовать фоновые задания. Это позволит выполнить тяжелую работу на сервере, не блокируя пользовательский интерфейс. В сочетании с событием ИзменениеТекстаРедактирования мы можем добиться полностью асинхронного и неблокирующего интерфейса.
Используем событие ИзменениеТекстаРедактирования:
Вместо АвтоПодбора, который имеет встроенную паузу и может быть менее предсказуемым в асинхронном сценарии, мы используем событие ИзменениеТекстаРедактирования. Оно срабатывает при каждом нажатии клавиши в поле ввода. Здесь мы можем проверять длину введенного текста и, если она достаточна (например, больше 2-3 символов), запускать фоновое задание.
&НаКлиенте
Процедура ИмяПоляВводаИзменениеТекстаРедактирования(Элемент, Текст)
// Добавим небольшую задержку, чтобы не отправлять запрос при каждом нажатии
// и дать пользователю возможность ввести несколько символов
ОтключитьОбработчикОжидания("ЗапуститьПоискНаСервере");
Если СтрДлина(Текст) >= 2 Тогда
ПодключитьОбработчикОжидания("ЗапуститьПоискНаСервере", 0.3, Истина); // 300 мс задержка
Иначе
Элемент.СписокВыбора.Очистить(); // Очищаем, если текста мало
КонецЕсли;
КонецПроцедуры
Запуск фонового задания на сервере:
В функции, которую мы вызываем через обработчик ожидания (или напрямую, если нет необходимости в задержке), мы запускаем фоновое задание. Фоновое задание будет выполнять долгую операцию (например, запрос к сервису) и возвращать результат.
&НаКлиенте
Процедура ЗапуститьПоискНаСервере()
ТекущийТекст = Элементы.ИмяПоляВвода.ТекстРедактирования;
Если ПустаяСтрока(ТекущийТекст) Тогда
Элементы.ИмяПоляВвода.СписокВыбора.Очистить();
Возврат;
КонецЕсли;
// Запускаем фоновое задание для поиска данных
ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьРезультатПоиска", ЭтотОбъект, ТекущийТекст);
ИдентификаторЗадания = ВыполнитьДлительнуюОперациюНаСервереАсинхронно(ОписаниеОповещения, ТекущийТекст);
// Можно сохранить ИдентификаторЗадания, чтобы отменять старые запросы, если придут новые
// Например, в реквизит формы: ТекущийИдентификаторПоиска = ИдентификаторЗадания;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ВыполнитьДлительнуюОперациюНаСервереАсинхронно(ОписаниеОповещения, ТекстДляПоиска)
// Здесь происходит вызов сервиса или сложный запрос
// Например, МодульСервиса.ПолучитьДанныеПоТексту(ТекстДляПоиска)
// Возвращаем результат
МассивДанныхДляВыбора = Новый Массив();
МассивДанныхДляВыбора.Добавить(Новый ЗначениеСписка("Элемент 1", "Код1"));
МассивДанныхДляВыбора.Добавить(Новый ЗначениеСписка("Элемент 2", "Код2"));
МассивДанныхДляВыбора.Добавить(Новый ЗначениеСписка("Еще один элемент", "Код3"));
// Имитация задержки
Для Инд = 1 По 10000000 Цикл КонецЦикла;
// После выполнения длительной операции вызываем оповещение на клиенте
ВызватьОповещение(ОписаниеОповещения, МассивДанныхДляВыбора);
КонецФункции
Обработка результата на клиенте:
Когда фоновое задание завершено, срабатывает метод, указанный в ОписаниеОповещения. В нем мы обновляем СписокВыбора и принудительно открываем его.
&НаКлиенте
Процедура ОбработатьРезультатПоиска(Результат, ДополнительныеПараметры) Экспорт
// ДополнительныеПараметры здесь - это ТекстДляПоиска, который мы передали
ТекстДляПоиска = ДополнительныеПараметры;
// КРИТИЧЕСКИ ВАЖНО: Проверить актуальность текста!
// Пользователь мог уже продолжить печать или удалить текст.
// Если текущий текст в поле ввода не соответствует тому, для которого был запрос,
// мы не должны показывать устаревший список.
Если ТекстДляПоиска <> Элементы.ИмяПоляВвода.ТекстРедактирования Тогда
Возврат; // Неактуальный запрос, игнорируем результат
КонецЕсли;
Элементы.ИмяПоляВвода.СписокВыбора.Очистить();
Для Каждого ЭлементСписка Из Результат Цикл
Элементы.ИмяПоляВвода.СписокВыбора.Добавить(ЭлементСписка.Значение, ЭлементСписка.Представление);
КонецЦикла;
// Теперь принудительно открываем список
Попытка
WshShell = Новый COMОбъект("Wscript.shell");
WshShell.SendKeys("%{DOWN}");
Исключение
// Для веб-клиента или если COM-объект недоступен, остаемся на естественном поведении
КонецПопытки;
КонецПроцедуры
Этот подход является наиболее надежным, так как он полностью асинхронен и не блокирует клиентский интерфейс, при этом позволяет нам точно контролировать момент отображения списка.
Если мы хотим полностью контролировать процесс автоподбора и предотвратить попытки платформы самостоятельно искать данные (например, в связанных ссылках), мы можем установить СтандартнаяОбработка = Ложь в событии АвтоПодбор.
&НаКлиенте
Процедура ИмяПоляВводаАвтоПодбор(Элемент, Текст, ДанныеВыбора, СтандартнаяОбработка)
// Отключаем стандартную обработку
СтандартнаяОбработка = Ложь;
// Здесь мы можем запустить свой асинхронный процесс,
// как описано выше (через ИзменениеТекстаРедактирования и фоновое задание).
// Если у нас уже есть готовые данные, мы можем их сразу же подставить:
// ДанныеВыбора.Добавить(Новый ЗначениеСписка("Мой элемент 1", "Э1"));
// ДанныеВыбора.Добавить(Новый ЗначениеСписка("Мой элемент 2", "Э2"));
// Если мы используем ДанныеВыбора, то платформа откроет список сама.
// Но если данные формируются долго, лучше использовать подход с Фоновыми заданиями
// и обновлением СписокВыбора после их готовности.
КонецПроцедуры
Установка СтандартнаяОбработка = Ложь дает нам полный контроль. Однако, чтобы список все же открылся, после заполнения свойства СписокВыбора или параметра ДанныеВыбора (если данные уже готовы), нам нужно либо полагаться на естественное поведение платформы (которое не всегда срабатывает мгновенно для СписокВыбора), либо использовать эмуляцию нажатия клавиш.
Метод ПоказатьВыборИзСписка(), предложенный в одном из сообщений, хоть и позволяет программно отобразить список, имеет существенный недостаток: он уводит фокус с поля ввода. Это означает, что пользователь не сможет продолжить печатать текст, что делает его непригодным для сценариев автоподбора, где важна непрерывность ввода.
Если асинхронное получение данных занимает некоторое время, мы можем улучшить пользовательский опыт, предоставляя визуальную индикацию. Мы можем программно включать или выключать кнопку выпадающего списка (свойство КнопкаСпискаВыбора = Истина или Ложь) у элемента поля ввода. Это может сигнализировать пользователю, что данные подгружаются или что список готов к развертыванию.
Например, во время запроса мы можем сделать кнопку недоступной или изменить ее вид, а после получения данных — вернуть ей обычное состояние и, возможно, принудительно открыть список.
Давайте подытожим наиболее эффективные подходы для программного управления списком выбора поля ввода в 1С при работе с асинхронными данными:
Используем асинхронность: Для длительных операций (запросы к сервисам, объемные расчеты) всегда выносите их в фоновые задания на сервере, а не просто в обработчики ожидания на клиенте. Это предотвращает блокировку пользовательского интерфейса.
Контролируем ввод: Используем событие ИзменениеТекстаРедактирования для запуска асинхронного поиска с небольшой задержкой (ПодключитьОбработчикОжидания), чтобы не перегружать сервер частыми запросами.
Обновляем СписокВыбора: После получения данных из фонового задания, заполняем свойство СписокВыбора элемента поля ввода.
Принудительно открываем список: Используем COMОбъект("Wscript.shell").SendKeys("%{DOWN}") для эмуляции нажатия Alt + Стрелка вниз. Помним про ограничения в веб-клиенте.
Проверяем актуальность: Перед отображением списка всегда сравниваем текущий Элементы.ИмяПоля.ТекстРедактирования с тем текстом, для которого был выполнен запрос. Это предотвращает показ устаревших или нерелевантных данных, если пользователь успел изменить ввод.
Предоставляем обратную связь: При необходимости используем свойство КнопкаСпискаВыбора для визуальной индикации состояния загрузки данных.
Следуя этим рекомендациям, мы можем создать отзывчивый и функциональный интерфейс для полей ввода с динамическим списком выбора, даже при необходимости выполнения длительных асинхронных операций.