В процессе разработки на платформе 1С часто возникает задача отслеживания изменений не во всей таблице целиком, а в каждой её конкретной строке. Это необходимо для оптимизации записи данных, выделения измененных строк цветом или реализации сложной логики проверки данных. Рассмотрим подробнее, почему встроенное свойство Модифицированность относится только к форме в целом и как мы можем реализовать контроль изменений для каждой строки самостоятельно.
Прежде чем перейти к рабочим алгоритмам, проанализируем распространенную ошибку. Многие разработчики пытаются использовать функцию ЗначениеВСтрокуВнутр() для сравнения состояния строки "до" и "после" редактирования. Выясним причину, по которой этот метод не подходит для объекта СтрокаТаблицыЗначений.
Дело в том, что для строки таблицы (в отличие от простых типов или ссылочных объектов) эта функция возвращает внутренний идентификатор объекта в памяти, а не его содержимое. Посмотрим на пример кода, который наглядно это демонстрирует:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Поле", Новый ОписаниеТипов("Число"));
Строка = Таблица.Добавить();
Строка.Поле = 1;
Сообщить(ЗначениеВСтрокуВнутр(Строка)); // Выведет внутренний ID
Строка.Поле = 2;
Сообщить(ЗначениеВСтрокуВнутр(Строка)); // Выведет ТОТ ЖЕ САМЫЙ внутренний ID
Как мы видим, значения в колонках изменились, но строковое представление объекта осталось прежним. Следовательно, обычное сравнение через системный хеш здесь не поможет. Нам потребуются более гибкие инструменты.
Это наиболее простой и надежный метод, который выбрал автор темы. Разберем его по шагам. Мы добавляем в таблицу значений (или в реквизит формы типа ДанныеФормыКоллекция) дополнительную колонку Изменено с типом Булево.
ПриИзменении.Пример реализации обработчика:
&НаКлиенте
Процедура ТаблицаДанныхКолонка1ПриИзменении(Элемент)
ТекущаяСтрока = Элементы.ТаблицаДанных.ТекущиеДанные;
Если ТекущаяСтрока <> Неопределено Тогда
ТекущаяСтрока.Изменено = Истина;
КонецЕсли;
КонецПроцедуры
Важный нюанс: Чтобы избежать ложных срабатываний (когда пользователь зашел в ячейку и просто нажал Enter, не меняя данных), в начале редактирования стоит запоминать старое значение и сравнивать его с новым.
Если мы не хотим добавлять лишние колонки в данные, проанализируем ситуацию с использованием временных хранилищ или клиентских переменных. Мы можем «снимать слепок» строки в момент начала редактирования и сравнивать его в момент окончания.
Для этого лучше всего подходит объект Структура. Рассмотрим алгоритм:
ПриНачалеРедактирования выгружаем данные текущей строки в структуру.ПриОкончанииРедактирования сравниваем текущие значения колонок с сохраненными в структуре.
&НаКлиенте
Перем ДанныеСтрокиДоИзменения;
&НаКлиенте
Процедура ТаблицаДанныхПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
Если НоваяСтрока Тогда
ДанныеСтрокиДоИзменения = Неопределено;
Иначе
// Фиксируем состояние всех колонок
ДанныеСтрокиДоИзменения = Новый Структура;
ТекущиеДанные = Элементы.ТаблицаДанных.ТекущиеДанные;
Для Каждого Колонка Из Элементы.ТаблицаДанных.ПодчиненныеЭлементы Цикл
ДанныеСтрокиДоИзменения.Вставить(Колонка.Имя, ТекущиеДанные[Колонка.Имя]);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ТаблицаДанныхПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
Если ОтменаРедактирования Или НоваяСтрока Тогда
Возврат;
КонецЕсли;
ТекущиеДанные = Элементы.ТаблицаДанных.ТекущиеДанные;
Для Каждого КлючИЗначение Из ДанныеСтрокиДоИзменения Цикл
Если ТекущиеДанные[КлючИЗначение.Ключ] <> КлючИЗначение.Значение Тогда
Сообщить("Строка с идентификатором " + ТекущиеДанные.ПолучитьИдентификатор() + " была изменена!");
Прервать;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Для более современных версий платформы (8.3) мы можем использовать преобразование в JSON. Это позволяет получить реальное строковое представление данных, которое будет меняться при изменении значений ячеек, в отличие от ЗначениеВСтрокуВнутр.
Выясним, как это работает: мы сериализуем строку (предварительно преобразовав её в структуру или соответствие) в формат JSON. Полученная строка и будет нашим «хешем» для сравнения.
// Пример функции для получения "слепка" строки
&НаКлиенте
Функция ПолучитьХешСтроки(ДанныеСтроки)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
// Преобразуем ДанныеФормыСтруктура в обычную структуру для сериализации
Структ = Новый Структура;
// Перечисляем только нужные значимые колонки
Структ.Вставить("Количество", ДанныеСтроки.Количество);
Структ.Вставить("Цена", ДанныеСтроки.Цена);
ЗаписатьJSON(ЗаписьJSON, Структ);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
При ручном сравнении значений (через <>) следует быть осторожными с типами Неопределено, NULL и пустыми ссылками. В 1С сравнение Неопределено и пустой ссылки справочника вернет Ложь (они не равны), что может быть расценено как изменение, хотя фактически поле осталось "пустым" для пользователя.
Также проанализируем ситуацию с добавлением и удалением строк:
НоваяСтрока в событиях начала редактирования.Помните, что в управляемом приложении таблица на форме — это объект типа ДанныеФормыКоллекция. У каждой строки такой коллекции есть уникальный идентификатор, который можно получить методом ПолучитьИдентификатор(). Используйте его для хранения "слепков" данных в соответствии (объект Соответствие), где ключом будет ID строки, а значением — структура её данных до изменения. Это позволит вам эффективно проверять модифицированность любой строки в любой момент времени, не привязываясь только к событиям окончания редактирования.
Таким образом, хотя встроенного свойства Строка.Модифицированность не существует, комбинация событий формы и сравнения структур данных позволяет реализовать этот механизм с высокой точностью.