Как в форме выбора справочника затемнить зарезервированные элементы и заблокировать их выбор?

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

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

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

Шаг 1. Проектирование и оптимизация запроса динамического списка

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

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

Правильным решением будет использование обычного ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей СрезПоследних регистра сведений. Посмотрим на пример такого запроса для динамического списка:


ВЫБРАТЬ
    СправочникМашины.Ссылка КАК Ссылка,
    СправочникМашины.Код КАК Код,
    СправочникМашины.Наименование КАК Наименование,
    ВЫБОР
        КОГДА ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата
            ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК Занята,
    ВЫБОР
        КОГДА ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата
            ТОГДА "Занята до " + ВЫРАЗИТЬ(ЗанятостьМашинСрезПоследних.ПериодЗавершения КАК СТРОКА(100))
        ИНАЧЕ "Свободна"
    КОНЕЦ КАК СтатусТекст
ИЗ
    Справочник.Машины КАК СправочникМашины
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, ) КАК ЗанятостьМашинСрезПоследних
        ПО СправочникМашины.Ссылка = ЗанятостьМашинСрезПоследних.Машина

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


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Инициализируем параметр запроса динамического списка
    Список.Параметры.УстановитьЗначениеПараметра("ТекущаяДата", ТекущаяДатаСеанса());
    
    // Вызываем процедуру настройки оформления
    НастроитьУсловноеОформление();
    
КонецПроцедуры

Шаг 2. Визуальное затемнение через условное оформление

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

Разберем по шагам процедуру настройки программного условного оформления:


&НаСервере
Процедура НастроитьУсловноеОформление()
    
    // 1. Создаем новый элемент условного оформления
    ЭлементОформления = Список.УсловноеОформление.Элементы.Добавить();
    
    // 2. Указываем свойства оформления: серый цвет текста и наклонный шрифт
    ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", WebЦвета.Серый);
    
    НовыйШрифт = Новый Шрифт(,, Истина); // Наклонный (курсив)
    ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Шрифт", НовыйШрифт);
    
    // 3. Задаем условие применения: оформлять только строки, где Занята = Истина
    ЭлементУсловия = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементУсловия.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Занята");
    ЭлементУсловия.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементУсловия.ПравоеЗначение = Истина;
    ЭлементУсловия.Использование = Истина;
    
    // 4. Указываем оформляемые поля формы
    ПолеОформленияНаименование = ЭлементОформления.Поля.Элементы.Добавить();
    ПолеОформленияНаименование.Поле = Новый ПолеКомпоновкиДанных("Наименование");
    ПолеОформленияНаименование.Использование = Истина;
    
    ПолеОформленияКод = ЭлементОформления.Поля.Элементы.Добавить();
    ПолеОформленияКод.Поле = Новый ПолеКомпоновкиДанных("Код");
    ПолеОформленияКод.Использование = Истина;
    
    ПолеОформленияСтатус = ЭлементОформления.Поля.Элементы.Добавить();
    ПолеОформленияСтатус.Поле = Новый ПолеКомпоновкиДанных("СтатусТекст");
    ПолеОформленияСтатус.Использование = Истина;
    
КонецПроцедуры

Шаг 3. Решение бага с обходом блокировки выбора

Выясним причину проблемы, описанной автором в Сообщении 12. Даже если мы заблокируем изменение полей строки, установив в условном оформлении свойство ТолькоПросмотр = Истина или Доступность = Ложь, это не отменит режим выбора самой формы. Когда пользователь открывает группу (папку) в иерархическом списке, фокус автоматически устанавливается на первую строку. Если эта строка занята, нажатие клавиши Enter или двойной клик мыши все равно приведут к выбору этого элемента и закрытию формы.

Чтобы исключить эту уязвимость, необходимо перехватить событие выбора на клиенте и отменить стандартную обработку платформы. Нам потребуются два обработчика событий формы выбора.

Во-первых, переопределим обработчик события Выбор у таблицы формы Список:


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

Во-вторых, оператор может выделить занятую строку и нажать стандартную кнопку «Выбрать» на командной панели формы. Чтобы перекрыть этот путь обхода, добавим проверку в обработчик события формы ПередЗакрытием:


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

Шаг 4. Защита от альтернативных путей ввода (Быстрый ввод и ввод по строке)

Опытный пользователь может обойти форму выбора целиком. Если он помнит код или госномер занятой машины, он может начать вводить его клавиатурой непосредственно в поле ввода документа. Сработает стандартный механизм автоподбора («ввод по строке»), форма выбора не откроется, и заблокированный элемент успешно запишется в документ.

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

  1. В модуле менеджера справочника Машины для исключения занятых элементов из быстрого автоподбора.
  2. В форме самого документа для окончательной валидации введенного значения.

Рассмотрим реализацию обработчика в модуле менеджера справочника Машины:


Процедура ОбработкаПолученияДанныхВыбора(ДанныеВыбора, Параметры, СтандартнаяОбработка)
    
    // Отменяем стандартную генерацию данных автоподбора
    СтандартнаяОбработка = Ложь;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
        |    СправочникМашины.Ссылка КАК Ссылка,
        |    СправочникМашины.Наименование КАК Представление
        |ИЗ
        |    Справочник.Машины КАК СправочникМашины
        |    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, ) КАК ЗанятостьМашинСрезПоследних
        |        ПО СправочникМашины.Ссылка = ЗанятостьМашинСрезПоследних.Машина
        |ГДЕ
        |    СправочникМашины.Наименование ПОДОБНО &СтрокаПоиска + ""%""
        |    И (ЗанятостьМашинСрезПоследних.ПериодЗавершения ЕСТЬ NULL 
        |        ИЛИ ЗанятостьМашинСрезПоследних.ПериодЗавершения <= &ТекущаяДата)";
        
    Запрос.УстановитьПараметр("СтрокаПоиска", Параметры.СтрокаПоиска);
    Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
    
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    СписокВыбора = Новый СписокЗначений;
    Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
        СписокВыбора.Добавить(СтрокаТЗ.Ссылка, СтрокаТЗ.Представление);
    КонецЦикла;
    
    ДанныеВыбора = СписокВыбора;
    
КонецПроцедуры

Теперь реализуем проверку на форме документа в событии ОбработкаВыбора поля ввода машины. Это гарантирует, что даже при копировании строк или обходных путях занятая машина не попадет в документ:


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

&НаСервереБезКонтекста
Функция ПроверитьЗанятостьАвтомобиля(Знач Автомобиль)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ИСТИНА КАК Занята
        |ИЗ
        |    РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, Машина = &Автомобиль) КАК ЗанятостьМашинСрезПоследних
        |ГДЕ
        |    ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата";
        
    Запрос.УстановитьПараметр("Автомобиль", Автомобиль);
    Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
    
    Результат = Запрос.Выполнить();
    Возврат Не Результат.Пустой();
    
КонецФункции

Шаг 5. Текст подсказки при наведении мыши

Для идеального удобства пользователя настроим динамическое отображение всплывающей подсказки. В условном оформлении, которое мы создали во втором шаге, можно дополнительно использовать свойство ТекстАссоциативнойПодсказки. Добавим строку кода в процедуру НастроитьУсловноеОформление():


// Добавляем понятный текст подсказки при наведении на серые строки
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ТекстАссоциативнойПодсказки", "Автомобиль зарезервирован под другой рейс. Выберите другую машину.");

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

Резюме

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

← На главную