Как реализовать перетаскивание строк и групп в дереве значений на обычных формах 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

Интерактивное перетаскивание элементов (drag-and-drop) в табличных частях и деревьях значений — удобный механизм, позволяющий пользователям быстро менять структуру данных или подчиненность элементов. Однако при работе с элементом управления Дерево на обычных формах встроенное поведение платформы требует дополнительной настройки и программной обработки в событиях формы.

Разберем по шагам, как правильно настроить свойства элемента формы и написать программный код для перетаскивания как отдельных строк, так и целых групп с подчиненными узлами.

Шаг 1. Настройка свойств элемента управления на форме

Для включения возможности перетаскивания перейдем в палитру свойств элемента управления Дерево и установим два флажка:

  1. РазрешитьНачалоПеретаскивания — определяет, что пользователь может потянуть строку из данного дерева.
  2. РазрешитьПеретаскивание — разрешает принимать перетаскиваемые элементы в данный элемент формы.

Если данные флажки не установлены, при попытке потянуть строку курсор мыши будет принимать вид перечеркнутого круга, а события перетаскивания не будут вызываться.

Шаг 2. Настройка событии начала и проверки перетаскивания

Рассмотрим обработчик события НачалоПеретаскивания. В нем требуется явно определить, какие действия разрешены для перетаскиваемого объекта. Если необходимо именно перемещение строки (а не копирование), укажем соответствующие параметры в объекте ПараметрыПеретаскивания:


Процедура ДеревоНачалоПеретаскивания(Элемент, ПараметрыПеретаскивания, Выполнение)
    ПараметрыПеретаскивания.Действие = ДействиеПеретаскивания.Перемещение;
    ПараметрыПеретаскивания.ДопустимыеДействия = ДопустимыеДействияПеретаскивания.Перемещение;
КонецПроцедуры

Далее настроим событие ПроверкаПеретаскивания. Оно срабатывает при протаскивании элемента над потенциальной целью. Чтобы отключить стандартное поведение платформы и разрешить сброс объекта, отключим стандартную обработку:


Процедура ДеревоПроверкаПеретаскивания(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Колонка)
    СтандартнаяОбработка = Ложь;
КонецПроцедуры

Шаг 3. Обработка сброса и перемещение одиночной строки

Проанализируем ситуацию, когда пользователь отпускает левую клавишу мыши над целевой строкой. В объекте СтрокаДереваЗначений платформы 1С отсутствует метод для прямого изменения родительского узла (нельзя просто перезаписать свойство Родитель). Перемещение реализуется путем создания новой строки в целевой коллекции, переноса значений колонок и последующего удаления исходной строки.

Важно учитывать, что у элементов верхнего уровня (корневых) свойство Родитель имеет значение Неопределено. Для безошибочного удаления исходного элемента проверим наличие родительского узла:


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

Шаг 4. Перенос группы со всеми вложенными элементами

Рассмотрим более сложный случай: переносу подлежит родительская группа, содержащая подгруппы и дочерние элементы. Чтобы перенести всю ветку целиком, применим рекурсивную функцию для копирования всей структуры дочерних строк.

Также важно защитить дерево от циклической подчиненности — ситуации, когда пользователь пытается перетащить родительскую группу внутрь ее собственного дочернего узла. Для этого реализуем функцию проверки в событии ПроверкаПеретаскивания.


Функция ЭтоДочернийУзел(ИсходнаяСтрока, ЦелеваяСтрока)
    ТекущийРодитель = ЦелеваяСтрока;
    Пока ТекущийРодитель <> Неопределено Цикл
        Если ТекущийРодитель = ИсходнаяСтрока Тогда
            Возврат Истина;
        КонецЕсли;
        ТекущийРодитель = ТекущийРодитель.Родитель;
    КонецЦикла;
    Возврат Ложь;
КонецФункции

Процедура ДеревоПроверкаПеретаскивания(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Колонка)
    СтандартнаяОбработка = Ложь;
    
    ИсходнаяСтрока = ПараметрыПеретаскивания.Значение;
    Если ТипЗнч(ИсходнаяСтрока) = Тип("СтрокаДереваЗначений") И Строка <> Неопределено Тогда
        // Запрещаем перетаскивание узла в самого себя или в свои дочерние элементы
        Если ИсходнаяСтрока = Строка Или ЭтоДочернийУзел(ИсходнаяСтрока, Строка) Тогда
            ПараметрыПеретаскивания.Действие = ДействиеПеретаскивания.Отмена;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Процедура СкопироватьВеткуДерева(Источник, ПриемникКоллекция)
    НоваяСтрока = ПриемникКоллекция.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, Источник);
    
    Для Каждого ПодчиненнаяСтрока Из Источник.Строки Цикл
        СкопироватьВеткуДерева(ПодчиненнаяСтрока, НоваяСтрока.Строки);
    КонецЦикла;
КонецПроцедуры

Процедура ДеревоПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Колонка)
    СтандартнаяОбработка = Ложь;
    
    ИсходнаяСтрока = ПараметрыПеретаскивания.Значение;
    Если ТипЗнч(ИсходнаяСтрока) <> Тип("СтрокаДереваЗначений") Тогда
        Возврат;
    КонецЕсли;
    
    ПриемникКоллекция = ИИФ(Строка = Неопределено, Дерево.Строки, Строка.Строки);
    
    // Рекурсивно копируем ветку со всеми вложенными элементами
    СкопироватьВеткуДерева(ИсходнаяСтрока, ПриемникКоллекция);
    
    // Удаляем исходную ветку
    Если ИсходнаяСтрока.Родитель = Неопределено Тогда
        Дерево.Строки.Удалить(ИсходнаяСтрока);
    Иначе
        ИсходнаяСтрока.Родитель.Строки.Удалить(ИсходнаяСтрока);
    КонецЕсли;
КонецПроцедуры

Подводя итоги, отметим главные правила при перетаскивании элементов дерева значений:

← На главную