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