Обработка событий отчетов

d

Событие BeforePrint: не попадитесь в ловушку «пустого» отчета

Самое распространенное заблуждение среди новичков — считать, что событие BeforePrint вызывается строго один раз перед печатью всего документа. На практике, и FastReport, и ReportBuilder могут генерировать это событие многократно, особенно при предпросмотре или в многопоточной среде. Эксперты всегда ставят защитный флаг: переменная-счетчик или проверка состояния отчета. Иначе вы рискуете повторно открыть соединение с БД или наложить данные поверх уже выведенных — получите «слоеный пирог» вместо отчета. Совет: используйте свойство Tag главного компонента отчета как флаг инициализации.

OnGetValue: где подвох со ссылками на классы

Многие разработчики полагают, что в обработчике OnGetValue (или аналогах вроде OnData) можно безопасно работать с глобальными объектами VCL. Это ошибка. В момент вызова этого события контекст печати уже «заморожен» для рендеринга. Если вы попытаетесь обратиться к гриду, который перерисовывается в UI-потоке, — получите либо deadlock, либо исключение. Профессионалы передают в отчет данные только через подготовленный список TList или массив Variant, а сам обработчик реализуют как чистое получение значения по индексу — без побочных эффектов.

Событие AfterPrint: неочевидный «сброс» ресурсов

Типичный совет в мануалах — освобождать временные файлы в AfterPrint. Но мало кто предупреждает: это событие вызывается и при отмене печати пользователем. Если вы удалите изображение, которое еще нужно для предпросмотра, — отчет превратится в набор красных крестов. Опытные дельфисты проверяют флаг PrintSuccess или Aborted в параметрах события. Даже если ваша библиотека не предоставляет такую проверку, храните свои ресурсы в TObjectList и чистите их только после полного уничтожения TfrxReport (OnDestroy объекта).

OnHandleClick: миф о «живом» отчете

На форумах часто спрашивают: как из обработчика клика в отчете (OnClick на объекте TfrxMemoView) получить доступ к форме приложения. Забудьте — в момент генерации отчета вы работаете на холсте, а не в живой форме. Любое обращение Screen.ActiveForm в этом контексте даст вам nil. Вместо этого элегантный прием: сохраните нужную ссылку в переменной TfrxReport через позднее связывание (через свойство UserData), и используйте ее только после того, как отчет снова перейдет в режим дизайнера или просмотра. И никогда не модальные окна из такого события — только PostMessage с последующей обработкой.

Нюанс с порядком событий в дочерних полосах

Даже Senior-разработчики иногда путаются: если у вас есть MasterData и DetailData, события BeforePrint и AfterPrint для каждой полосы могут вызываться в порядке вложенности, а не сверху вниз. То есть сначала DetailData может получить сигнал, а потом MasterData. Особенно это проявляется при группировках. Это не баг — это оптимизация для пейджинга. Экспертный хак: не полагайтесь на последовательность событий — инициализируйте переменные не в BeforePrint, а в начале отчета, используя OnStartReport или аналог, либо вешайте логику на уровень страницы.

Совет по отладке событий, чтобы не перекомпилировать проект

Пожалуй, самый ценный профессиональный совет: никогда не пишите в события отчета (особенно в FastReport) объемный код напрямую в Delphi IDE. Каждая правка требует перекомпиляции модуля и перезапуска билдера отчета. Гораздо эффективнее вынести всю логику в отдельный класс-хендлер, который реализует интерфейс IFrxReportComponent. Тогда изменения вносятся только в одном методе, а отчет пересобирается через RefreshReport без полной перекомпиляции. Это практически не документировано, но применяется в enterprise-разработке.

Ресурсный менеджмент: о чем молчат документации

В событийной модели отчетов часто забывают про утечки BDE или ADO-соединений. Если в событии BeforePrint вы открываете TADOQuery, то обязательно проверьте, не был ли он уже открыт. Иначе после пяти предпросмотров вы получите исключение «Query is already active». Решение парадоксальное: открывайте соединение один раз в внешнем коде и передавайте в событие только ссылку на TDataSet, а сам отчет пусть читает уже открытый курсор. Это избавит вас от 80% проблем с событиями.

И последнее: если ваш отчет печатается на среде с разными версиями FastReport (например, от FR4 к FR6), учитывайте, что события OnBeforeConnect может не быть. Эмулируйте его через Engine.OnRunReport.

Добавлено: 27.04.2026