В процессе разработки на платформе «1С:Предприятие» программисты часто сталкиваются с двумя техническими вызовами. Первый связан с эстетикой и производительностью интерфейса: когда на форме программно изменяется множество элементов, она начинает «мерцать», что раздражает пользователя. Второй вызов — чисто алгоритмический: как в отчете или динамическом списке присвоить объектам места (рейтинг) так, чтобы при одинаковых показателях присваивалось одинаковое место без пропусков в нумерации. Рассмотрим подробные решения этих задач, опираясь на опыт разработчиков и возможности платформы.
В «Обычных формах» 1С (в отличие от «Управляемых») отсутствует встроенный метод ЭтаФорма.Обновить() с параметром запрета перерисовки. Когда мы в цикле меняем свойства Видимость, Доступность или заголовки у десятков элементов, операционная система пытается перерисовать форму после каждого изменения. Это и создает эффект «дребезга» интерфейса. Разберем основные способы решения этой проблемы.
Метод 1. Использование группировки и управления видимостью
Самый простой и эффективный способ в рамках штатных средств — это объединение всех динамически изменяемых элементов в одну группу (рамку группы) или на одну панель. Проанализируем алгоритм действий:
Панель или Группа).ЭлементыФормы.ИмяГруппы.Видимость = Ложь;.ЭлементыФормы.ИмяГруппы.Видимость = Истина;.Этот подход заставляет платформу перерисовать всё содержимое контейнера один раз, а не для каждого элемента по отдельности. Рассмотрим пример кода:
// Скрываем контейнер для оптимизации перерисовки
ЭлементыФормы.ОсновнаяПанель.Видимость = Ложь;
Для Каждого Элемент Из СписокЭлементов Цикл
// Выполняем множество изменений
Элемент.Доступность = ПроверитьДоступность(Элемент);
Элемент.ЦветТекста = ОпределитьЦвет(Элемент);
КонецЦикла;
// Возвращаем видимость одним действием
ЭлементыФормы.ОсновнаяПанель.Видимость = Истина;
Если скрытие основной области формы выглядит «некрасиво», мы можем применить архитектурный прием с переключением страниц. Разберем этот вариант по шагам:
Панель без отображения закладок.Это не только убирает мерцание, но и дает пользователю понять, что система занята выполнением задачи.
Для тех случаев, когда штатные методы не помогают, можно прибегнуть к использованию функций операционной системы Windows. Мы можем отправить окну формы сообщение WM_SETREDRAW, которое полностью запрещает перерисовку до тех пор, пока мы не разрешим её снова. Выясним, как это реализовать:
// Определение внешней функции
#Если Клиент Тогда
Процедура ЗаблокироватьПерерисовку(Знач ХэндлОкна, Знач Состояние) Экспорт
// Подключаем user32.dll для работы с сообщениями Windows
Попытка
User32 = Новый COMОбъект("DynamicWrapperX"); // Или использование внешних компонент
User32.Register("user32.dll", "SendMessageA", "i=llll", "f=s", "r=l");
User32.SendMessageA(ХэндлОкна, 11, Состояние, 0); // 11 - это WM_SETREDRAW
Исключение
// Обработка ошибки, если компонента не зарегистрирована
КонецПопытки;
КонецПроцедуры
#КонецЕсли
Важно: При использовании этого метода необходимо гарантировать, что флаг перерисовки будет возвращен в истину даже в случае возникновения ошибки (используйте блок Попытка...Исключение), иначе форма останется «мертвой» для интерфейса.
Перейдем ко второму вопросу: как правильно пронумеровать строки в отчете, если у нескольких объектов (например, сотрудников или товаров) одинаковые числовые показатели, и им нужно присвоить одно и то же место? Это называется «плотным рейтингом» (Dense Rank).
Проанализируем классический подход через язык запросов 1С. Основная идея заключается в том, чтобы для каждой записи подсчитать количество уникальных значений, которые больше (или меньше) текущего значения.
Предположим, у нас есть таблица с полями Сотрудник и Показатель. Нам нужно вывести место:
ВЫБРАТЬ
Т1.Сотрудник,
Т1.Показатель,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.Показатель) КАК Место
ИЗ
ВТ_Данные КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Данные КАК Т2
ПО Т1.Показатель < Т2.Показатель
СГРУППИРОВАТЬ ПО
Т1.Сотрудник,
Т1.Показатель
УПОРЯДОЧИТЬ ПО
Т1.Показатель УБЫВ
Разберем, как это работает: если у нас есть показатели 100, 90, 90, 80, то для значения 90 условие Т1.Показатель < Т2.Показатель сработает только для значения 100. Количество различных значений будет равно 1, и прибавив 1 (или используя <=), мы получим второе место для обоих сотрудников с показателем 90.
Как справедливо отмечено в обсуждении, прямое соединение таблицы на 500 записей самой с собой порождает 250 000 временных строк в памяти СУБД. Если данных много (тысячи записей), используем двухэтапный расчет:
РАЗЛИЧНЫЕ показатели во временную таблицу ВТ_УникальныеПоказатели.Показатель.Этот способ позволяет избежать геометрической прогрессии нагрузки на сервер.
Если ваша конфигурация работает на современной версии платформы, задача решается элегантно с помощью оконных функций. Проанализируем пример синтаксиса:
ВЫБРАТЬ
Сотрудник,
Показатель,
ПЛОТНЫЙ_НОМЕР() ПО (УПОРЯДОЧИТЬ ПО Показатель УБЫВ) КАК Место
ИЗ
Справочник.Сотрудники
Функция ПЛОТНЫЙ_НОМЕР() (аналог DENSE_RANK в SQL) автоматически присваивает одинаковые номера одинаковым значениям, при этом следующему за ними значению присваивается номер, идущий непосредственно по порядку (без «дырок»).
Иногда проще и быстрее рассчитать места не в самом запросе, а при выводе данных в коллекцию или таблицу. Посмотрим на алгоритм:
Результат = Запрос.Выполнить().Выбрать();
ТекущееМесто = 0;
ПредыдущееЗначение = Неопределено;
Пока Результат.Следующий() Цикл
Если Результат.Показатель <> ПредыдущееЗначение Тогда
ТекущееМесто = ТекущееМесто + 1;
ПредыдущееЗначение = Результат.Показатель;
КонецЕсли;
// Используем ТекущееМесто для вывода
Сообщить(Результат.Сотрудник + ": " + ТекущееМесто + " место");
КонецЦикла;
Данный метод работает за один проход по данным ($O(N)$), что делает его самым производительным для огромных выборок, если не требуется сложная сортировка внутри самого SQL-сервера.
Подведем итог: для предотвращения мерцания в обычных формах используйте временное скрытие групп элементов или переключение страниц панели. Для реализации системы рейтингов выбирайте между оконными функциями (в новых версиях 1С) или алгоритмом сравнения с предыдущим значением при обходе выборки.