Как задать вопрос пользователю при смене родителя справочника в управляемой форме списка и остановить перенос?

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с управляемыми формами 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью подтверждения действий пользователя. Одной из таких задач является запрос подтверждения при смене родителя элемента справочника в форме списка. Однако, реализация такого функционала с использованием асинхронного диалога ПоказатьВопрос имеет свои особенности, которые могут привести к неожиданному поведению или зависанию системы.

Рассмотрим подробнее ситуацию, когда при перетаскивании элемента справочника или использовании команды "Переместить в группу" нам необходимо задать пользователю вопрос: "Вы действительно хотите перенести элемент справочника в другую папку?". В зависимости от ответа, действие должно либо продолжиться, либо быть отменено.

В чем заключается сложность асинхронного диалога ПоказатьВопрос?

Основная проблема, с которой сталкиваются разработчики, заключается в асинхронной природе функции ПоказатьВопрос. В отличие от модальных диалогов, которые блокируют выполнение кода до получения ответа, ПоказатьВопрос запускает диалог и немедленно возвращает управление процедуре-инициатору. Это означает, что код, расположенный после вызова ПоказатьВопрос, продолжит выполняться, не дожидаясь ответа пользователя. В случае смены родителя справочника, это приводит к тому, что система пытается выполнить перенос элемента еще до того, как пользователь успеет ответить на вопрос, что часто приводит к ошибкам или зависанию, так как внутренние механизмы 1С не ожидают прерывания на этом этапе.

Для примера, рассмотрим первоначальную попытку реализации, которая наглядно демонстрирует проблему:


&НаКлиенте
Процедура 111_СписокОбработкаВыбораПосле(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    РезультатПроверки = Ложь; 
    //Передадим на сервер проверку
    ОбработкаВыбораНаСервере(Элемент.ТекущаяСтрока,ВыбранноеЗначение,РезультатПроверки);
    Если РезультатПроверки=Истина Тогда 
        //Проверили на сервере, меняется родитель.
        //теперь надо задать вопрос пользователю
        //если он отвечает нет, надо сделать * обратно
        //если он отвечает да, то продолжить.
        Режим = РежимДиалогаВопрос.ДаНет;
        Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопроса", ЭтотОбъект, Параметры);
        ПоказатьВопрос(Оповещение, "Вы действительно хотите перенести элемент справочника в другую папку?", Режим, 0);
        // !!! Код здесь продолжит выполняться, не дожидаясь ответа пользователя
        // И система продолжит попытку переноса
    Иначе
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ОбработкаВыбораНаСервере(ТекущаяСтрока,ВыбранноеЗначение,РезультатПроверки)
    СпрЛицевыеСчетаОбъект = ТекущаяСтрока.ПолучитьОбъект;
    Если Не СпрЛицевыеСчетаОбъект.СтарыйРодитель = Null Тогда 
        Если СпрЛицевыеСчетаОбъект.СтарыйРодитель <> ВыбранноеЗначение Тогда
            РезультатПроверки=Истина; //родитель и старый родитель не совпадают, происходит перенос в другую группу
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

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

В приведенном примере, после вызова ПоказатьВопрос, система продолжает выполнять стандартное действие переноса, даже если пользователь еще не ответил. Это приводит к тому, что вопрос отображается, но процесс переноса уже запущен и может столкнуться с проблемами или завершиться нежелательным образом.

Правильный подход: отмена стандартного действия и программная запись

Для корректной реализации подтверждения при смене родителя необходимо применить двухэтапный подход:

  1. Предотвратить стандартное действие переноса. Это критически важно. Мы должны найти событие, которое срабатывает непосредственно перед тем, как система начнет менять родителя, и в этом событии отменить стандартную операцию.
  2. Асинхронно задать вопрос пользователю. После отмены стандартного действия, мы вызываем ПоказатьВопрос.
  3. Выполнить действие программно по подтверждению. В процедуре-обработчике оповещения от ПоказатьВопрос мы проверяем ответ пользователя. Если пользователь согласился, мы программно изменяем родителя и записываем элемент справочника. Если отказался, никаких действий не предпринимается, так как стандартное действие уже было отменено.

Наиболее подходящим событием для перехвата стандартного изменения родителя в форме списка является событие ПередИзменениемРодителя табличного поля. Это событие возникает непосредственно перед тем, как 1С пытается изменить родителя текущего элемента, будь то через команду "Переместить в группу" или посредством перетаскивания мышью.

Реализация решения по шагам

Шаг 1: Подготовка расширения и обработчика события

Все доработки рекомендуется выполнять в расширении конфигурации. Для этого, добавьте в расширение форму списка вашего справочника (например, "Справочник.ЛицевыеСчета.ФормаСписка") и подпишитесь на событие ПередИзменениемРодителя табличного поля, которое отображает элементы справочника.

Предположим, ваше табличное поле называется "Список".


// В модуле формы списка справочника (например, в расширении)
&НаКлиенте
Процедура СписокПередИзменениемРодителя(Элемент, НовыйРодитель, Отказ, ДополнительныеСвойства)

    // Исходный родитель текущего элемента
    СтарыйРодитель = Элемент.ТекущиеДанные.Родитель;
    // Родитель, в который пытаемся перенести
    ЦелевойРодитель = НовыйРодитель;

    // Проверяем, действительно ли меняется родитель
    // (исключаем случаи переноса в того же родителя или в себя, если это группа)
    Если СтарыйРодитель <> ЦелевойРодитель Тогда
        // 1. Отменяем стандартное действие переноса.
        // Это КРИТИЧЕСКИ ВАЖНО, чтобы система не продолжила работу
        // до ответа пользователя.
        Отказ = Истина;

        // 2. Подготавливаем параметры для асинхронного вопроса
        ПараметрыВопроса = Новый Структура;
        ПараметрыВопроса.Вставить("ТекущаяСсылка", Элемент.ТекущиеДанные.Ссылка);
        ПараметрыВопроса.Вставить("НовыйРодитель", ЦелевойРодитель);

        // 3. Задаем вопрос пользователю
        Режим = РежимДиалогаВопрос.ДаНет;
        Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопросаПриСменеРодителя", ЭтотОбъект, ПараметрыВопроса);
        ПоказатьВопрос(Оповещение, "Вы действительно хотите перенести элемент справочника в другую папку?", Режим, 0);
    КонецЕсли;

КонецПроцедуры

Мы видим, что в обработчике СписокПередИзменениемРодителя мы устанавливаем Отказ = Истина. Это гарантирует, что стандартный механизм изменения родителя будет остановлен. Затем мы сохраняем необходимые параметры (ссылка на текущий элемент и новый родитель) и вызываем ПоказатьВопрос, передавая ему ОписаниеОповещения для нашей процедуры обратного вызова.

Шаг 2: Обработка ответа пользователя

Теперь нам нужно реализовать процедуру ПослеЗакрытияВопросаПриСменеРодителя, которая будет вызвана после того, как пользователь ответит на вопрос. В этой процедуре мы проанализируем ответ и, в случае подтверждения, программно выполним перенос.


// В модуле формы списка справочника (например, в расширении)
&НаКлиенте
Процедура ПослеЗакрытияВопросаПриСменеРодителя(Результат, Параметры) Экспорт

    Если Результат = КодВозвратаДиалога.Да Тогда
        // Пользователь ответил "Да", выполняем перенос программно

        ТекущаяСсылка = Параметры.ТекущаяСсылка;
        НовыйРодитель = Параметры.НовыйРодитель;

        // Вызываем серверную процедуру для выполнения переноса и записи
        ПеренестиЭлементСправочникаНаСервере(ТекущаяСсылка, НовыйРодитель);

        // Обновляем данные списка, чтобы изменения отобразились
        Элементы.Список.Обновить(); 
    Иначе
        // Пользователь ответил "Нет" или закрыл диалог.
        // Ничего не делаем, так как стандартный перенос уже отменен.
    КонецЕсли;

КонецПроцедуры

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

Шаг 3: Серверная процедура для программного переноса

Создадим серверную процедуру, которая получит объект элемента справочника, изменит его родителя и запишет его.


// В модуле формы списка справочника (например, в расширении)
&НаСервере
Процедура ПеренестиЭлементСправочникаНаСервере(СсылкаНаЭлемент, НовыйРодитель)

    Попытка
        ОбъектСправочника = СсылкаНаЭлемент.ПолучитьОбъект();
        Если ОбъектСправочника <> Неопределено Тогда
            ОбъектСправочника.Родитель = НовыйРодитель;
            ОбъектСправочника.Записать();
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при переносе элемента: " + ОписаниеОшибки());
    КонецПопытки;

КонецПроцедуры

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

Дополнительные соображения и альтернативы

Хотя предложенное решение с ПередИзменениемРодителя является наиболее прямым и эффективным, существуют и другие моменты, которые стоит рассмотреть или альтернативные подходы:

  1. ПередЗаписью в модуле объекта: Событие ПередЗаписью в модуле объекта справочника срабатывает при любой записи элемента, включая изменение родителя. Однако, оно является серверным, и из него невозможно напрямую задать вопрос пользователю (ПоказатьВопрос — клиентский метод). Его можно использовать для дополнительной серверной валидации или для отмены записи, если вопрос не был задан или ответ был отрицательным на клиентской стороне. Например, если клиентский обработчик был обойден, сервер мог бы предотвратить несанкционированный перенос.
  2. Переопределение стандартных команд: Если вам требуется более глубокий контроль или полная замена логики, вы можете сделать стандартную команду "Переместить в группу" невидимой и создать свою собственную команду. Эта пользовательская команда будет выполнять всю необходимую логику, включая запрос подтверждения и последующий перенос.
  3. Переопределение формы выбора группы: В некоторых случаях, если вы хотите контролировать не только подтверждение переноса, но и сам процесс выбора группы (например, наложить жесткие отборы или запретить выбор определенных папок), можно переопределить открытие формы выбора группы. Это можно сделать через обработчик ОбработкаПолученияФормы в модуле менеджера справочника, открывая свою пользовательскую форму выбора.
  4. Обработка перетаскивания (Drag-and-Drop): Важно убедиться, что ваше решение покрывает все возможные способы переноса, включая перетаскивание элементов мышью. Событие ПередИзменениемРодителя обычно срабатывает и для перетаскивания, что делает его универсальным.

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

← На главную