При разработке в среде 1С:Предприятие 8.3 (особенно при переходе на управляемые формы) начинающие разработчики часто сталкиваются с трудностями при работе с табличными частями. Вопрос получения номера текущей строки кажется тривиальным, однако за ним скрывается целый ряд важных архитектурных нюансов. Ошибки в работе с индексами, номерами и идентификаторами строк могут приводить к трудновоспроизводимым багам в многопользовательском режиме. В этой статье мы подробно разберем, чем отличаются эти понятия, как правильно получать данные на клиенте и сервере, а также рассмотрим оптимальные паттерны проектирования.
Прежде чем писать код, давайте приведем в порядок терминологию. В платформе 1С для работы со строками таблиц используются три различные числовые величины. Понимание разницы между ними убережет нас от критических ошибок в будущем:
НомерСтроки): Это стандартный реквизит табличной части, хранящийся непосредственно в базе данных. Нумерация строк в нем всегда начинается с 1. Физический номер строки динамически меняется платформой при любой сортировке, удалении или перемещении строк пользователем интерактивно на форме. К нему не стоит привязываться для точной идентификации строки в процессе сложных вычислений.ТекущаяСтрока хранит именно уникальный идентификатор строки, а не ее индекс или номер.Рассмотрим стандартный сценарий: пользователь выделил строку в табличной части на управляемой форме, и нам необходимо получить ее физический номер для отображения или простых клиентских расчетов. Самый простой и лаконичный способ сделать это на клиенте — обратиться к свойству ТекущиеДанные таблицы формы.
Давайте разберем практический пример. Предположим, у нас есть табличная часть с именем "Товары", выведенная на управляемую форму как таблица формы с аналогичным именем. Нам нужно получить номер текущей строки:
&НаКлиенте
Процедура ПолучитьНомерТекущейСтроки()
// Сначала получаем текущие данные таблицы формы
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
// Обязательно проверяем, что в таблице выделена строка (данные не равны Неопределено)
Если ТекущиеДанные <> Неопределено Тогда
НомерСтроки = ТекущиеДанные.НомерСтроки;
ПоказатьПредупреждение(, "Номер текущей строки: " + Строка(НомерСтроки));
Иначе
ПоказатьПредупреждение(, "Ни одна строка не выбрана!");
КонецЕсли;
КонецПроцедуры
Этот способ является наиболее безопасным и производительным для выполнения на стороне клиента, поскольку он не требует вызова серверного контекста и напрямую считывает данные из кэша формы.
Очень часто разработчики пытаются использовать полученные на клиенте идентификаторы для поиска строки на сервере. Рассмотрим типичную архитектурную ошибку, которую часто допускают на форумах программистов:
// ОШИБОЧНЫЙ КОД!
ИдСтроки = Элементы.Товары.ТекущаяСтрока;
СтрокаОбъекта = Объект.Товары.Получить(ИдСтроки); // КРИТИЧЕСКАЯ ОШИБКА!
Выясним причину, почему так делать категорически нельзя. Метод Получить() у коллекции табличной части ожидает на вход индекс строки (число от 0 до N-1). Мы же передаем туда Идентификатор строки. Эти значения совпадут только в одном случае: если с момента открытия формы пользователь не удалял, не добавлял и не перемещал строки в таблице.
Представим ситуацию: пользователь открыл документ с тремя строками. Их индексы: 0, 1, 2. Их идентификаторы на форме тоже совпали: 0, 1, 2. Затем пользователь удалил первую строку (с индексом 0). Теперь у оставшихся строк индексы сместились и стали равны 0 и 1. Однако их уникальные идентификаторы на форме остались прежними: 1 и 2! Если мы попытаемся вызвать метод Получить(1), платформа вернет нам строку с индексом 1 (которая изначально была третьей), хотя пользователь выделил строку с идентификатором 1 (которая изначально была второй). Это приведет к перезаписи чужих данных и нарушению логики документа.
Проанализируем правильный паттерн поиска строки на сервере по ее идентификатору. Для этого мы должны использовать специализированный метод НайтиПоИдентификатору():
&НаСервере
Процедура ИзменитьСтрокуНаСервере()
// Получаем уникальный идентификатор выделенной строки с формы
ИдентификаторСтроки = Элементы.Товары.ТекущаяСтрока;
Если ИдентификаторСтроки <> Неопределено Тогда
// Ищем строку в коллекции данных формы по идентификатору
СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
Если СтрокаТЧ <> Неопределено Тогда
// Здесь мы можем безопасно менять любые реквизиты строки
СтрокаТЧ.Цена = 150;
СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Разберем еще один популярный сценарий: нам нужно программно добавить новую строку в табличную часть на клиенте и сразу же сфокусировать на ней внимание пользователя (сделать ее активной на форме).
Для решения этой задачи мы воспользуемся методом ПолучитьИдентификатор() у только что созданной строки данных формы. Это позволит нам связать коллекцию данных с элементом управления формы:
&НаКлиенте
Процедура ДобавитьИВыделитьНовуюСтроку()
// Добавляем новую строку в коллекцию
НоваяСтрока = Объект.Товары.Добавить();
// Заполняем реквизиты по умолчанию
НоваяСтрока.Количество = 1;
// Получаем уникальный идентификатор созданной строки
УникальныйИД = НоваяСтрока.ПолучитьИдентификатор();
// Передаем фокус элементу формы и устанавливаем его текущей строкой
Элементы.Товары.ТекущаяСтрока = УникальныйИД;
КонецПроцедуры
Благодаря этому коду платформа автоматически прокрутит список товаров к новой строке и выделит ее цветом, что делает интерфейс программы дружелюбным и понятным для конечного пользователя.
Иногда возникают ситуации, когда у нас есть только физический НомерСтроки (например, при парсинге файлов Excel, загрузке внешних данных или обработке результатов сложных SQL-запросов). Как мы уже выяснили, обращаться напрямую по индексу Объект.Товары[НомерСтроки - 1] крайне небезопасно.
Для гарантированно точного поиска строки по ее реальному номеру в базе данных следует использовать метод Найти(). Посмотрим на практический пример реализации такого поиска:
&НаСервере
Процедура НайтиСтрокуПоНомеру(ИскомыйНомер)
// Производим поиск строки по реквизиту "НомерСтроки"
СтрокаТЧ = Объект.Товары.Найти(ИскомыйНомер, "НомерСтроки");
Если СтрокаТЧ <> Неопределено Тогда
// Строка успешно найдена, выполняем необходимые действия
СтрокаТЧ.Комментарий = "Проверено аудитором";
Иначе
ВызватьИсключение "Строка с номером " + Строка(ИскомыйНомер) + " не найдена в документе!";
КонецЕсли;
КонецПроцедуры
При разработке под управляемый интерфейс критически важно минимизировать объем данных, передаваемых между клиентом и сервером. Обычный вызов контекстной процедуры &НаСервере гоняет туда-обратно всю форму целиком (весь тяжелый Объект), что может сильно замедлять работу системы на медленных каналах связи.
Для оптимизации скорости работы рекомендуется использовать внеконтекстные вызовы &НаСервереБезКонтекста. Но в таких процедурах у нас нет доступа к реквизиту Объект и элементам формы, а значит, мы не можем оперировать идентификаторами строк. Как же быть?
Давайте разберем эффективный паттерн проектирования:
ТекущиеДанные) и упаковываем их в структуру.ТекущиеДанные.Посмотрим, как выглядит реализация этого паттерна на практике:
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
// 1. Упаковываем исходные данные в структуру
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("Номенклатура", ТекущиеДанные.Номенклатура);
ПараметрыЗапроса.Вставить("ТипЦен", Объект.ТипЦен);
// 2. Вызываем сервер без контекста
РезультатыРасчета = ПолучитьЦенуНаСервереБезКонтекста(ПараметрыЗапроса);
// 4. Записываем результаты обратно в текущую строку
ТекущиеДанные.Цена = РезультатыРасчета.Цена;
ТекущиеДанные.Сумма = ТекущиеДанные.Количество * РезультатыРасчета.Цена;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьЦенуНаСервереБезКонтекста(ПараметрыЗапроса)
// 3. Выполняем легкий запрос к базе данных без передачи контекста формы
СтруктураОтбора = Новый Структура("Номенклатура", ПараметрыЗапроса.Номенклатура);
ЦенаТовара = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(ТекущаяДата(), СтруктураОтбора).Цена;
Результат = Новый Структура;
Результат.Вставить("Цена", ЦенаТовара);
Возврат Результат;
КонецФункции
Применение данного паттерна позволяет разгрузить сервер, снизить трафик и обеспечить мгновенный отклик интерфейса программы для пользователя даже при работе через мобильный интернет или веб-клиент.