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