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