В процессе разработки в среде 1С:Предприятие 8.3 на управляемых формах часто возникает задача синхронизации данных между несколькими табличными частями. Типичный сценарий: пользователь меняет данные в строке таблицы «А», и программа должна мгновенно пересчитать показатели, опираясь на значения в таблице «Б». Однако начинающие разработчики часто сталкиваются с тем, что код, который кажется логичным, внезапно прерывается ошибкой «Значение не является значением объектного типа».
В этой статье мы подробно разберем, почему использование свойства ТекущиеДанные для обращения ко второй таблице является ошибкой, проанализируем механику работы клиентского приложения и рассмотрим правильные способы реализации подобных расчетов.
Рассмотрим исходный код, который стал причиной проблемы. Разработчик пытается в процедуре РасчетПолотнаПриИзменении, которая привязана к одной таблице, получить данные из другой таблицы через элементы формы:
&НаКлиенте
Процедура РасчетПолотнаПриИзменении(Элемент)
ТД2 = Элементы.Раскладка.ТекущиеДанные; // Ошибка зреет здесь
ТД = Элементы.РасчетПолотна.ТекущиеДанные;
// ... расчеты ...
ТД.НаДокрой = ТД2.Крой * ТД.КоэфДокроя / 100 * ТД2.СрГрИзд; // Ошибка вылетает здесь
КонецПроцедуры
Проанализируем происходящее. Когда пользователь редактирует ячейку в таблице РасчетПолотна, фокус ввода (курсор) находится именно в этой таблице. Свойство ТекущиеДанные элемента формы возвращает данные активной строки — той, на которой стоит курсор.
Для таблицы Раскладка в этот момент может не быть активной строки, либо она может быть не выбрана программно. В этом случае Элементы.Раскладка.ТекущиеДанные возвращает значение Неопределено. Как только код доходит до попытки прочитать свойство ТД2.Крой, система пытается обратиться к свойству Крой у значения Неопределено, что и вызывает исключение «Значение не является значением объектного типа».
Вместо того чтобы полагаться на то, куда нажал пользователь (на «текущие данные»), нам необходимо четко определить, какая именно строка из второй таблицы нам нужна. Если строки связаны между собой (например, по номеру или ключевому идентификатору), мы должны использовать методы поиска в коллекции Объект.
Рассмотрим, как переписать код, чтобы он работал стабильно. Допустим, нам нужна строка из таблицы Раскладка с тем же индексом, что и в текущей таблице. Посмотрим на пример:
&НаКлиенте
Процедура РасчетПолотнаПриИзменении(Элемент)
// Получаем текущую строку первой таблицы
ТекущаяСтрока = Элементы.РасчетПолотна.ТекущиеДанные;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
// Определяем индекс текущей строки
Индекс = Объект.РасчетПолотна.Индекс(ТекущаяСтрока);
// Пытаемся получить строку из второй таблицы по тому же индексу
// Важно: проверяем, существует ли такая строка во второй таблице
Если Объект.Раскладка.Количество() > Индекс Тогда
СтрокаРаскладки = Объект.Раскладка[Индекс];
// Теперь мы уверены, что объект существует, и можем проводить расчет
ТекущаяСтрока.НаДокрой = СтрокаРаскладки.Крой * ТекущаяСтрока.КоэфДокроя / 100 * СтрокаРаскладки.СрГрИзд;
КонецЕсли;
КонецПроцедуры
Важный момент: использование индекса оправдано только в том случае, если таблицы «зеркальны» и пользователь не может их сортировать независимо друг от друга. Если сортировка возможна, лучше использовать поиск по уникальному ключу (например, реквизит КлючСвязи).
Работа с Элементы.ИмяТаблицы.ТекущиеДанные на клиенте — это интерфейсная манипуляция. Для надежных расчетов, особенно если они затрагивают итоги таблиц или сложные связи, правильнее использовать серверный контекст. На сервере нам доступен весь объект документа со всеми его коллекциями.
Разберем алгоритм по шагам:
&НаСервере.Пример реализации такого подхода:
&НаКлиенте
Процедура РасчетПолотнаПриИзменении(Элемент)
СтрокаДанных = Элементы.РасчетПолотна.ТекущиеДанные;
Если СтрокаДанных <> Неопределено Тогда
// Передаем идентификатор строки для точного позиционирования на сервере
РассчитатьДанныеНаСервере(СтрокаДанных.ПолучитьИдентификатор());
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура РассчитатьДанныеНаСервере(ИдентификаторСтроки)
// Находим строку в коллекции данных формы через идентификатор
ТекСтрока = Объект.РасчетПолотна.НайтиПоИдентификатору(ИдентификаторСтроки);
// Теперь мы можем использовать методы поиска по всей второй таблице
// Например, суммировать показатели или искать связанные записи
Для Каждого СтрРаскладки Из Объект.Раскладка Цикл
// Пример упрощенной логики связи
Если СтрРаскладки.НомерСтроки = ТекСтрока.НомерСтроки Тогда
ТекСтрока.НаДокрой = СтрРаскладки.Крой * ТекСтрока.КоэфДокроя / 100;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Часто разработчики обращаются ко второй таблице не за конкретной строкой, а чтобы получить сумму или среднее значение по всей колонке. В этом случае вообще не нужно использовать ТекущиеДанные. Выясним, как получить итог по всей таблице Раскладка одной строкой кода.
Проанализируем метод Итог(). Это самый быстрый и эффективный способ получить данные всей коллекции:
&НаКлиенте
Процедура РасчетПолотнаПриИзменении(Элемент)
ТД = Элементы.РасчетПолотна.ТекущиеДанные;
// Получаем общую сумму кроя по всей второй таблице
ОбщийКрой = Объект.Раскладка.Итог("Крой");
Если ОбщийКрой > 0 Тогда
ТД.НаДокрой = ОбщийКрой * ТД.КоэфДокроя / 100;
КонецЕсли;
КонецПроцедуры
Проанализируем сообщения опытных разработчиков из обсуждения. Они справедливо указывают на плохую практику использования сокращений типа ТД, ТД2.
Почему это важно:
ТД2 — таблица дополнений, таблица данных или таблица документов. Используйте понятные имена: СтрокаРасчета, СтрокаРаскладки.ТекущиеДанные, на Неопределено. Это золотое правило разработки управляемых форм 1С.Подведем итог: никогда не используйте свойство ТекущиеДанные одной таблицы внутри событий другой таблицы для получения связанных значений. ТекущиеДанные — это то, что «подсвечено» мышкой. Для программной логики используйте методы Индекс(), Найти(), НайтиСтроки() или Итог() применительно к коллекции Объект.ИмяТаблицы. Это сделает ваш код надежным и защитит пользователя от внезапных ошибок при случайных кликах по форме.