Реализация механизма Drag-and-Drop (перетаскивания) в дереве значений на обычных формах 1С:Предприятие 8 — это задача, требующая четкого понимания жизненного цикла событий элемента управления и особенностей работы с объектом ДеревоЗначений. В отличие от простых списков, дерево подразумевает иерархическую структуру, что накладывает дополнительные ограничения при перемещении элементов.
В этой статье мы подробно разберем, как организовать полноценный перенос веток дерева, почему нельзя просто изменить родителя строки и какую последовательность действий необходимо соблюсти, чтобы интерфейс работал корректно и без ошибок.
Прежде чем приступать к написанию программного кода, проанализируем ситуацию и выясним, в каком порядке платформа вызывает обработчики событий. Это критически важно, так как нарушение логики в одном из них может прервать весь процесс. Рассмотрим стандартную цепочку:
Для начала разберем настройки самого табличного поля, отображающего дерево значений. Чтобы механизм заработал, в свойствах элемента управления (на форме) необходимо установить флаги РазрешитьНачалоПеретаскивания и РазрешитьПеретаскивание. Без этих галочек платформа не будет инициализировать соответствующие события. При настройке свойств элементов управления и изучении доступных событий можно использовать шпаргалку по разработке обычных и управляемых форм.
Рассмотрим подробнее событие НачалоПеретаскивания. В этом обработчике нам нужно поместить перетаскиваемую строку в параметры. Посмотрим на пример кода:
Процедура ДеревоЗначенийНачалоПеретаскивания(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка)
// Передаем текущую строку в параметры перетаскивания
ПараметрыПеретаскивания.Значение = Элемент.ТекущаяСтрока;
// Определяем разрешенные действия (копирование или перемещение)
ПараметрыПеретаскивания.ДопустимыеДействия = ДопустимыеДействияПеретаскивания.Перемещение;
КонецПроцедуры
Важный момент: если у вас включено множественное выделение, в ПараметрыПеретаскивания.Значение следует передавать массив строк.
Теперь проанализируем событие ПроверкаПеретаскивания. Это «фильтр», который определяет визуальный отклик (вид курсора). Здесь мы должны реализовать бизнес-логику: можно ли переносить этот элемент в выбранную папку?
Одной из главных опасностей является попытка перенести родительскую ветку внутрь одного из своих собственных дочерних элементов. Это создаст бесконечную рекурсию и приведет к ошибке. Также часто требуется запретить перенос в корень дерева.
Процедура ДеревоЗначенийПроверкаПеретаскивания(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
// Отключаем стандартную обработку, чтобы управлять процессом вручную
СтандартнаяОбработка = Ложь;
// Получаем источник и потенциальный приемник
Источник = ПараметрыПеретаскивания.Значение;
Приемник = Строка; // Это строка, на которую наведен курсор
// 1. Проверяем, что не пытаемся перетащить строку в саму себя или в своих детей
ТекущийРодитель = Приемник;
Пока ТекущийРодитель <> Неопределено Цикл
Если ТекущийРодитель = Источник Тогда
ПараметрыПеретаскивания.Действие = ДействиеПеретаскивания.Отмена;
Возврат;
КонецЕсли;
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
// 2. Если все проверки пройдены, разрешаем перемещение
ПараметрыПеретаскивания.Действие = ДействиеПеретаскивания.Перемещение;
КонецПроцедуры
Перейдем к самому сложному этапу — событию Перетаскивание. Нужно понимать технический нюанс: в 1С:Предприятии 8 свойство Родитель у строки дерева значений доступно только для чтения. Мы не можем просто написать Строка.Родитель = НоваяГруппа.
Вместо этого мы применим следующий алгоритм:
Разберем, как это реализовать с помощью вспомогательной процедуры. Сначала основной обработчик:
Процедура ДеревоЗначенийПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
СтандартнаяОбработка = Ложь;
Источник = ПараметрыПеретаскивания.Значение;
Приемник = Строка; // Новая группа-родитель
// Вызываем процедуру копирования ветки
НоваяСтрока = ПереместитьВеткуДерева(Источник, Приемник);
// Удаляем старую ветку
Если Источник.Родитель = Неопределено Тогда
Элемент.Значение.Строки.Удалить(Источник);
Иначе
Источник.Родитель.Строки.Удалить(Источник);
КонецЕсли;
// Фокусируемся на новом элементе
Элемент.ТекущаяСтрока = НоваяСтрока;
КонецПроцедуры
Чтобы перенос работал для веток любой вложенности, нам понадобится рекурсивная функция. Она будет создавать структуру в новом месте, проходя сверху вниз по всем уровням иерархии.
Функция ПереместитьВеткуДерева(Источник, Приемник)
// Определяем коллекцию, куда добавлять
Если Приемник = Неопределено Тогда
КоллекцияСтрок = ДеревоЗначений.Строки; // Корень
Иначе
КоллекцияСтрок = Приемник.Строки;
КонецЕсли;
// Добавляем новую строку
НоваяСтрока = КоллекцияСтрок.Добавить();
// Копируем данные всех колонок
ЗаполнитьЗначенияСвойств(НоваяСтрока, Источник);
// Рекурсивно переносим подчиненные строки
Для Каждого ПодчиненнаяСтрока Из Источник.Строки Цикл
ПереместитьВеткуДерева(ПодчиненнаяСтрока, НоваяСтрока);
КонецЦикла;
Возврат НоваяСтрока;
КонецФункции
Использование метода ЗаполнитьЗначенияСвойств позволяет нам не перечислять все колонки дерева вручную, что делает код универсальным и устойчивым к изменениям состава колонок в будущем.
При работе с большими деревьями значений стоит учитывать несколько моментов:
Развернуть() для новых строк.Перетаскивание можно анализировать, куда именно упал элемент — «на строку» или «между строк». Для этого используются параметры координат, что позволяет не только менять родителя, но и изменять порядок строк внутри одной группы (используя метод Вставить() вместо Добавить()).Таким образом, мы выяснили, что перетаскивание в «обычных формах» — это не просто смена ссылки на родителя, а полноценная операция трансформации структуры данных. Следуя описанной последовательности событий и используя рекурсивный метод копирования, вы сможете реализовать интуитивно понятный и стабильный интерфейс для пользователей вашей конфигурации.