Как правильно найти и использовать номер строки табличной части в 1С 8.3

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

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

Разница между Номером строки, Индексом и Идентификатором

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

  1. Номер строки (НомерСтроки): Это стандартный реквизит табличной части, хранящийся непосредственно в базе данных. Нумерация строк в нем всегда начинается с 1. Физический номер строки динамически меняется платформой при любой сортировке, удалении или перемещении строк пользователем интерактивно на форме. К нему не стоит привязываться для точной идентификации строки в процессе сложных вычислений.
  2. Индекс строки: Порядковый номер элемента в коллекции строк на сервере. Индексация в коллекциях 1С (как и во многих языках программирования) начинается с 0. Индекс жестко привязан к текущей физической позиции строки в коллекции в данный момент времени. Если пользователь переместит строку вверх или вниз, ее индекс тут же изменится.
  3. Идентификатор строки (ID): Уникальное числовое значение, которое платформа присваивает строке таблицы формы при ее выводе на экран. Этот идентификатор гарантированно не меняется в течение всего времени жизни формы. Сортировка таблицы, удаление соседних строк или перемещение строки никак не влияют на ее идентификатор. Свойство таблицы формы ТекущаяСтрока хранит именно уникальный идентификатор строки, а не ее индекс или номер.

Как правильно получить номер текущей строки на клиенте

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

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


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

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

Анализ и исправление критической ошибки при передаче данных на сервер

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


// ОШИБОЧНЫЙ КОД!
ИдСтроки = Элементы.Товары.ТекущаяСтрока;
СтрокаОбъекта = Объект.Товары.Получить(ИдСтроки); // КРИТИЧЕСКАЯ ОШИБКА!

Выясним причину, почему так делать категорически нельзя. Метод Получить() у коллекции табличной части ожидает на вход индекс строки (число от 0 до N-1). Мы же передаем туда Идентификатор строки. Эти значения совпадут только в одном случае: если с момента открытия формы пользователь не удалял, не добавлял и не перемещал строки в таблице.

Представим ситуацию: пользователь открыл документ с тремя строками. Их индексы: 0, 1, 2. Их идентификаторы на форме тоже совпали: 0, 1, 2. Затем пользователь удалил первую строку (с индексом 0). Теперь у оставшихся строк индексы сместились и стали равны 0 и 1. Однако их уникальные идентификаторы на форме остались прежними: 1 и 2! Если мы попытаемся вызвать метод Получить(1), платформа вернет нам строку с индексом 1 (которая изначально была третьей), хотя пользователь выделил строку с идентификатором 1 (которая изначально была второй). Это приведет к перезаписи чужих данных и нарушению логики документа.

Проанализируем правильный паттерн поиска строки на сервере по ее идентификатору. Для этого мы должны использовать специализированный метод НайтиПоИдентификатору():


&НаСервере
Процедура ИзменитьСтрокуНаСервере()
    // Получаем уникальный идентификатор выделенной строки с формы
    ИдентификаторСтроки = Элементы.Товары.ТекущаяСтрока;
    
    Если ИдентификаторСтроки <> Неопределено Тогда
        // Ищем строку в коллекции данных формы по идентификатору
        СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
        
        Если СтрокаТЧ <> Неопределено Тогда
            // Здесь мы можем безопасно менять любые реквизиты строки
            СтрокаТЧ.Цена = 150;
            СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Как программно добавить и активировать новую строку на клиенте

Разберем еще один популярный сценарий: нам нужно программно добавить новую строку в табличную часть на клиенте и сразу же сфокусировать на ней внимание пользователя (сделать ее активной на форме).

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


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

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

Поиск строки по реальному "Номеру строки"

Иногда возникают ситуации, когда у нас есть только физический НомерСтроки (например, при парсинге файлов Excel, загрузке внешних данных или обработке результатов сложных SQL-запросов). Как мы уже выяснили, обращаться напрямую по индексу Объект.Товары[НомерСтроки - 1] крайне небезопасно.

Для гарантированно точного поиска строки по ее реальному номеру в базе данных следует использовать метод Найти(). Посмотрим на практический пример реализации такого поиска:


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

Оптимальный паттерн работы без контекста формы

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

Для оптимизации скорости работы рекомендуется использовать внеконтекстные вызовы &НаСервереБезКонтекста. Но в таких процедурах у нас нет доступа к реквизиту Объект и элементам формы, а значит, мы не можем оперировать идентификаторами строк. Как же быть?

Давайте разберем эффективный паттерн проектирования:

  1. На клиенте мы считываем данные только текущей строки (из свойства ТекущиеДанные) и упаковываем их в структуру.
  2. Передаем эту компактную структуру в серверную функцию без контекста.
  3. На сервере выполняем необходимые расчеты (например, ищем цены, скидки или остатки в базе данных) и возвращаем результат обратно в виде структуры.
  4. На клиенте распаковываем полученную структуру и записываем результаты напрямую в ТекущиеДанные.

Посмотрим, как выглядит реализация этого паттерна на практике:


&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    
    Если ТекущиеДанные <> Неопределено Тогда
        // 1. Упаковываем исходные данные в структуру
        ПараметрыЗапроса = Новый Структура;
        ПараметрыЗапроса.Вставить("Номенклатура", ТекущиеДанные.Номенклатура);
        ПараметрыЗапроса.Вставить("ТипЦен", Объект.ТипЦен);
        
        // 2. Вызываем сервер без контекста
        РезультатыРасчета = ПолучитьЦенуНаСервереБезКонтекста(ПараметрыЗапроса);
        
        // 4. Записываем результаты обратно в текущую строку
        ТекущиеДанные.Цена = РезультатыРасчета.Цена;
        ТекущиеДанные.Сумма = ТекущиеДанные.Количество * РезультатыРасчета.Цена;
    КонецЕсли;
КонецПроцедуры

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

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

← На главную