Как динамически ограничить ввод числа в колонке табличной части 1С?

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

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

Постановка задачи

Представьте следующую ситуацию: у нас есть табличная часть документа с колонкой, которая имеет тип "Число". Для каждой строки этой табличной части нам необходимо установить свой диапазон допустимых значений для этого числового поля. Например, в первой строке максимальное значение может быть 20, а во второй — 15. Условие для определения этих ограничений, как правило, зависит от значения в другой колонке этой же строки. Мы ищем наиболее оптимальный подход для реализации такого динамического поведения.

Использование свойств элемента формы "Поле ввода"

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

  1. МаксимальноеЗначение: Это свойство определяет наибольшее допустимое значение, которое пользователь может ввести в данное поле. Если введенное число превысит установленный лимит, система автоматически его скорректирует или не позволит ввести.
  2. МинимальноеЗначение: Аналогично, это свойство определяет наименьшее допустимое значение. Если введенное число окажется меньше установленного предела, система также предпримет корректирующие действия.

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

Выбор подходящего события для установки ограничений

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

Событие ПриАктивизацииСтроки

Изначально может показаться логичным устанавливать ограничения в событии табличного поля ПриАктивизацииСтроки. Это событие возникает каждый раз, когда пользователь перемещает курсор на новую строку табличной части. При таком подходе мы могли бы получить текущие данные строки и на их основе установить нужные лимиты:


&НаКлиенте
Процедура ТаблицаЗначенийПриАктивизацииСтроки(Элемент)
    ТекущиеДанные = Элемент.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;

    // Предположим, "ТаблицаЗначенийПолеВвода" - это элемент формы, связанный с числовой колонкой,
    // а "КолонкаУсловия" - колонка, от которой зависят ограничения.
    Если ТекущиеДанные.КолонкаУсловия = "Условие1" Тогда
        Элементы.ТаблицаЗначенийПолеВвода.МаксимальноеЗначение = 20;
    ИначеЕсли ТекущиеДанные.КолонкаУсловия = "Условие2" Тогда
        Элементы.ТаблицаЗначенийПолеВвода.МаксимальноеЗначение = 15;
    Иначе
        // Сброс ограничения, если нет специфических условий
        Элементы.ТаблицаЗначенийПолеВвода.МаксимальноеЗначение = Неопределено;
    КонецЕсли;
КонецПроцедуры

Недостатки этого подхода:

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

Событие ПриНачалеРедактирования

Наиболее "красивым" и эффективным вариантом, который позволяет избежать бесполезных действий, является установка свойств МаксимальноеЗначение и МинимальноеЗначение для элемента формы в событии ПриНачалеРедактирования табличного поля. Это событие возникает непосредственно перед тем, как пользователь начинает редактировать ячейку в строке табличной части.

Преимущества этого подхода:

  1. Оптимизация производительности: Действия по установке ограничений выполняются только тогда, когда пользователь действительно намерен изменить данные, а не при простом перемещении по строкам. Это экономит ресурсы системы.
  2. Точность и актуальность: Ограничения устанавливаются точно в момент начала редактирования конкретной ячейки, что гарантирует их актуальность для текущей строки и ее специфических условий.
  3. Чистота кода: Логика привязана к моменту, когда она действительно необходима, что делает код более понятным и поддерживаемым.

Пример реализации с событием ПриНачалеРедактирования

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

  1. Определимся с элементами формы. У нас есть табличное поле на форме, например, с именем ТаблицаЗначений. Внутри этой табличной части есть колонка, куда мы вводим число, например, Количество (это название реквизита табличной части и, соответственно, имя элемента формы, которое отображает эту колонку). И есть другая колонка, скажем, ТипОграничения, от значения которой зависят наши пределы ввода.

  2. Создадим обработчик события ПриНачалеРедактирования. Откроем форму в конфигураторе, перейдем в свойства табличного поля (например, ТаблицаЗначений) и найдем событие ПриНачалеРедактирования. Создадим для него клиентский обработчик, нажав кнопку с лупой.

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

    
    &НаКлиенте
    Процедура ТаблицаЗначенийПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
        // "Элемент" - это само табличное поле, инициировавшее событие.
        // "НоваяСтрока" - Булево: Истина, если пользователь начинает редактировать новую строку.
        // "Копирование" - Булево: Истина, если строка копируется.
    
        // Если это новая строка или строка копируется,
        // возможно, нам нужны особые ограничения или значения по умолчанию.
        // Мы можем установить здесь начальные значения или базовые лимиты.
        Если НоваяСтрока Или Копирование Тогда
            // Например, для новых строк можно установить максимальное значение по умолчанию 100
            // Элементы.ТаблицаЗначенийКоличество.МаксимальноеЗначение = 100;
            // Элементы.ТаблицаЗначенийКоличество.МинимальноеЗначение = 0;
        КонецЕсли;
    
        // Получаем объект данных текущей строки, которую пользователь собирается редактировать.
        // Свойство Элемент.ТекущиеДанные содержит объект типа "ДанныеФормыЭлементКоллекции"
        // или "ДанныеФормыЭлемент", который соответствует текущей строке.
        ТекущиеДанные = Элемент.ТекущиеДанные;
    
        // Проверяем, что ТекущиеДанные не Неопределено, это может произойти при некоторых сценариях
        // или если таблица пуста.
        Если ТекущиеДанные <> Неопределено Тогда
            // Получаем значение из другой колонки текущей строки,
            // от которой зависят наши ограничения.
            // Предположим, у нас есть реквизит "ТипОграничения" в табличной части.
            // Имя элемента формы для нашей числовой колонки, например, "ТаблицаЗначенийКоличество".
            Перем ИмяЭлементаЧисловогоПоля;
            ИмяЭлементаЧисловогоПоля = "ТаблицаЗначенийКоличество"; 
    
            // Применяем условную логику для установки Максимального и Минимального значений
            // в зависимости от ТекущиеДанные.ТипОграничения
            Если ТекущиеДанные.ТипОграничения = "МалыйДиапазон" Тогда
                // Устанавливаем ограничения для элемента формы,
                // который отображает нашу числовую колонку "Количество".
                Элементы[ИмяЭлементаЧисловогоПоля].МаксимальноеЗначение = 20;
                Элементы[ИмяЭлементаЧисловогоПоля].МинимальноеЗначение = 1;
            ИначеЕсли ТекущиеДанные.ТипОграничения = "СреднийДиапазон" Тогда
                Элементы[ИмяЭлементаЧисловогоПоля].МаксимальноеЗначение = 50;
                Элементы[ИмяЭлементаЧисловогоПоля].МинимальноеЗначение = 10;
            ИначеЕсли ТекущиеДанные.ТипОграничения = "БольшойДиапазон" Тогда
                Элементы[ИмяЭлементаЧисловогоПоля].МаксимальноеЗначение = 100;
                Элементы[ИмяЭлементаЧисловогоПоля].МинимальноеЗначение = 0;
            Иначе
                // Если для текущих условий нет специфических ограничений,
                // мы сбрасываем ранее установленные.
                // Это важно для того, чтобы ограничения из предыдущих строк не применялись
                // к текущей, если для нее они не предусмотрены или должны быть шире.
                Элементы[ИмяЭлементаЧисловогоПоля].МаксимальноеЗначение = Неопределено;
                Элементы[ИмяЭлементаЧисловогоПоля].МинимальноеЗначение = Неопределено;
            КонецЕсли;
        КонецЕсли;
    КонецПроцедуры
    

    Важные моменты и пояснения к коду:

    • Перем ИмяЭлементаЧисловогоПоля; ИмяЭлементаЧисловогоПоля = "ТаблицаЗначенийКоличество"; — мы используем переменную для имени элемента формы. Это делает код более читаемым и гибким. Если бы у нас было несколько числовых колонок, мы могли бы динамически определять имя нужного элемента. В большинстве случаев можно использовать прямое обращение, например, Элементы.ТаблицаЗначенийКоличество, если имя элемента известно заранее и не меняется.
    • ТекущиеДанные = Элемент.ТекущиеДанные; — это ключевая строка. Она позволяет нам получить доступ к объекту данных текущей строки табличной части. Через этот объект мы можем обратиться к значениям других колонок этой строки (например, ТекущиеДанные.ТипОграничения), чтобы определить, какие именно лимиты должны быть применены.
    • Сброс ограничений с помощью Неопределено: Если никаких специальных условий для установки ограничений не выполняется, мы сбрасываем их, присваивая Неопределено свойствам МаксимальноеЗначение и МинимальноеЗначение. Это очень важный шаг! Без него ограничения, установленные для предыдущей строки (если они были), будут продолжать действовать для текущей строки, даже если для нее они неактуальны или должны быть другими. При сбросе платформа будет использовать любые ограничения, заданные на уровне метаданных (если они есть), или позволит вводить любые числа.

Дополнительные механизмы валидации

Хотя установка свойств МаксимальноеЗначение и МинимальноеЗначение является отличным способом клиентской валидации при интерактивном вводе с клавиатуры, всегда рекомендуется использовать многоуровневую систему контроля. Это гарантирует, что данные будут корректными, даже если они были изменены программно (например, при подборе) или если пользователь попытается обойти клиентские ограничения с помощью других механизмов (например, вставки из буфера обмена).

Валидация при изменении данных (Событие ПриИзменении)

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


&НаКлиенте
Процедура ТаблицаЗначенийКоличествоПриИзменении(Элемент)
    // Получаем текущие данные строки, в которой произошло изменение
    ТекущиеДанные = Элементы.ТаблицаЗначений.ТекущиеДанные;

    Если ТекущиеДанные <> Неопределено Тогда
        Перем МаксЗначение, МинЗначение;
        МаксЗначение = Неопределено;
        МинЗначение = Неопределено;

        // Повторяем логику определения ограничений, как в ПриНачалеРедактирования
        Если ТекущиеДанные.ТипОграничения = "МалыйДиапазон" Тогда
            МаксЗначение = 20;
            МинЗначение = 1;
        ИначеЕсли ТекущиеДанные.ТипОграничения = "СреднийДиапазон" Тогда
            МаксЗначение = 50;
            МинЗначение = 10;
        ИначеЕсли ТекущиеДанные.ТипОграничения = "БольшойДиапазон" Тогда
            МаксЗначение = 100;
            МинЗначение = 0;
        КонецЕсли;

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

Валидация при записи объекта (Событие ОбработкаПроверкиЗаполнения)

Для реквизитов объекта (включая реквизиты табличной части) можно использовать событие ОбработкаПроверкиЗаполнения, которое вызывается в модуле объекта перед записью. Здесь мы выполняем финальную проверку всех данных табличной части на уровне объекта. Если проверка не пройдена, мы можем отказать в записи документа, выдав пользователю подробное сообщение об ошибке. Это событие вызывается при записи любого объекта конфигурации.

Пример реализации в модуле объекта, например, документа "МойДокумент":


// В модуле объекта, например, документа "МойДокумент"
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
    // Проходим по всем строкам нашей табличной части
    Для Каждого СтрокаТЧ Из ТабличнаяЧасть Цикл
        Перем МаксЗначение, МинЗначение;
        МаксЗначение = Неопределено;
        МинЗначение = Неопределено;

        // Повторяем логику определения ограничений
        Если СтрокаТЧ.ТипОграничения = "МалыйДиапазон" Тогда
            МаксЗначение = 20;
            МинЗначение = 1;
        ИначеЕсли СтрокаТЧ.ТипОграничения = "СреднийДиапазон" Тогда
            МаксЗначение = 50;
            МинЗначение = 10;
        ИначеЕсли СтрокаТЧ.ТипОграничения = "БольшойДиапазон" Тогда
            МаксЗначение = 100;
            МинЗначение = 0;
        КонецЕсли;

        // Проверяем значение в текущей строке табличной части
        Если МаксЗначение <> Неопределено И СтрокаТЧ.Количество > МаксЗначение Тогда
            Сообщить("В строке " + Строка(СтрокаТЧ.НомерСтроки) + " значение '" + Строка(СтрокаТЧ.Количество) + "' превышает максимально допустимое '" + Строка(МаксЗначение) + "'.", СтатусСообщения.Важное);
            Отказ = Истина; // Отказываем в записи объекта
        ИначеЕсли МинЗначение <> Неопределено И СтрокаТЧ.Количество < МинЗначение Тогда
            Сообщить("В строке " + Строка(СтрокаТЧ.НомерСтроки) + " значение '" + Строка(СтрокаТЧ.Количество) + "' меньше минимально допустимого '" + Строка(МинЗначение) + "'.", СтатусСообщения.Важное);
            Отказ = Истина; // Отказываем в записи объекта
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

Серверная валидация (ПередЗаписьюНаСервере)

Для еще более надежного контроля, особенно в клиент-серверных приложениях, рекомендуется дублировать (или выполнять основную) валидацию на сервере. Это гарантирует, что даже если пользователь обойдет клиентские ограничения или некорректные данные поступят на сервер другим путем, они не будут сохранены в базе данных. Серверная проверка может быть реализована в обработчиках событий формы, таких как ПередЗаписьюНаСервере.


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    // "ТекущийОбъект" - это объект данных формы (с измененными данными).
    // Проходим по всем строкам табличной части объекта формы.
    Для Каждого СтрокаТЧ Из ТекущийОбъект.ТабличнаяЧасть Цикл
        Перем МаксЗначение, МинЗначение;
        МаксЗначение = Неопределено;
        МинЗначение = Неопределено;

        // Повторяем логику определения ограничений
        Если СтрокаТЧ.ТипОграничения = "МалыйДиапазон" Тогда
            МаксЗначение = 20;
            МинЗначение = 1;
        ИначеЕсли СтрокаТЧ.ТипОграничения = "СреднийДиапазон" Тогда
            МаксЗначение = 50;
            МинЗначение = 10;
        ИначеЕсли СтрокаТЧ.ТипОграничения = "БольшойДиапазон" Тогда
            МаксЗначение = 100;
            МинЗначение = 0;
        КонецЕсли;

        // Проверяем значение в строке табличной части
        Если МаксЗначение <> Неопределено И СтрокаТЧ.Количество > МаксЗначение Тогда
            // Сообщаем об ошибке. Сообщение будет показано пользователю на клиенте.
            Сообщить("В строке " + Строка(СтрокаТЧ.НомерСтроки) + " значение '" + Строка(СтрокаТЧ.Количество) + "' превышает максимально допустимое '" + Строка(МаксЗначение) + "'.", СтатусСообщения.Важное);
            Отказ = Истина; // Отказываем в записи, так как обнаружены некорректные данные
        ИначеЕсли МинЗначение <> Неопределено И СтрокаТЧ.Количество < МинЗначение Тогда
            Сообщить("В строке " + Строка(СтрокаТЧ.НомерСтроки) + " значение '" + Строка(СтрокаТЧ.Количество) + "' меньше минимально допустимого '" + Строка(МинЗначение) + "'.", СтатусСообщения.Важное);
            Отказ = Истина; // Отказываем в записи
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Важно отметить, что событие ПередЗаписьюНаСервере в модуле формы работает с объектом данных формы, а ОбработкаПроверкиЗаполнения в модуле объекта — непосредственно с объектом базы данных. Оба подхода дополняют друг друга, создавая надежный барьер для некорректных данных.

Ограничение количества строк в табличной части (дополнительно)

Хотя наш основной вопрос касался ограничения числовых значений в строке, иногда в проектах возникает потребность ограничить и общее количество строк в табличной части. Это можно реализовать в событии ПередНачаломДобавления табличного поля:


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

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

Резюме и лучшие практики

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

  1. Основная клиентская валидация (при интерактивном вводе): Мы используем событие ПриНачалеРедактирования табличного поля для динамической установки свойств МаксимальноеЗначение и МинимальноеЗначение элемента формы, который отображает нашу числовую колонку. Этот подход обеспечивает мгновенную обратную связь для пользователя, предотвращая ввод некорректных данных непосредственно в момент их ввода, и при этом является наиболее производительным.
  2. Дополнительная клиентская валидация (при программном изменении на клиенте): Если существуют сценарии, когда значения могут быть изменены на клиенте программно (например, вставка из буфера обмена), или нам требуется более сложная логика корректировки, рассмотрите использование события ПриИзменении для поля ввода.
  3. Серверная и объектная валидация (при записи): Крайне важно дублировать логику проверки в событиях модуля объекта (ОбработкаПроверкиЗаполнения) и/или событиях формы на сервере (ПередЗаписьюНаСервере). Эти уровни валидации служат последней линией защиты и обеспечивают целостность данных в базе, независимо от способа их ввода или изменения. Они предотвращают сохранение некорректных данных даже в обход клиентских проверок.

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

← На главную