СписокЗначений в 1С 8.3: доступ по индексу, поиск, перебор, отбор и сортировка

· 10 мин

Содержание

Чтобы получить значение из списка значений в 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С проверяет Или слева направо и останавливается на первом Истина. Поэтому при Список = Неопределено вызов Количество() не произойдёт, и ошибки не будет.

💡 Если список ограничен по типу, задайте свойство ТипЗначения (объект ОписаниеТипов). Тогда в список не попадёт значение чужого типа, и сравнения в НайтиПоЗначению станут предсказуемыми.

Проверьте себя
Что вернёт выражение Список[0] для непустого списка значений?
Что вернёт НайтиПоЗначению, если значение в списке не найдено?
Как найти элемент по представлению?

Частые вопросы

Чем список значений отличается от массива?
У массива каждая ячейка — само значение. У списка значений каждая строка — элемент с четырьмя свойствами: значение, представление, пометка, картинка. Список удобен для форм и диалогов выбора, массив — для расчётов.
Можно ли передать список значений с клиента на сервер?
Да. Список значений доступен и на клиенте, и на сервере, его можно передавать параметром серверного вызова и хранить в реквизите формы. Таблицу значений так передать нельзя.
Как проверить, что значение есть в списке, не получая элемент?
Сравните результат поиска с Неопределено: Список.НайтиПоЗначению(Х) <> Неопределено. Это и есть проверка вхождения.
Как получить все значения списка одной строкой через запятую?
Если в списке строки, выгрузите их в массив и соедините: СтрСоединить(Список.ВыгрузитьЗначения(), ", "). Метод рассчитан на массив строк, поэтому для чисел и ссылок сначала соберите в цикле массив Строка(Элемент.Значение) или представлений.
Как убрать дубли из списка значений?
Соберите новый список: перед добавлением проверяйте НайтиПоЗначению. Если значение уже есть, пропускайте его.