Как автоматически заполнить одну колонку табличной части при выборе значения в другой в 1С 8.3?

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

При разработке интерфейсов на управляемых формах в 1С часто возникает стандартная задача: пользователь выбирает значение в одной колонке табличной части (например, конкретный риск), и система должна автоматически заполнить сопоставленное ему значение во второй колонке (например, причину возникновения этого риска). На первый взгляд задача кажется простой, но в управляемом приложении она требует четкого понимания клиент-серверного взаимодействия.

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

Понимание клиент-серверной архитектуры в управляемых формах

Прежде чем перейти к написанию кода, проанализируем, почему возникает сложность. В управляемом приложении интерфейс (форма) "живет" на клиенте, а база данных и запросы доступны только на сервере.

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

Рассмотрим, как по шагам реализовать эту логику.

Шаг 1. Создание обработчика события на клиенте

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

Внутри этой процедуры нам нужно получить данные текущей строки. Для этого используется свойство управляемой формы Элементы.ИмяТабличнойЧасти.ТекущиеДанные. Разберем шаблон клиентской процедуры:


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

Шаг 2. Реализация серверной функции получения данных

Теперь создадим серверную функцию ПолучитьПричинуНаСервере. Обратите внимание, что мы объявляем ее с директивой компиляции &НаСервереБезКонтекста. Это гарантирует, что на сервер не будет передаваться тяжелый контекст всей формы (данные других реквизитов, элементов управления), что значительно ускорит работу интерфейса.

Внутри функции мы пишем стандартный запрос к базе данных. Проанализируем правильный вариант реализации:


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

Важный нюанс оптимизации запроса: Обратите внимание на условие связи в запросе. Начинающие разработчики часто пишут ПричиныРисков.Владелец.Ссылка = &Риск. Обращение через точку к стандартному реквизиту Ссылка является избыточным. Платформа 1С при компиляции такого запроса в SQL выполняет лишнее неявное левое соединение таблиц, что снижает скорость выполнения. Пишите лаконично и правильно: ПричиныРисков.Владелец = &Риск.

Очистка зависимого поля при очистке основного

Рассмотрим сценарий, который часто упускается из виду при проектировании логики. Что произойдет, если пользователь сначала выбрал риск (и заполнилась причина), а затем решил очистить ячейку "Риск"?

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


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

Оптимизация: Заполнение нескольких колонок через структуру

Выясним, как поступить, если при изменении риска нам нужно автоматически заполнить не одну колонку "Причина", а сразу несколько параметров (например: степень важности риска, ответственного сотрудника и примечание по умолчанию).

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

Посмотрим на практический пример такой реализации:


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

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

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

Альтернативный подход: Настройка "Связей параметров выбора" без кода

Проанализируем альтернативную ситуацию. Жесткое автозаполнение первой попавшейся причины из базы данных — не всегда оптимальный бизнес-сценарий. Часто пользователю требуется предоставить свободу выбора, но ограничить список доступных причин только теми, которые привязаны к выбранному в текущей строке риску (где риск является владельцем в иерархии справочников).

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

  1. Откройте конфигуратор и найдите нужный документ (или справочник), содержащий вашу табличную часть.
  2. Перейдите на вкладку Данные и откройте свойства реквизита табличной части Причина.
  3. В палитре свойств найдите строку Связи параметров выбора и нажмите кнопку открытия (три точки).
  4. В открывшемся окне нажмите кнопку Добавить.
  5. В поле Имя параметра связи укажите стандартный отбор по владельцу: Отбор.Владелец.
  6. В поле Путь к данным выберите реквизит этой же табличной части: ТекущиеДанные.Риск (или ИмяТабличнойЧасти.Риск).
  7. Сохраните изменения.

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

Обработка копирования и добавления новых строк

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

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

Рассмотрим пример обработки создания новой строки или копирования существующей:


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

Кэширование данных на клиенте для высоконагруженных систем

В распределенных системах или при работе пользователей через веб-клиент (тонкий клиент по медленному каналу интернет-связи) каждый интерактивный клик, вызывающий серверную процедуру &НаСервереБезКонтекста, может приводить к микросекундным задержкам "залипания" интерфейса. Если табличная часть содержит сотни строк, постоянные обращения к БД снижают комфорт работы.

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


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

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

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

← На главную