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