СписокЗначений в 1С 8.3: доступ по индексу, поиск, перебор, отбор и сортировка
Содержание
Чтобы получить значение из списка значений в 1С, возьмите элемент и обратитесь к его свойству Значение: Список[0].Значение или Список.Получить(0).Значение. Индексы начинаются с нуля. Если нужен элемент с конкретным значением, используйте Список.НайтиПоЗначению(Искомое) — метод вернёт элемент или Неопределено.
Частая ошибка новичков — считать, что Список[0] и есть значение. Нет: квадратные скобки возвращают объект ЭлементСпискаЗначений, у которого четыре свойства. Само значение лежит в одном из них. Всё, что ниже, относится к платформе 1С:Предприятие 8.3 и одинаково работает в любой конфигурации: БП, ЗУП, УТ, ERP или своей разработке.
Как устроен СписокЗначений и что возвращает обращение по индексу
Список значений — упорядоченная коллекция. Каждая её строка — объект ЭлементСпискаЗначений со свойствами:
| Свойство | Что хранит | Пример |
|---|---|---|
Значение | Само значение любого типа | ссылка на контрагента, число, строка |
Представление | Строка, которую видит пользователь | «ООО Ромашка (основной)» |
Пометка | Булево, флажок элемента | Истина |
Картинка | Картинка рядом с элементом в форме | картинка из общих картинок |
Отсюда два способа прямого доступа по номеру:
Список = Новый СписокЗначений;
Список.Добавить(100, "Сто");
Список.Добавить(200, "Двести");
Список.Добавить(300, "Триста");
// Оба варианта дают одинаковый результат — элемент с индексом 1
Элемент1 = Список[1];
Элемент2 = Список.Получить(1);
Сумма = Элемент1.Значение; // 200
Подпись = Элемент1.Представление; // "Двести"
Последний элемент берут через количество: Список[Список.Количество() - 1]. Первый — Список[0].
Обратная задача — узнать номер известного элемента. Для этого есть Список.Индекс(Элемент). Метод принимает именно элемент, а не значение. Чтобы получить индекс по значению, сначала найдите элемент, потом спросите его индекс.
Элемент = Список.НайтиПоЗначению(300);
Если Элемент <> Неопределено Тогда
Номер = Список.Индекс(Элемент); // 2
КонецЕсли;
💡 Если вам нужны только значения, без представлений и пометок, выгрузите их одним вызовом: Массив = Список.ВыгрузитьЗначения();. Получится обычный массив, где Массив[1] — уже само значение. Обратный метод — Список.ЗагрузитьЗначения(Массив).
Поиск в списке значений: по значению, по представлению, по идентификатору
По значению — встроенный метод
НайтиПоЗначению сравнивает значение элемента с искомым и возвращает первый найденный элемент. Если совпадений нет — Неопределено.
Найденный = Список.НайтиПоЗначению(Контрагент);
Если Найденный = Неопределено Тогда
Сообщить("Контрагента нет в списке");
Иначе
Найденный.Пометка = Истина;
КонецЕсли;
Сравнение идёт по равенству значений. Для ссылок это значит: тот же объект базы. Для строк регистр учитывается — «Москва» и «москва» считаются разными значениями.
Все совпадения, а не только первое
В списке могут быть одинаковые значения: метод Добавить дубли не проверяет. Если нужно найти все, пройдите по коллекции:
Функция ВсеЭлементыСоЗначением(Список, Искомое)
Результат = Новый Массив;
Для Каждого Элемент Из Список Цикл
Если Элемент.Значение = Искомое Тогда
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
По представлению — только перебором
Метода «найти по представлению» у списка значений нет. Пишите свою функцию:
Функция НайтиПоПредставлению(Список, Текст)
Для Каждого Элемент Из Список Цикл
Если Элемент.Представление = Текст Тогда
Возврат Элемент;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Для поиска без учёта регистра сравнивайте НРег(Элемент.Представление) = НРег(Текст).
По идентификатору — для форм
У каждого элемента есть внутренний идентификатор: Элемент.ПолучитьИдентификатор(). Он нужен, когда список лежит в реквизите формы и выведен в таблицу. Таблица формы хранит в ТекущаяСтрока именно идентификатор, а не индекс. Элемент по нему находит НайтиПоИдентификатору — пример ниже, в разделе про выбор пользователя.
Перебор всех элементов списка значений
Основной способ — Для Каждого. Он короткий и не требует думать об индексах:
Итого = 0;
Для Каждого Элемент Из Список Цикл
Итого = Итого + Элемент.Значение;
КонецЦикла;
Когда в теле цикла нужен номер строки, используйте цикл по счётчику:
Для Индекс = 0 По Список.Количество() - 1 Цикл
Элемент = Список[Индекс];
Сообщить(СтрШаблон("%1. %2 = %3", Индекс + 1, Элемент.Представление, Элемент.Значение));
КонецЦикла;
СтрШаблон сам приводит числа к строке, поэтому не нужно склеивать число со строкой через +. Для пустого списка цикл Для 0 По -1 просто не выполнится ни разу — ошибки не будет.
Не удаляйте элементы внутри Для Каждого по тому же списку. После удаления коллекция сдвигается, и часть элементов цикл пропустит.
Если удалять всё-таки надо, идите с конца. Количество зафиксируйте до цикла:
Всего = Список.Количество();
Для Сч = 1 По Всего Цикл
Элемент = Список[Всего - Сч];
Если Не Элемент.Пометка Тогда
Список.Удалить(Элемент);
КонецЕсли;
КонецЦикла;
Удаление с конца не сдвигает элементы, до которых цикл ещё не дошёл. Поэтому индекс Всего - Сч всегда указывает на нужную строку.
Отбор части элементов: три рабочих приёма
Встроенного метода «отфильтровать» у списка значений нет, в отличие от НайтиСтроки у таблицы значений. Выбирайте подход по задаче.
| Способ | Когда подходит | Минус |
|---|---|---|
| Собрать новый список в цикле | Исходный список ещё понадобится | Лишняя коллекция в памяти |
| Удалить лишнее с конца | Исходный список больше не нужен | Данные теряются безвозвратно |
| Передать список в запрос | Отбор по реквизитам объектов базы | Работает только на сервере |
Новый список по условию
Функция ТолькоОтмеченные(Список)
Результат = Новый СписокЗначений;
Для Каждого Элемент Из Список Цикл
Если Элемент.Пометка Тогда
Результат.Добавить(Элемент.Значение, Элемент.Представление, Истина);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Условие можно взять любое: значение больше порога, тип значения, текст в представлении (СтрНайти(Элемент.Представление, "склад") > 0).
Отбор через запрос
Если в списке лежат ссылки, а условие касается их реквизитов, не читайте реквизиты в цикле по одному. Отдайте весь список запросу — параметр типа СписокЗначений подходит для условия В:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка В (&Список)
| И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("Список", Список);
Отобранные = Новый СписокЗначений;
Отобранные.ЗагрузитьЗначения(Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка"));
Один запрос вместо сотни обращений к базе. На списке из 500 контрагентов разница заметна даже на глаз.
💡 Список отмеченных пользователем значений часто нужен как массив — для того же запроса или для передачи в другую процедуру. Соберите его функцией ТолькоОтмеченные, а затем вызовите ВыгрузитьЗначения().
Отбор флажками: пусть отметит пользователь
Иногда условие отбора знает не программист, а пользователь. Например, из двадцати складов нужно выгрузить остатки только по трём. Для этого у списка есть диалог с флажками — ПоказатьОтметкуЭлементов. Он открывает окно со всеми элементами, пользователь ставит галочки, а результат возвращается в процедуру-обработчик.
&НаКлиенте
Процедура ОтметитьСклады(Команда)
Склады.ЗаполнитьПометки(Ложь); // снять старые флажки
Оповещение = Новый ОписаниеОповещения("ПослеОтметкиСкладов", ЭтотОбъект);
Склады.ПоказатьОтметкуЭлементов(Оповещение, "Отметьте склады для отчёта");
КонецПроцедуры
&НаКлиенте
Процедура ПослеОтметкиСкладов(Список, ДополнительныеПараметры) Экспорт
Если Список = Неопределено Тогда
Возврат; // окно закрыто без подтверждения
КонецЕсли;
Отмеченные = Новый Массив;
Для Каждого Элемент Из Список Цикл
Если Элемент.Пометка Тогда
Отмеченные.Добавить(Элемент.Значение);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Здесь Склады — реквизит формы типа СписокЗначений. ЗаполнитьПометки(Ложь) в начале нужен, чтобы флажки от прошлого открытия не попали в новый отбор. Если, наоборот, удобнее предложить «всё отмечено, снимите лишнее», вызовите ЗаполнитьПометки(Истина).
Когда список значений — неудачный выбор для отбора
Если отбирать приходится часто, по нескольким полям сразу и на тысячах строк, список значений начинает мешать. У него всего одно поле данных. Сложное условие по реквизитам превращается в цепочку обращений через точку, и каждое такое обращение к ссылке — чтение из базы.
В такой задаче на сервере лучше сразу работать с таблицей значений: у неё есть колонки, индексы и метод НайтиСтроки по структуре отбора. Список значений оставьте для того, где он силён: короткие перечни, диалоги выбора, параметры запросов и передача данных между клиентом и сервером.
Сортировка списка перед тем, как брать значения
Сортировка меняет порядок элементов в самом списке. Отдельной копии метод не создаёт.
- 🔢
Список.СортироватьПоЗначению()— по возрастанию значения. - 🔤
Список.СортироватьПоПредставлению()— по тексту представления. - ↕️ Оба метода принимают направление:
НаправлениеСортировки.ВозрилиНаправлениеСортировки.Убыв.
Типовой приём — найти максимум или минимум без собственного цикла сравнения:
Копия = Список.Скопировать();
Копия.СортироватьПоЗначению(НаправлениеСортировки.Убыв);
Максимум = Копия[0].Значение;
Минимум = Копия[Копия.Количество() - 1].Значение;
Копия нужна, чтобы не испортить исходный порядок. Пользователь мог расставить элементы вручную, и после сортировки «на месте» его порядок пропадёт.
Если в списке лежат ссылки на справочники, порядок по значению не совпадает с алфавитным. Для списка, который увидит человек, сортируйте по представлению.
Сдвинуть один элемент вверх или вниз без полной сортировки можно методом Список.Сдвинуть(Элемент, Смещение). Отрицательное смещение двигает элемент к началу списка, положительное — к концу.
Значение, которое выбрал пользователь
Это сценарий, которого нет в большинстве шпаргалок, а спрашивают про него часто. Список значений — один из немногих универсальных типов, доступных и на клиенте, и на сервере. Поэтому его используют для диалогов выбора.
Выбор из списка в диалоге
На клиенте в управляемой форме вызывайте асинхронный метод ПоказатьВыборЭлемента. Выбранный элемент придёт в процедуру-обработчик:
&НаКлиенте
Процедура ВыбратьСклад(Команда)
Склады = Новый СписокЗначений;
Склады.Добавить("Основной", "Основной склад");
Склады.Добавить("Розница", "Розничный склад");
Оповещение = Новый ОписаниеОповещения("ПослеВыбораСклада", ЭтотОбъект);
Склады.ПоказатьВыборЭлемента(Оповещение, "Выберите склад");
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораСклада(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
Если ВыбранныйЭлемент = Неопределено Тогда
Возврат; // пользователь закрыл окно
КонецЕсли;
ВыбранныйКод = ВыбранныйЭлемент.Значение;
КонецПроцедуры
Процедура-обработчик должна быть экспортной, иначе оповещение её не вызовет. В платформе 8.3.18 и новее есть и вариант ВыбратьЭлементАсинх с оператором Ждать.
Текущая строка таблицы формы
Если реквизит формы имеет тип СписокЗначений и выведен в таблицу, текущий элемент получают так:
&НаКлиенте
Процедура ВзятьТекущее(Команда)
Идентификатор = Элементы.СписокТоваров.ТекущаяСтрока;
Если Идентификатор = Неопределено Тогда
Возврат;
КонецЕсли;
Элемент = СписокТоваров.НайтиПоИдентификатору(Идентификатор);
Товар = Элемент.Значение;
КонецПроцедуры
Здесь Элементы.СписокТоваров — элемент формы (таблица), а СписокТоваров — реквизит с данными. Имена у вас будут свои. Короче работает Элементы.СписокТоваров.ТекущиеДанные.Значение, но тоже с проверкой на Неопределено.
Список выбора поля ввода
У поля ввода свойство СписокВыбора — тоже СписокЗначений. Найти в нём строку и показать пользователю её представление можно тем же методом:
Элемент = Элементы.ВидОплаты.СписокВыбора.НайтиПоЗначению(Объект.ВидОплаты);
Пустой список, Неопределено и выход за границы
Большинство падений кода со списками значений — из трёх ситуаций.
Надёжная обёртка для чтения значения по индексу выглядит так:
Функция ЗначениеПоИндексу(Список, Индекс, ПоУмолчанию = Неопределено)
Если Список = Неопределено Или Индекс < 0 Или Индекс >= Список.Количество() Тогда
Возврат ПоУмолчанию;
КонецЕсли;
Возврат Список[Индекс].Значение;
КонецФункции
Порядок условий здесь важен. Встроенный язык 1С проверяет Или слева направо и останавливается на первом Истина. Поэтому при Список = Неопределено вызов Количество() не произойдёт, и ошибки не будет.
💡 Если список ограничен по типу, задайте свойство ТипЗначения (объект ОписаниеТипов). Тогда в список не попадёт значение чужого типа, и сравнения в НайтиПоЗначению станут предсказуемыми.
Частые вопросы
Чем список значений отличается от массива?
Можно ли передать список значений с клиента на сервер?
Как проверить, что значение есть в списке, не получая элемент?
Список.НайтиПоЗначению(Х) <> Неопределено. Это и есть проверка вхождения.Как получить все значения списка одной строкой через запятую?
СтрСоединить(Список.ВыгрузитьЗначения(), ", "). Метод рассчитан на массив строк, поэтому для чисел и ссылок сначала соберите в цикле массив Строка(Элемент.Значение) или представлений.Как убрать дубли из списка значений?
НайтиПоЗначению. Если значение уже есть, пропускайте его.