При разработке интерфейсов в 1С (особенно на обычных формах) часто возникает задача: ограничить выбор пользователя динамическим списком, сформированным по определенным условиям. Например, показывать в поле ввода «Сотрудник» только тех работников, которые занимают определенную должность. Однако начинающие разработчики часто сталкиваются со следующей проблемой: список выбора заполняется корректно, пользователь видит нужные строки, нажимает на одну из них, но после выбора поле ввода на форме мгновенно очищается, а значение в документе не сохраняется.
В этой статье мы подробно разберем, почему возникает такая ситуация, проанализируем типичные ошибки в коде и рассмотрим правильные варианты реализации этой задачи в соответствии со стандартами разработки фирмы 1С.
Главная причина, по которой значение не сохраняется в реквизите документа — это несоответствие типов данных (несовпадение типов). 1С является системой со строгой типизацией реквизитов базы данных. Если реквизит вашего документа (например, Сотрудник) имеет тип СправочникСсылка.Сотрудники, то система ожидает записать в него именно уникальную ссылку на элемент справочника (GUID), а не текстовую строку.
Давайте посмотрим, как работает стандартный элемент управления формы типа «Поле ввода». Когда пользователь выбирает значение из выпадающего списка, платформа берет свойство Значение выбранного элемента списка и пытается сопоставить его с типом данных реквизита, к которому привязано это поле ввода:
Именно эта ошибка и произошла у автора темы на форуме. Давайте проанализируем его исходный код, выявим архитектурные недочеты и исправим их.
---
Посмотрим на код, который привел к неработающему интерфейсу:
Процедура НарядПриИзменении(Элемент)
Заявка = Наряд.Заявка;
Наряд = Наряд.Ссылка;
Работа.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сотрудники.Наименование КАК Сотрудник
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность.Наименование = ""Рабочий""";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
ЭлементыФормы.Сотрудник.СписокВыбора.Очистить();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ЭлементыФормы.Сотрудник.СписокВыбора.Добавить(Строка(ВыборкаДетальныеЗаписи.Сотрудник));
КонецЦикла;
КонецПроцедуры
Проанализируем эту процедуру детально. Здесь допущено сразу несколько серьезных ошибок разного уровня — от синтаксических до архитектурных.
В тексте запроса разработчик выбирает поле Сотрудники.Наименование, которое имеет тип Строка. Далее при заполнении списка выбора используется конструкция:
ЭлементыФормы.Сотрудник.СписокВыбора.Добавить(Строка(ВыборкаДетальныеЗаписи.Сотрудник));
Метод Добавить() для объекта СписокЗначений принимает параметры следующим образом: Добавить(<Значение>, <Представление>). Если передать только один параметр, платформа запишет его и в качестве технического значения, и в качестве текста на экране. Таким образом, в список выбора попадает обычная текстовая строка. При попытке выбора этой строки платформа пытается записать текст в ссылку, что закономерно приводит к очистке поля.
Строка кода Наряд = Наряд.Ссылка; выглядит крайне странно. Если переменная Наряд изначально содержит ссылку на документ, то обращение к свойству Ссылка избыточно. Если же переменная содержала объект документа (что в контексте обработчика события формы маловероятно), то такое переопределения переменной "на лету" может нарушить логику работы формы.
Разработчик поместил код заполнения списка сотрудников в обработчик НарядПриИзменении. Это означает, что список сотрудников обновится только тогда, когда пользователь выберет или поменяет документ в поле Наряд. Если пользователь просто откроет существующий документ и решит сразу изменить сотрудника, список выбора окажется абсолютно пустым! Логика формирования списков выбора должна быть привязана к событиям самого поля ввода, а не сторонних реквизитов.
Фильтрация по строковому представлению должности: Сотрудники.Должность.Наименование = "Рабочий" — это грубое нарушение стандартов разработки 1С. Во-первых, если в справочнике должностей случайно переименуют «Рабочий» в «Рабочий (цех №1)» или поставят лишний пробел, программа перестанет работать. Во-вторых, обращение через точку к реквизитам связанных справочников снижает общую производительность системы.
---
Давайте исправим код так, чтобы в список выбора попадали ссылки, а пользователи видели понятные текстовые наименования. Для этого мы будем использовать перегрузку метода Добавить() с двумя параметрами:
СправочникСсылка.Сотрудники), которая запишется в базу данных.Рассмотрим правильный вариант запроса и заполнения списка:
// В запросе выбираем Ссылку и Наименование сотрудника
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сотрудники.Ссылка КАК Ссылка,
| Сотрудники.Наименование КАК Наименование
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность = &ДолжностьРабочий";
// Передаем правильную ссылку на должность через параметры запроса вместо строки
Запрос.УстановитьПараметр("ДолжностьРабочий", Справочники.Должности.НайтиПоНаименованию("Рабочий"));
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
ЭлементыФормы.Сотрудник.СписокВыбора.Очистить();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Первым параметром передаем саму ССЫЛКУ, вторым - ее текстовое представление
ЭлементыФормы.Сотрудник.СписокВыбора.Добавить(
ВыборкаДетальныеЗаписи.Ссылка,
ВыборкаДетальныеЗаписи.Наименование
);
КонецЦикла;
Теперь, когда пользователь выберет значение из списка, платформа сопоставит выбранную ссылку СправочникСсылка.Сотрудники с типом реквизита документа, и данные сохранятся без каких-либо проблем.
---
Для того чтобы сделать интерфейс максимально надежным и быстрым, воспользуемся официальной методической поддержкой фирмы 1С. Правильный паттерн проектирования заключается в использовании события НачалоВыбора у самого элемента управления (поля ввода) Сотрудник.
Разберем по шагам, как это реализовать на обычных формах:
Сотрудник на форме.НачалоВыбора и сгенерируем процедуру-обработчик на клиенте.СтандартнаяОбработка = Ложь).ВыбратьИзСписка(), чтобы показать красивое выпадающее меню прямо под полем ввода.Посмотрим на пример кода, написанного по стандартам ИТС:
Процедура СотрудникНачалоВыбора(Элемент, СтандартнаяОбработка)
// Шаг 1. Отменяем стандартное открытие формы списка сотрудников
СтандартнаяОбработка = Ложь;
// Шаг 2. Создаем временный список для выбора
СписокВыбора = Новый СписокЗначений;
// Шаг 3. Заполняем его актуальными данными из базы данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сотрудники.Ссылка КАК Ссылка,
| Сотрудники.Наименование КАК Представление
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| НЕ Сотрудники.ПометкаУдаления"; // Пример простого условия на актуальность
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СписокВыбора.Добавить(Выборка.Ссылка, Выборка.Представление);
КонецЦикла;
// Шаг 4. Показываем список пользователю "под" элементом формы
ВыбранныйЭлемент = ВыбратьИзСписка(СписокВыбора, Элемент);
// Шаг 5. Если пользователь сделал выбор, записываем значение в реквизит
Если ВыбранныйЭлемент <> Неопределено Тогда
// Записываем именно Значение (там хранится наша Ссылка)
Сотрудник = ВыбранныйЭлемент.Значение;
КонецЕсли;
КонецПроцедуры
Если вы работаете в современном интерфейсе «Такси» на управляемых формах, то модальный метод ВыбратьИзСписка() использовать нельзя. Платформа 1С v8.3 требует асинхронного подхода.
На управляемых формах для решения этой задачи мы используем метод ПоказатьВыборИзСписка() с описанием оповещения. Посмотрим на этот пример:
&НаКлиенте
Процедура СотрудникНачалоВыбора(Элемент, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
СписокВыбора = Новый СписокЗначений;
// На управляемых формах запрос выполняется на сервере
ПолучитьСписокСотрудниковНаСервере(СписокВыбора);
// Описываем процедуру, которая выполнится после того, как пользователь выберет значение
Оповещение = Новый ОписаниеОповещения("СотрудникЗавершениеВыбора", ЭтотОбъект);
ПоказатьВыборИзСписка(Оповещение, СписокВыбора, Элемент);
КонецПроцедуры
&НаКлиенте
Процедура СотрудникЗавершениеВыбора(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
Если ВыбранныйЭлемент <> Неопределено Тогда
// Записываем Ссылку из выбранного элемента в реквизит объекта
Объект.Сотрудник = ВыбранныйЭлемент.Значение;
КонецЕсли;
КонецПроцедуры
Проанализировав оба подхода, можно сделать вывод: правильное управление типами данных и выбор верных обработчиков событий платформы гарантируют быструю, стабильную работу интерфейса программы и исключают ошибки с потерей введенных данных.