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