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