Генератор табличных отчетов в WinWord RTF формат.

Карпов Владислав Софтсервис

Введение.

С первых дней моего знакомства с Microsoft Windows у меня появилась непреодолимая тяга к получению всяких разных выходных бланков, отчетов, да и в принципе любых бумажек со всеми теми возможностями, которые предоставляет операционная система. В сравнении со старым добрым DOS возможности эти, прямо скажем, неограниченны и не заканчиваются только наустановке различных шрифтов в одном документе. Речь идет о применении всей палитры средств, предоставляемой, например, таким широко распространенным редактором, как Microsoft Word. Тяга моя значительно усилилась, когда в одном из технических заданий очередного проекта (кстати, весьма значительного для весьма значительной нефтяной компании LukOil) было написано: “Все выходные формы в формате WinWord с последующей возможностью редактирования.. В принципе, все, что Вы найдете в данном документе - это полное описание того, как выполнялся мной вышеприведенный пункт технического задания плюс все возможные вариации на эту тему.

Краткое описание.

Продукт представляет из себя генератор табличных отчетов в WinWord RTF формате. Выполнен в трех вариантах - универсальная DLL для любых Windows приложений, компонент для Delphi 3 и компонент для C++ Builder 3.

Работает генератор следующем образом: Вы макетируете бланк отчета в среде Microsoft Word, где определяете секции отчета (REPORT HEADER, GROUP HEADER, BODY, и т.д.),и расставляете метки запросов на данные - такие как @0001, @0002, и т.д. Потом (для варианта QREP.DLL) в своей среде разработки определяете окно с оконнойфункцией и запускаете процесс построения отчета, который выделен в отдельный поток. Поэтому можно делать несколько отчетов одновременно. Процесс построения отчета делает лексический разбор бланка и начинает копировать секции отчета в выходной файл, при этом делая запросы в оконную функцию 1) на предоставление строк данных по меткам запросов данных, 2) передвижению источника данных, 3) запрос на конец источника данных. Вы должны прописать реакции на приходящие события в окно в своей среде разработки.

Для компонент Delphi и C++ Builder все значительно проще. Все сообщения, приходящие в окно, переопределены в события для Object Inspector. А само окно конструировать не надо, так как сами компоненты есть окна (наследники от TWinControl). Вам необходимо смакетировать бланк отчета; бросить на формукомпонент WordRpt; задать ему свойства имен входного и выходного бланка; определить несколько Event-ов, зависящих от Вашего отчета. Все системные вызовы скрыты внутри события, поэтому, например, для того, чтобы добавить в отчет данные, нужно просто заполнить строковую переменную, пришедшую вместе с событием OnBodyDataRequest по ссылке. (Единственно событие, которое должно быть всегда определено - это событие запрос на конец источника данных. Иначе процесс копирования может не закончится никогда J .)

Генератор отчета совершенно автономный. Он не зависит ни от среды разработки, ни от источника данных. Единственное требование - среда разработки должна быть под 32 разрядный Windows. Источникданных может быть всем чем угодно - ODBC, BDE, DataServer, ArrayServer, Ttable, Tquey, и т.д. и т.п. - вобщем, все, что можно представить в виде таблицы строк.

Компоненты для Delphi и C++ Builder не столь универсальны с точки зрениясреды разработки (естественно они привязаны жестко к Delphi и C++ Builder),но при этом значительно более удобны в использовании.

Компонент для C++ Builder не требует для работы библиотеки QREP.DLL и избавлен от некоторых ограничений, присущих QREP.DLL. Так ограничение на длину передаваемой строки для отчета в 2048 байт полностью отсутствует в компоненте для C++ Builder . Там можно передать строку в отчет длинной хоть мегабайт.

Выходной файл получается в формате Microsoft WinWord RTF и практически не зависит от версии Word.

Это достаточно быстрый построитель отчетов. 40 листов текста он формирует за 20 секунд на Pentium 150 64 Мб.

Это очень компактный генератор. QREP.DLL занимает 93 Кб на диске. Что по меркам Windows приложений просто ничто. (Найдите другой генератор с таким весом!).

Не требует значительных ресурсов. Каждый процесс резервирует 128 Кб памяти.

Кроме того, так как Вам самим приходится отвечать на запросы данных, Вы можете запрограммировать все, что поддается в принципе программированию,для ответов на эти запросы. И это просто здорово, с одной стороны. С другой,такие вещи, как просто расчет итоговых сумм, тоже придется программировать.

Что Вы найдете в поставке.

Корневая директория
Компонент и примеры для C++ Builder 3
Исходный код и библиотеки компонента для C++ Builder
Пример использования компонента для C++ Builder
Пример использования компонента для C++ Builder
Компонент и примеры для Delphi 3
Исходный код и библиотеки компонента для Delphi
Пример использования компонента для Delphi
Пример использования компонента для Delphi
Директория с документацией
Исходный код и примеры для универсальной QREP.DLL
Исходный код на Borland С++ 5.01
Небольшое описание редактирования бланков.
Директория примеров.
Пример работы QREP.DLL с CA-Visual Objects 2.0a-1
Пример работы QREP.DLL с CA-VO 2.0a-1. Картинки в отчете.
Пример работы QREP.DLL с Borland C++ Builder 3
Пример работы QREP.DLL с Borland Delphi 3

В корневой директории находится универсальный шаблон для начального макетирования бланка отчета в Microsoft Word.

Установка пакета.

Установка QREP.DLL для любых систем программирования под Windows.

Конкретная установка сильно зависит от того, начем Вы в дальнейшем будете работать. Возможны варианты: Borland C++ Builder3, Borland Delphi 3, Borland C++, все остальное под Windows. Если Вы будите работать со “Всем остальным под Windows”, то Вам потребуется только то, что находится вдиректории WordRpt\Qrep\Bin, а точнее всего одна маленькая DLL библиотека QREP.DLL, и прототипы функций, ей экспортируемые. Прототипы находятся в файле QREP.H и написанны в терминах языка С. Во первых надо определить доступ к библиотеке тем или иным способом. И во вторых, прописать прототипы функций, экспортируемых библиотекой в Вашу среду программирования. (Для С, С++, С++ Builder, Delphi и CA-Visual Objects 2.0a-1 прописывать ничего не надо, т.к. Вы это найдете в примерах и системных директориях). Для любителей острых ощущений предоставляется исходный текст библиотеки на языке С. При компиляции использовался Borland C++ 5.01. Соответственно в директории WordRpt\Qrep\Bin есть файл проекта для среды Borland C++ 5.01.

При дистрибуции Вышего приложения Вашему клиенту необходимо поставить вместе с Вашим продуктом , библиотеку QREP.DLL.

Установка компонента для Borland Delphi 3.

Компонент для Delphi написан с использованием библиотекиQREP.DLL. Поэтому (это важно) компонент не может существовать отдельно от библиотеки и при установке компонента Вы должны обеспечить доступ к QREP.DLL, для чего выполнить требования предыдущего раздела.

Для установки компонента выполните следующие шаги:

Если все сделано правильно, то в палитре компонент должна появиться закладка Word Report, в которой Вы найдете единственный компонент WordRpt.

Попробуйте после установки откомпилировать один из примеров. Вряд лион у Вас запустится из-за ненастроенных псевдонимов BDE, но сам факт безошибочной компиляции будет на 100% гарантировать безошибочную инсталляцию компонента.

Замечание: Если Вы проделаете все, что находится в предыдущем разделе, то библиотеку QREP.DLL не обязательно хранить вместе с компонентом.В этом случае лучше ее хранить там же где исходные тексты к ней. Естественно в PATH должен быть прописан путь как до компонента, так и до QREP.DLL.

Установка компонента для Borland С++ Builder 3.

Компонент для Borland C++ Builder 3 не требует библиотеки QREP.DLL. Все ее содержимое органично с небольшими переделками в части адаптации под классовую систему С++ входит в компонент. Это значит что компонент работает абсолютно автономно в отличие от компонента для Borland Delphi 3. Кроме того, компонент для С++ избавлен от некоторых ограничений, присущих библиотеке QREP.DLL. Так Вы можете запустить неограниченное число потоков (точнее ограниченное только памятью Вашего компьютера) дляодновременного получения нескольких отчетов. Вы можете передать в запрос на данные строку фактически любой длинны, в то время как QREP.DLL позволяет выводить в отчет строки длинной только 2048 байт, не более (включая завершающий 0). Так же компонент не содержит некоторые другие ограничения, о которых речь пойдет несколько позже.

Итак, для установки компоненты для С++ Builder Вам необходимо выполнить следующие шаги:

Если все сделано правильно, то в палитре компонент должна появится закладка Word Report в которой Вы найдете единственный компонент WordRpt.

Если Вы думаете, что это все, то ошибаетесь. В отличие от Delphi в C++ Builder 3 имеются еще заголовочные файлы, которые компилятор ищет либо в указанной директории, либо в текущей. Это значит, что для каждого проекта, который будет использовать WordRpt необходимо указывать директорию, где лежит файл WordRpt.h. Делается это в пункте меню Project->Options... В закладке Directories/Conditionals. Если Вы не указали конкретно путь к WordRpt.h, то среда сама спросит вас о том где он находится при первой компиляции и автоматически пропишет путь в Directories/Conditionals.

Введение в RTF формат.

RTF-формат (Rich Text Format) был определен фирмой Microsoft для общего обмена текстовыми документами. Формат довольно быстро завоевал свое место под солнцем и, сначала, поддерживался продуктами самого Microsoft (начиная с WinWord 2.0, Word for DOS 4.x, Works (не помню с какой версии, но не позже чем 3.0)), апотом и другими фирмами. В настоящий момент почти любая (себя уважающая) программа, работающая с текстами, если и не читает, то по крайнеймере экспортирует RTF-формат. Базируется этот формат на том, что состоит исключительно из символов, которые читабельны на любой РС или МАС-системе. Это, всем известный ASCII-стандарт. Кроме самого текста файл в формате RTF содержит управляющие коды, которые тоже представлены вчитабельной форме. Управляющий коды делятся на Control wordsи Control symbols.

Control word - состоит из набора символов и разделителя. Общая форма такова:

 

\набор символов разделитель

 

Control word всегда начинается с знака “\”. Для разделителей предусмотрены следующие знаки:

Для Control symbols используются отдельные буквы. Каждый такой управляющий символ начинается со знаком “\”.Общаяформа такова:

 

\контроль символ

 

Дополнительно к этому определена возможность с помощью фигурных скобок задавать группы последовательности символов.

 

{ - Начало группы.

} - Конец группы.

 

Такие группы используются, например, для сносок, колонтитулов и т.п. Одна группа может содержать неограниченное число других групп. Вложенность групп друг в друга ограниченна только возможностями программного средства, при помощи которого происходит редактирование или просмотр RTF файла.

Если же в исходном тексте встречается один из знаков \{} как ASCII-символ, то перед ним ставится символ “\”:

 

\\

\{

\}

 

Таким образом RTF-скеннер определяет, что следующий за “\” символ не является управляющим.

Весь раздел Введение в RTF формат целиком взят со странички Александра Кука http://pool-7.divo.ru/neuro/help_l1.html#nachalo с его разрешения. Домашняя страница автора: http://www.halyava.ru/alexkuck

Основная идея

Любой генератор отчетов делит отчет на определенные секции - REPORT HEADER, REPORT FOOTER, GROUP HEADER, GROUP FOOTER, BODY, и т.д. При работе происходит копирование каждой секции в выходной файл по определенным правилам. Так прикопировании надо заполнить информацией ссылки на данные, которые находятся в исходном документе в том или ином виде. Как правило есть одна секция, копирование которой повторяется до тек пор, пока отчет не достигнет конца источника данных. В данной реализации эта секция называется BODY.Сразу возникает проблема - как разметить входной бланк, чтобы синтаксический анализатор понял где начинается какая секция и где она заканчивается. Если бы я строил отчет в текстовый файл, то проблема решалась бы весьма просто - добавлением во входной файл специальных управляющих тегов, легко определяемых анализатором и показывающих с какого момента начинается та или иная секция отчета и где она заканчивается.

Сложнее обстоит дело с RTF файлом, да еще с RTF файлом от Microsoft Word, спецификация которого известна в полном объеме, пожалуй, только Microsoft.Как выйти из данной ситуации? К счастью выход есть. Выход заключается в том, чтобы поместить управляющей тег внутрь группы RTF файла, причем так,что бы он был неразрывен. Другими словами, надо добиться, что бы в RTF файле группа с управляющем тегом выглядела следующем образом:

 

{\v\f53\cf2\f17#BEGIN QREPORT

\par }

 

Алгоритм работы синтаксического анализатора, разбирающего входной бланк по секциям отчета, сразу становиться понятен. Ищем ключевые теги обычным поиском во входном тексте. Если нашли, то ищем открывающую фигурную скобку группы RTF файла и, соответственно, закрывающую. После определения пары, описывающей секцию отчета, я точно буду знать, что секция начинается с закрывающей фигурной скобки группы открывающего тега, и заканчивается на открывающей фигурной скобке группы закрывающего тега:

 

{\v\f53\cf2\f17#BEGIN REPORT HEADER

\par }

 

Тело секции REPORT HEADER

 

{\v\f53\cf2\f17#END REPORT HEADER

\par }

 

Итак, будем считать, что проблема с разметкой входного документа решена. Теперь попробуем решить другую проблему - проблему размещения полезной информации в отчет. Проблема эта имеет две нюанса:

Касательно первого нюанса, неплохо было бы брать данные для отчета из любого источника, доступного для Вашего приложения. Я говорю об универсальном механизме, не зависящем от реализации того или иного источника данных. Главное, чтобы эти данные можно было представить в табличной форме. И это понятно, ведь я пытаюсь построить генератор табличных отчетов.

Исходя из ранееупамянутого, я могу для второго нюанса придумать, например, следующей механизм. Помещать в любое место любой секции выходного бланка отчета ссылки на данные, т.е. определять места во входном бланке,где должны размещаться данные из пользовательского источника данных. Сами ссылки должны удовлетворять определенным требованиям, так программа копирование секций отчета должна однозначно определить, что это ссылка на данные, вместо которой она должна скопировать информацию из какого-то другого места.

Проще всего зарезервировать для ссылки на данные какой-либо малоупотребительный символ, а сами ссылки пронумеровать, например:

 

{\v\f53\cf2\f17#BEGIN BODY

\par }

...

Тело секции BODY

@0001 ... @0002 ... @0003 и т.д. - Ссылки на данные.

Тело секции BODY

...

{\v\f53\cf2\f17#END BODY

\par }

 

Осталось продумать только механизм получения данных по ссылкам из пользовательского приложения.

Задача, кстати сказать, весьма не тривиальная для операционной системы типа DOS. Но я воспользуюсь одной из возможностей операционной системы Windous.

Возможность Windows такова: любой процесс или поток может посылать сообщения любому окну, открытому в системе любым приложением. Окно описывается дескриптором окна - уникальным числом внутри одной сессии Windows. Функция API, которой я пользуюсь - SendMessage(HWND hWindow, MSG Message, WPARAM wParam, LPARAM lParam). Где первый параметр это дескриптор окна, второй параметр - сообщение, я определяю свои сообщения для общения окна приложения с процессом построения отчета. Третий и четвертый параметры - это просто числа, типа LONG, конкретный смысл которых зависит от сообщения.

Так, если при копировании секции отчета BODY, программа идентифицирует ссылку на данные @0003, то в окно владельца процесса копирования уйдет сообщение:

 

SendMessage(hwndOw, WM_REP_BODY, 3, BODY_DATA_REQUEST);

 

Обратите внимание на то, что в качестве WPARAM передается номер ссылки.

Это сообщение обязано быть обработано оконной функцией окна hwndOw, которая собственно и поставляет данные при помощи специальной функции DataRequest.

Сообщение, посылаемое функцией SendMessage, асинхронное. Т.е., фактически, напрямую вызывается оконная процедура и пока она не отработает, поставив данные из своего источника, процесс копирования в выходной файл непродолжится.

На самом деле по описанному принципу работает отчет целиком, а не толькоосуществляется поставка данных. Схематично описанный процессможно представить так:

Редактирование бланка отчета.

Требования к входному бланку отчета определяют специфичные правила для его редактирования. Основное , что Вы должныдобиться, это неразрывность тегов разметки секций и меток запросов данных, а также помещение тегов разметки секций в отдельные группы RTF файла. Редактирование производится в Microsoft Word. Версия этого продукта не имеет принципиального значения, поскольку все версии обязаны поддерживать RTF формат. Единственная проблема, которую придется решать при переходе от версии к версии, это проблема шрифтов. Помните, что более поздние версии Word могут содержать возможности, не предусмотренные в более ранних версиях. Эти возможности соответственно находят отражение в новой версии RTF формата, поэтому бланки не должны быть совместимы сверху вниз. Это значит, что, если Вы редактировали бланк в версии 7.0 Microsoft Word, то при переходе на 8.0 Вы можете обойтись только настройкой шрифтов. Если изначально редактировали бланк в 8.0, то есть большая вероятность того,что Word 7.0 просто не воспримет этот файл.

Описание допустимых тегов разметки секций отчета.

Собственно редактирование в Microsoft Word.

Обязательными тегами для бланка отчета являются #BEGIN QREPORT и #END QREPORT, которые соответственно должны находиться в начале и в конце любого отчета. Наличие любых других тегов необязательно.

Вы можете определить секцию с тегами, которых нет в описании. В таком случае этой секции просто не будет в выходном файле. Это удобно, например, для того, что бы сделать примечание, которое видно только во входной форме, но его нет в выходной. Шрифт в такой секции лучше сделать СКРЫТЫМ для того,чтобы получить возможность просмотреть бланк в том виде, в котором он будет в отчете, например:

 

#BEGIN NOTE

Любой текст нотации

#END NOTE

 

Если Вы открыли секцию открывающем тегом (начинаются с ключевого слова #BEGIN), то Вы должны закрыть эту секцию соответствующем закрывающем тегом.

Длинна любого входного бланка ограничена 64 Кб. По этой причине не храните картинки непосредственно в бланке к отчету, делайте ссылки на них, Word позволяет это.

Выделяйте открывающие и закрывающие теги разными цветами и своим шрифтом. Это дает больше вероятности появления тега в своей RTF секции. По сложившейся традиции я использую цвета, уже приведенные мной в предыдущем разделе. Шрифт исключительно для тегов я использую СКРЫТЫЙ. Это удобно тем, что когда я убираю флажок среды Word “Показывать все впункте меню Сервис->Параметры->Вид, я получаю на экране реальный вид моего отчета.

В бланке и в отчете можно делать все, что может делать Word, в том числеи таблицы и графика (смотри абзац выше).

В допустимых тегах нет определения PAGE HEADER и PAGE FOOTER. Заголовок страницы и Footer страницы определяется при помощи верхних и нижних колонтитулов. В колонтитулах ссылки на данные не допустимы. Конечно, их можно поставить, но толку от этого не будет, так как колонтитулы описываются один раз в начале RTF файла (до тега #BEGIN QREPORT) и будут единожды скопированы в начале построения отчета без учета ссылок на данные. (Кстати, в этом проблема получения итогов по страницам.). В колонтитулы я обычно помещаю в верхний, - заголовок таблицы отчета, в нижний - номер страницы. Кроме того, в Word есть возможность различать колонтитулы для первой и последующих страниц, что я обычно и использую.

После редактирования макета отчета у Вас должно получиться приблизительно следующее:

 

#BEGIN QREPORT

#BEGIN REPORT HEADER

ЭТО ЗАМЕЧАТЕЛЬНЫЙ ЗАГОЛОВОК ОТЧЕТА

Параметр для заголовка № 1: @0001

Параметр для заголовка № 2: @0002

Параметр для заголовка № 3: @0003

Параметр для заголовка № 4: @0004

#END REPORT HEADER

#BEGIN GROUP HEADER

Номер отдела:

@0001

#END GROUP HEADER

#BEGIN SUBGROUP HEADER

Номер начальника:

@0001

#END SUBGROUP HEADER

#BEGIN SUBSUBGROUP HEADER

Зарплата:

@0001

#END SUBSUBGROUP HEADER

#BEGIN BODY

@0001

@0002

@0003

@0004

#END BODY

#BEGIN SUBSUBGROUP FOOTER

Конец группы по зарплате:

@0001

#END SUBSUBGROUP FOOTER

#BEGIN SUBGROUP FOOTER

Конец группы по начальникам:

@0001

#END SUBGROUP FOOTER

#BEGIN GROUP FOOTER

Конец группы по отделам.

Средняя зарплата в отделе:

 

@0001

#END GROUP FOOTER

#BEGIN REPORT FOOTER

@0001

@0002

@0003

@0004

#END REPORT FOOTER

#END QREPORT

 

Не вздумайте копировать этот пример черезCLIPBOARD, воспользуйтесь лучше шаблоном, который я положил в корневую директорию - Template.rtf.

Сохраните бланк как файл RTF.

Если Вы вдруг подумали, что это все, то глубоко заблуждаетесь. Помните, что Вы должны обеспечить при редактировании бланка неразрывность теговразметки секций и меток запросов данных, а так же помещение тегов разметки секций в отдельные группы RTF файла. После того, как Вы просто сохраните документ, никакой уверенности в этом нет. Как же достичь уверенности?

Очень просто. Откройте только что отредактированный бланк обычным текстовым редактором, любым, можно даже DOS-овским. Зайдите там в поиск инайдите вначале все символы “#”. Как вы понимаете, с этого символа начинаются теги секций отчета. Проанализируйте найденные теги. Вид уних должен быть такой :

 

{...Control words...#BEGIN BODY

\par }

 

Если Вы встретите что либо подобное следующему:

 

{...Control words...#{...Control words...BEGIN} BODY

\par }

 

То удалите все, что выделено.

Тоже самое Вам надо проделать с метками запросов по данным. Для этого найдите в файле все вхождения символа “@”.

Проанализируйте метки. Нахождение метки в одной RTF секции не обязательно, но неразрывность ее должна быть обеспечена. Опять убираете все, что выделено:

 

{...Control words...{ ...Control words...@00}{...Control words...01}}

 

Убедившись, что все в порядке, сохраните документ.

Для проверки того, что Вы ничего лишнего не удалили, просто загрузите бланк в Word. Если загрузка прошла успешно, ничего не редактируя, выйдете из Word. Если же загрузка не прошла, то на этот случай неплохо иметь резервную копию Вашего бланка ДО редактирования на низком уровне.

Вот на этом действительно все.

Естественно, что после любого редактирования бланка необходимо проверять его содержимое на удовлетворение всех вышеописанных требований методом поиска управляющих символов в теле документа. Может быть это и не совсем удобно, зато надежно.

Имейте в виду тот факт, что если что -либо с отчетом не получается, то на 80% что-то не так с входным бланком.

Универсальная QREP.DLL для Windows систем разработки приложений.

Концептуальное описание DLL библиотеки.

Библиотека написана на языке С. Использовался Borland C++ 5.01. В поставку включены как исходные коды библиотеки, так и файлдля интегрированной среды разработки для сборки проекта qrep.ide.

После компиляции Вы получаете в распоряжение DLL библиотеку qrep.dll и файл qrep.lib. QREP.LIB - результат работы утилиты InpLib, которая по DLL библиотеке делает статический LIB файл ссылок на DLL для подключения к проектам на C, C++ и, в принципе, любым системам программирования, понимающем LIB формат при линковке.

Библиотека включает один модуль на языке С - qrep.c. Заголовочный файл описания соответственно qrep.h. В нем Вы найдете определения всех оконных сообщений, которые поступают в окно пользовательского приложения, и прототипы всех экспортируемых функций библиотеки QREP.DLL в терминах языка С.

Основная функция, которая запускает процесс построения отчета описанакак:

 

unsigned int FAR PASCAL _export RepRTF( HWND hwndOwner,

unsigned char * ucpInBlank,

unsigned char * ucpOutBlank);

 

Как видите, ей передаются имена входного и выходного бланка, а также дескриптор окна, владельца данного процесса. Функция возвращает беззнаковое целое. Возвращаемое значение очень важно (назовем его индексом процесса), так как в последующем общение с процессом построения отчета будет происходить именно по этому индексу. Возвращаемое значение функции RepRTF не единственный источник индекса процесса. Его можно получитьиз обработки первого сообщения, которое приходит в окно, если процесс стартовал удачно, - WM_REP_START_REPORT. В LPARAM этого сообщения приходит индекс процесса.

Индекс процесса необходимо сохранить в Вашем приложении, если Вы запустили несколько процессов, то Вы должны сохранить все индексы всех процессов.

Лучше всего это делать при помощи скрытой переменной класса, к которомупринадлежит окно, если Вы программируете в объектно-ориентированной среде, если нет, то придется делать некоторые массивы индексов.

Функция RepRTF открывает входной бланк, создает файл для выходного потока, делает лексический разбор входного бланка и запускает цикл копирования секций отчета. Цикл копирования продолжается до тех пор, пока при обработке сообщения WM_REP_MOVE, которое является запросом на перемещение источника данных, Вы не указали процессу, что достигнут конец источника данных.

При копировании секций, если программа обнаруживает метку запроса данных, происходит запрос этих данных по номеру метки.

Схематично работу функции RepRTF можно представить следующем образом:

 

SendMessage(hwndOw, WM_REP_START_REPORT, 0, uiIndex);

Открытие файлов;

Резервирование памяти;

Лексический разбор входного бланка;

Копирование системной области и секции REPORT HEADER(с учетом @0001, ...);

ucEof = false;

while ( !ucEof ){

Копирование остальных секций отчета{

if ( встретили @0001, @0002... ){

SendMessage(hwndOw, [WM_REP_GROUP_HEADER, WM_REP_BODY, ...], [1, 2, ...], [GROUP_HEADER_DATA_REQUEST, BODY_DATA_REQUEST, ...]);

};

};

ucEof = false;

SendMessage(hwndOw, WM_REP_MOVE, ++uiJ, MOVE_NEXT);

};

Копирование системной области и секции REPORT FOOTER(с учетом @0001, ...);

Освобождение памяти;

Закрытие файлов;

SendMessage(hwndOw, WM_REP_END_REPORT, 0, _threadid);

 

Рассмотрим более подробно отдельные шаги данной схемы.

Лексический разбор входного бланка.

Схематично размеченный входной бланк в RFT формате выглядит так:

 

Начало FTR файла

. . .

{ #BEGIN QREPORT }

{ #BEGIN REPORT HEADER }

. . .

{ #END REPORT HEADER }

{ #BEGIN GROUP HEADER }

. . .

{ #END GROUP HEADER }

{ #BEGIN BODY }

. . .

{ #END BODY }

{ #BEGIN GROUP FOOTER }

. . .

{ #END GROUP FOOTER }

{ #BEGIN REPORT FOOTER }

. . .

{ #END REPORT FOOTER }

{ #END QREPORT }

. . .

Конец FTR файла

 

Первое, что делает программа лексического анализа, это ищет теги #BEGIN QREPORT и #END QREPORT. Если анализатор не нашел #BEGIN QREPORT, то выдается сообщение в оконную функцию:

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_BEGIN_QREPORT);

 

Соответственно, не найдя #END QREPORT:

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_QREPORT);

 

И программа завершает свою работу, не сформировав никакого выходногофайла.

Если же теги найдены, то происходит поиск открывающей фигурной скобки для #BEGIN QREPORT и закрывающей фигурной скобки для #END QREPORT. Если скобки не найдены, то окно получает сообщения соответственно для #BEGIN QREPORT и #END QREPORT:

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_BEGIN_QREPORT_TAG);

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_QREPORT_TAG);

 

И программа завершает свою работу.

Если все удачно, все теги и скобки стоят на своих местах, программа запоминает длину начала RTF файла (все, что от начала до открывающей скобки тега #BEGIN QREPORT). В этой области находится системная информация, общие описания шрифтов, стилей, описания колонтитулов, и т.д., и т.п. Все это благополучно скачивается в начало выходного файла в дальнейшем. И запоминается концовка (все, что находится, начиная от закрывающей фигурной скобки тега #END QREPORT до конца RTF файла). Обычно там находится некоторое количество закрывающих фигурных скобок, которые благополучно копируются в дальнейшем в выходной отчет.

Далее лексическому анализатору предстоит определить смещения и длинысекций отчета. Процесс этот совершенно одинаковый для любой секции. Он заключается в поиске тега заголовка секции. Если такой имеется, ищется закрывающая фигурная скобка тега заголовка секции. Если скобка не найдена, окно получает сообщение WM_REP_SECTION_ERROR , в LPARAM которого содержится константа, описанная #define идентификатором, заканчивающимся на _TAG, и эта секция в дальнейшем никуда не копируется. После того,как найдена закрывающая фигурная скобка, ищутся тег конца секции отчета и открывающаяфигурная скобка группы RTF с завершающем тегом. Когда все найдено, секция принимается для копирования. В противном случае окно получает сообщение либо о том, что не найдено завершающего тега для секции:

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_GROUP_FOOTER);

 

Либо о том, что не найдена открывающая скобка для этого тега:

 

SendMessage( hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_GROUP_FOOTER_TAG);

 

Для примера я приведу целиком лексический разбор секции BODY из исходного текста:

 

//Ищем тег #BEGIN BODY

SeekText(ucpInBuff, uiSizeIn, "#BEGIN BODY", 10, &fppP);

if ( fppP.ucF ){

//Если нашли, ищем закрывающую скобку группы для тега #BEGIN BODY

while ( true ){

if ( fppP.uiP < uiSizeIn ){

fppP.uiP++;

if ( *(ucpInBuff+fppP.uiP) == 0x7D ){ //}

fppP.uiP++;

break;

};

} else break;

};

if ( fppP.uiP <= uiSizeIn ){

//Если закрывающая фигурная скобка на месте,

//то ищем закрывающий тег #END BODY

qReportBody_p = ucpInBuff + fppP.uiP;

uiI = fppP.uiP;

SeekText(ucpInBuff, uiSizeIn, "#END BODY", 9, &fppP);

if ( fppP.ucF ){

//Если нашли, ищем открывающую скобкугруппы RTF файла

//для тега #END BODY

while ( true ){

if ( fppP.uiP ){

fppP.uiP--;

if ( *(ucpInBuff+fppP.uiP) == 0x7B ){ //{

break;

};

} else break;

};

if ( fppP.uiP != 0 ){

qReportBody_s = fppP.uiP - uiI;

} else {

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_BODY_TAG);

qReportBody_s = 0;

};

} else {

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_END_BODY);

qReportBody_s = 0;

};

} else {

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_SECTION_ERROR,

0,

SECTION_ERROR_NOT_BEGIN_BODY_TAG);

qReportBody_s = 0;

};

} else qReportBody_s = 0;

 

Две переменные отвечают за секцию BODY: qReportBody_p - указатель на начало секции (указатель на первый символ после закрывающей фигурной скобки секции RTF файла с тегом #BEGIN BODY), qReportBody_s - длина в байтах секции BODY. Индикатором, есть ли во входном бланке секция BODY, служит не равенство 0переменной qReportBody_s. Эта переменная равна 0 в 4-х случаях (смотрите программный код):

Если qReportBody_s равна 0, копирования секции BODY не происходит.

Процедура, приведенная для BODY, повторяется для остальных секций отчета, кроме REPORT HEADER и REPORT FOOTER. Подставляйте только разные названия тегов, переменные, определяющие местоположение секции, и константы для общения с окном, владельцем процесса.

Копирование секций отчета.

Перед запуском основного цикла процесса в выходной файл копируется заголовок RTF файла. Это самое простое копирование без какого либо оповещения окна, владельца процесса. Потом (опять же до входа в цикл) копируется REPORT HEADER. После выхода из циклакопируется REPORT FOOTER и концовка RTF файла. Копирование REPORT HEADER и REPORT FOOTERпроисходит без запросов на разрешение это сделать, поэтому единственный способ не включать в отчет секции REPORT HEADER и REPORT FOOTER - не описывать их во входном бланке.

Копирование REPORT HEADER и REPORT FOOTER осуществляется с учетом меток запросов данных (в отличие от заголовка и концовки RTF файла).

Непосредственно в основном цикле процесса происходит копирование секций в следующем порядке:

Перед копированием любой из секций (если онавообще определена во входном бланке), программа делает запрос у окна владельца о необходимости копирования секции. Когда оконная процедура отвечает да, то проводится копирование с поиском меток запросов данных, по которым происходит поставка данных для отчета.

Процедура копирования для всех секций, приведенных в списке, одинакова,за исключением секции BODY, где программа дополнительно посылает окну сообщения информационного плана для нотификации окна о том, что, например, начато копирование BODY и закончено копирование. На эти события удобно вешать, например, наращивание переменных сумм для последующего вывода их в качестве итогов групп, подгрупп, и т.д.

Вы можете воспользоваться этим, например, следующем образом. Допустим, Вы получили какую-то таблицу с какими-то цифрами и Вам необходим отчет с итоговыми цыфрами по каким-то группам этой таблицы. Вы можете определить во входном бланке секцию BODY, внешний вид которой не имеет принципиального значения, т.к. в выходной бланк она не попадет, и определить секцию GROUP FOOTER, в которую будут выводиться итоги. При построении отчета на запрос печати секции BODY, сообщайте, что не надо ее печатать. А вобработчике события начала копирования секции BODY (это событие появляется раньше, чем запрос на печать) суммируете переменную итога. Когда придет время напечатать GROUP FOOTER, выводите туда итог и обнуляете переменную счетчик. Таким образом, Вы получаете отчет из одних итогов. По моему, очень удобно, если еще учесть, что этим можно заниматься параллельно с построением полного отчета по всей таблице (использую свойство поддержки многопоточности в генераторе отчетов).

На последок я приведу типичную процедуру копирования для секции GROUP HEADER:

 

if ( qReportGroupHeader_s ){

//Запрос на печать GROUP HEADER

ppttT[uiIndex].ucGroup = false; //По умолчанию не печатать

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_GROUP_HEADER,

0,

GROUP_HEADER_CHECK);

if ( ppttT[uiIndex].ucGroup ){ //Если результат запроса положителен

for (uiI=0; uiI<qReportGroupHeader_s; uiI++){

//По ходу копирования ищем метку запроса данных

if (*(qReportGroupHeader_p+uiI) == 0x40 /*@*/){

uiDataIndex = RetIndex(qReportGroupHeader_p+uiI+1,

qReportGroupHeader_p+uiI+4);

//Обратите внимание

//!! НОМЕРА МЕТОК НАЧИНАЕТСЯ С 0001 !!

if ( uiDataIndex ){

//Запрос данных по метке

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_GROUP_HEADER,

uiDataIndex,

GROUP_HEADER_DATA_REQUEST);

i = 0;

//Копирование данных из пользовательского

//источника

while ( ppttT[uiIndex].ucpS[i] ){

*(ucpOutBuff+uiOutP++) =

ppttT[uiIndex].ucpS[i++];

if ( uiOutP == FILE_BUFFER ){

WriteFile(fhOutBlank,

(LPCSTR)ucpOutBuff,

FILE_BUFFER,

(LPDWORD)&uiRW,

NULL);

uiOutP = 0;

if ((FILE_BUFFER) != uiRW)

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_WRITE_ERROR,

0,

0);

};

};

uiI += 5;

};

};

//Собственно копирование

*(ucpOutBuff+uiOutP) = *(qReportGroupHeader_p+uiI);

uiOutP++;

if (uiOutP == FILE_BUFFER){

WriteFile(fhOutBlank,

(LPCSTR)ucpOutBuff,

FILE_BUFFER,

(LPDWORD)&uiRW,

NULL);

uiOutP = 0;

if ((FILE_BUFFER) != uiRW)

SendMessage(ppttT[uiIndex].hwndOw,

WM_REP_WRITE_ERROR,

0,

0);

};

};

};

};

 

Перемещение по данным. Конец источника данных.

Отчет порождает сообщение WM_REP_MOVE, когда с его точки зрения окно обязано передвинуть текущий указатель источника данных. При обработки этого сообщения окно обязано подвинуть указатель и вернуть процессу признак, указывающий достигнут ли конец источника данных. В LPARAM сообщения WM_REP_MOVE может прейти либо константа MOVE_TOP, либо MOVE_NEXT. Сообщение WM_REP_MOVE с константой MOVE_TOP порождается перед самым началом основного цикла копирования (перед тем как его запустить).

Сообщение WM_REP_MOVE с константой MOVE_ NEXT порождается в самом конце этого цикла и повторяется столько раз, сколько необходимо передвинуть указатель в Вашем источнике данных.

Вот схема посылки сообщения WM_REP_MOVE:

 

uiJ = 1;

ppttT[uiIndex].ucEof = true;

SendMessage( ppttT[uiIndex].hwndOw,

WM_REP_MOVE,

uiJ,

MOVE_TOP);

while ( !ppttT[uiIndex].ucEof ){

. . .

ppttT[uiIndex].ucEof = true;

SendMessage( ppttT[uiIndex].hwndOw,

WM_REP_MOVE,

++uiJ,

MOVE_NEXT);

};

 

В WPARAM сообщения WM_REP_MOVE содержится требуемый номер записи для источника данных. Этим можно пользоваться, можно и не пользоваться, если Вы двигаетесь по источнику данных при помощи относительного перемещения.

Цикл будет продолжаться до тех пор, пока переменная ppttT[uiIndex].ucEof, где uiIndex - индекс процесса, не станет false. Установку этой переменной делает специальная экспортируемая функция EofRequest(this->lL, b), вызываемая из пользовательского обработчика оконных событий. Первым параметром передается индекс процесса, пришедший из сообщения WM_REP_START_REPORT и сохраненный в protect переменной lL класса того окна, к которому принадлежит процесс. Второй параметр как раз и сигнализирует о достижении конца данных. Эта переменная типа unsigned char и, если она отлична от 0, то конец данных достигнут ипроцесс благополучно выходит из цикла.

Через подобного рода функции ( EofRequest(this->lL, b) ) проходит все общение окна с процессом построения отчета. Полный их список смотрите дальше по описанию.

Осталось только добавить, что, если Вы не определили или определили не правильно процедуру обработки сообщения WM_REP_MOVE, то есть вариант зациклитьсяи надолго.

Вот типичный обработчик сообщения WM_REP_MOVE в оконной процедуре Вашего приложения:

 

//----------------------------------------------------------------------------------------------------

void __fastcall TForm2::MsgReportMove(Messages::TMessage &Msg){

unsigned char b;

if ( Msg.LParam == MOVE_TOP ){

oDB->First();

} else {

if ( Msg.LParam == MOVE_NEXT ) oDB->Next();

};

if ( oDB->Eof ){

b = 1;

} else {

b = 0;

};

EofRequest(this->lL, b); //It call is nassasary

};

 

oDB в данном контексте - это объект типа Ttable, свойство класса TForm2. Это и есть источник данных.

Общие замечания по конструированию оконной функции для отчета.

Основное правило заключается в том, что на большинство сообщений от процесса окно после выполнения соответствующих действий должно дать ответ при помощи одной из функций: DataRequest, EofRequest, BodyRequest, GroupRequest, SubGroupRequest, SubSubGroupRequest.

При этом Вы можете, например, забыть поставить DataRequest при обработке сообщения запроса данных по метке (сообщения с LPARAM, удовлетворяющего шаблону *_DATA_REQUEST). При этом просто ничего не выведется в метку. В программе предусмотрена некоторая защита от “дураков. Но обработать сообщение WM_REP_MOVE и не забыть в конце поставить EofRequest Вы просто обязаны. Все вышеперечисленные функции первым параметром принимают индекс процесса, второйпараметр зависит от функции. По названию функции весьма просто догадаться, что она делает. Так DataRequest вторым параметром возвращает в отчет ссылку на строку пользовательских данных. EofRequest возвращает в отчет признак конца источника данных. BodyRequest возвращает признак копирования секции BODY для текущей записи источника данных и т.д.

Функции работы с группами должны вызываться в процедурах обработки сообщений как для GROUP HEADER-ов так и для GROUP FOOTER-ов для установки признаков копирования их в выходной файл для текущей записи источника данных.

Желательно всегда определять некоторый блок хотя бы минимальный (я понимаю, что лень) для обработки сообщений ошибок. Это весьма помогает установить причину многих ошибок особенно на первых порах, когда нету опыта редактирования входного бланка отчета.

Описание экспортируемых функций.

RepRTF

unsigned int FAR PASCAL _export RepRTF( HWND hwndOwner,

unsigned char * ucpInBlank,

unsigned char * ucpOutBlank);

Функция запускает процесс построения отчета.

< HWND hwndOwner > - Дискриптор окна владельца процесса построения отчета.

< unsigned char * ucpInBlank > - Имя входного бланка.

< unsigned char * ucpOutBlank > - Имя выходного отчета

Возвращаемое значение: Индекс процесса.

DataRequest

void FAR PASCAL _export DataRequest( unsigned int uiIndex,

unsigned char * ucpData);

 

Функция поставляет данные для печати по меткам запросов данных.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char * ucpData > - Указатель на строку, заканчивающуюся 0. Длинной не более 2048 байт вместе с этим 0.

Возвращаемое значение: Нет.

 

EofRequest

void FAR PASCAL _export EofRequest( unsigned int uiIndex,

unsigned char ucEof);

 

Функция указывает конец источника данных.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char ucEof > - Индикатор конца источника данных. Если 1, то достигнут конец данных. Если 0, то нет.

Возвращаемое значение: Нет.

BodyRequest

void FAR PASCAL _export BodyRequest( unsigned int uiIndex,

unsigned char ucBody);

Функция указывает копировать ли в выходной файл секцию BODY.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char ucBody > - Индикатор копирования секции BODY. Если 1, то копировать. Если 0, то нет.

Возвращаемое значение: Нет.

SubSubGroupRequest

void FAR PASCAL _export SubSubGroupRequest( unsigned int uiIndex,

unsigned char ucSubSubGroup);

Функция указывает копировать ли в выходной файл секции SUBSUBGROUP HEADER или SUBSUBGROUP FOOTER.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char ucSubSubGroup > - Индикатор копирования секций SUBSUBGROUP HEADER и SUBSUBGROUP FOOTER. Если 1, то копировать. Если 0, то нет.

Возвращаемое значение: Нет.

SubGroupRequest

void FAR PASCAL _export SubGroupRequest( unsigned int uiIndex,

unsigned char ucSubGroup);

Функция указывает копировать ли в выходной файл секции SUBGROUP HEADER или SUBGROUP FOOTER.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char ucSubGroup > - Индикатор копирования секций SUBGROUP HEADER и SUBGROUP FOOTER. Если 1, то копировать. Если 0, то нет.

Возвращаемое значение: Нет.

GroupRequest

void FAR PASCAL _export GroupRequest( unsigned int uiIndex,

unsigned char ucGroup);

Функция указывает копировать ли в выходной файл секции GROUP HEADER или GROUP FOOTER.

< unsigned int uiIndex > - Индекс процесса.

< unsigned char ucGroup > - Индикатор копирования секций GROUP HEADER и GROUP FOOTER. Если 1, то копировать. Если 0, то нет.

Возвращаемое значение: Нет.

Описание оконных сообщений.

WM_REP_START_REPORT

Возникает при начале работы отчета.

< WPARAM > - Нет.

< LPARAM > - Индекс процесса.

WM_REP_END_REPORT

Возникает в конце работы отчета.

< WPARAM > - Нет.

< LPARAM > - Индекс процесса.

WM_REP_THREAD_ERROR

Возникает при невозможности системой запустить поток, в котором будет строиться отчет.

< WPARAM > - Нет.

< LPARAM > - Нет.

WM_REP_OPEN_ERROR

Возникает при невозможности открыть входной бланк отчета.

< WPARAM > - Нет.

< LPARAM > - Индекс процесса.

WM_REP_CREATE_ERROR

Возникает при невозможности создать выходной файл отчета.

< WPARAM > - Нет.

< LPARAM > - Индекс процесса.

WM_REP_BODY

Возникает при копировании секции BODY в выходной поток. Конкретный смысл сообщения WM_REP_BODY зависит от параметра LPARAM, который может принимать 4 значения:

WM_REP_MEM_ERROR

Возникает при ошибках распределения памяти.

< WPARAM > - Нет.

< LPARAM > - Одна из 5 констант:

WM_REP_READ_ERROR

Возникает при ошибках чтения входного файлас диска.

< WPARAM > - Нет.

< LPARAM > - 2 константы, определяющие при каких условиях файл не был прочитан с диска:

WM_REP_SECTION_ERROR

Возникает при ошибках лексического разборавходного бланка отчета.

< WPARAM > - Нет.

< LPARAM > - 31 константа, которые определяют какая именно лексическая ошибка в бланке. Константы, которые Вы найдете в файле qrep.h, могут заканчиваться как *_TEG. Это значит, что не найдена в RTF файле фигурная скобка для тега секции группы RTF. Если нет завершающего *_TEG в идентификаторе константы, то не найден какой-то тег для секции.

WM_REP_WRITE_ERROR

Возникает при ошибках записи на диск выходного файла.

< WPARAM > - Нет.

< LPARAM > - Нет

WM_REP_MOVE

Возникает при потребности отчета переместить текущую запись источника данных. Обработка данного сообщения должназаканчиваться вызовом функции EofRequest. По умолчанию флаг конца данных всегда установлен в TRUE. Т.е., если Вы забыли вызвать EofRequest или, вообще,не определили реакцию на сообщение WM_REP_MOVE, основной цикл копированиямоментально завершится. При обработке сообщения WM_REP_MOVE будьте внимательны. Легко можно попасть в бесконечный цикл.

< WPARAM > - Номер требуемой записи.

< LPARAM > - Одна из двух констант:

WM_REP_HEADER

Возникает при копировании секции REPORT HEADER в выходной файл при обнаружении программой копировщиком ссылки на данные. Обработчик сообщения должен выдавать данные по метке в конце обработки запроса при помощи функции DataRequest. При отсутствии DataRequest скорее всего будут выведены устаревшие данные.

< WPARAM > - Номер метки запросов на данные.

< LPARAM > - Нет

WM_REP_FOOTER

Возникает при копировании секции REPORT FOOTER в выходнойфайл при обнаружении программой копировщиком ссылки на данные. Обработчик сообщения должен выдавать данные по метке в конце обработки запроса при помощи функции DataRequest. При отсутствии DataRequest скорее всего будут выведены устаревшие данные.

< WPARAM > - Номер метки запросов на данные.

< LPARAM > - Нет

WM_REP_GROUP_HEADER, WM_REP_GROUP_FOOTER, WM_REP_SUBGROUP_HEADER, WM_REP_SUBGROUP_FOOTER, WM_REP_SUBSUBGROUP_HEADER, WM_REP_SUBSUBGROUP_FOOTER

Возникает при копировании секций GROUP [HEADER, FOOTE], SUBGROUP [HEADER, FOOTE], SUBSUBGROUP [HEADER, FOOTE] при запросах на копирование и запросах данных по меткам

< WPARAM > - При запросах на копирование - 0, при запросах данных по меткам - номер метки.

< LPARAM > - Константы, идентификаторы которых определенны в #DEFINE 1) когда заканчиваются *_DATA_REQUEST информируют о запросе данных и обработка таких запросов должна заканчиваться вызовом функции DataRequest; 2) когда заканчиваются на *_CHECK - значит есть запросы на копирование соответствующей секции в выходной поток и эти запросы должны заканчиваться вызовом одной из функций: SubSubGroupRequest, SubGroupRequest, GroupRequest.

Компоненты для Borland Delphi 3 и Borland C++ Builder 3.

После инсталяции компонент и в Delphi, и в C++ Builder в палитре компонент появляется закладка Word Report с одним компонентом WordRpt.Свойсва и события, предоставляемые Вам Object Inspector, в обеих системах абсолютно одинаковы. Разберем подробно эти свойства и события.

Свойства.

InBlank

Содержит имя входного бланка в формате RTF файла.

OutBlank

Содержит имя выходного отчета в формате RTF файла.

События.

OnBodyBegin

Это событие висит на оконном сообщении WM_REP_BODY при LPARAM, установленном в BODY_BEGIN. Вы можете перехватить данное сообщение для наращивания переменных итогов.

В качестве параметра передается объект WordRpt.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure Tform2.WordRpt1BodyBegin(Sender: TObject);

begin

s1 := s1 + oDB.Fields[9].AsFloat;

s2 := s2 + oDB.Fields[9].AsFloat;

end;

void __fastcall TForm2::WordRpt1BodyBegin(TObject *Sender){

s1 += oDB->Fields[9]->AsFloat;

s2 += oDB->Fields[9]->AsFloat;

}

 

OnBodyCheck

Это событие висит на оконном сообщении WM_REP_BODY при LPARAM, установленном в BODY_CHECK. Вы можете перехватить данное сообщение, если хотите указать выводить или не выводить в отчет секцию BODY.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию BODY в выходной файл для текущей позиции источника данных. По умолчанию секция BODY выводится в выходной отчет всегда.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1BodyCheck(Sender: Tobject;

var bBodyCheck: Boolean);

begin

if oDB.Fields[10].AsInteger = 0 then bBodyCheck := false;

end;

void __fastcall TForm2::WordRpt1BodyCheck(TObject *Sender, bool &eof){

if (oDB->Fields[10]->AsInteger = 0 ) {

eof = false;

};

};

 

OnBodyDataRequest

Это событие висит на оконном сообщении WM_REP_BODY при LPARAM, установленном в BODY_DATA_REQUEST. Событие приходит, когда копировщик секции BODYнатыкается на метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure Tform2.WordRpt1BodyDataRequest(Sender: TObject; var iP: Integer;

var sStr: String);

begin

case iP of

1: sStr := Trim(oDB.Fields[0].AsString

2: sStr := Trim(oDB.Fields[2].AsString);

3: sStr := Trim(oDB.Fields[3].AsString);

end;

end;

void __fastcall TForm2::WordRpt1BodyDataRequest(TObject * Sender, int &iP, AnsiString &sStr){

switch ( iP ){

case 1: sStr = Trim(oDB->Fields[0]->AsString)

break;

case 2: sStr = Trim(oDB->Fields[2]->AsString);

break;

case 3: sStr = Trim(oDB->Fields[3]->AsString);

break;};

};

 

OnBodyEnd

Это событие висит на оконном сообщении WM_REP_BODY при LPARAM, установленном в BODY_END. Вы можете перехватить данное сообщение для наращивания переменных итогов или для любых других побочных действий.

В качестве параметра передается объект WordRpt.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure Tform2.WordRpt1BodyEnd(Sender: Tobject);

begin

s1 := s1 + oDB.Fields[9].AsFloat;

s2 := s2 + oDB.Fields[9].AsFloat;

end;

void __fastcall TForm2::WordRpt1BodyEnd(Tobject *Sender){

s1 += oDB->Fields[9]->AsFloat;

s2 += oDB->Fields[9]->AsFloat;

}

 

OnEndReport

Это событие висит на оконном сообщении WM_REP_END_REPORT. Вы можете перехватить данное сообщение для показа получившегося файла отчета в WinWord.

В качестве параметра передается объект WordRpt.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1EndReport(Sender: TObject);

var

asS: String;

begin

asS := 'c:\MSOffice\WinWord\WinWord.exe ' + sOut;

WinExec(PChar(asS), SW_SHOW);

Close;

end;

void __fastcall TForm2::WordRpt1EndReport(Tobject *Sender){

AnsiString asS;

asS = AnsiString("c:\\MSOffice\\WinWord\\WinWord.exe ") + sOut;

WinExec(asS.c_str(), SW_SHOW);

this->Close();

}

 

OnGroupFooterCheck

Это событие висит на оконном сообщении WM_REP_GROUP_FOOTER при LPARAM, установленном в GROUP_FOOTER_CHECK. Вы можете перехватить данное сообщение, если хотите указать выводить или не выводить в отчет секцию GROUP FOOTER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию GROUP FOOTER в выходной файл для текущей позиции источника данных. По умолчанию секция GROUP FOOTER НЕ выводиться в выходной отчет.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1GroupFooterCheck(Sender: TObject;

var GroupFooterCheck: Boolean);

begin

oDB.Next;

if not oDB.Eof then

begin

OldGroup := Trim(oDB.Fields[4].AsString

oDB.Prior;

end

else

begin

OldGroup := 'chfdhhtryrye';

end;

if OldGroup <> Trim(oDB.Fields[4].AsString) then GroupFooterCheck := true;

end;

void __fastcall TForm2::WordRpt1GroupFooterCheck(TObject *Sender, bool &eof){

oDB->Next();

if ( !oDB->Eof ){

OldGroup = Trim(oDB->Fields[4]->AsString);

oDB->Prior();

} else {

OldGroup = AnsiString("chfdhhtryrye");

};

if ( OldGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnGroupFooterDataRequest

Это событие висит на оконном сообщении WM_REP_GROUP_FOOTER при LPARAM, установленном в GROUP_FOOTER_DATA_REQUEST. Событие приходит, когда копировщик секции GROUP FOOTER натыкается на метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1GroupFooterDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

if iP = 1 then sStr := Trim(oDB.Fields[4].AsString);

if iP = 2 then

begin

sStr := Trim(FloatToStr(s1));

s1 := 0;

end;

end;

void __fastcall TForm2::WordRpt1GroupFooterDataRequest(TObject *Sender, int &iP, AnsiString &sStr){

if ( iP == 1 ) sStr = Trim(oDB->Fields[4]->AsString);

if ( iP == 2 ){

sStr = Trim(FloatToStr(s1));

s1 = 0;

};

}

 

OnGroupHeaderCheck

Это событие висит на оконном сообщении WM_REP_GROUP_HEADER при LPARAM, установленном в GROUP_HEADER_CHECK. Вы можете перехватить данное сообщение, если хотите указать выводить или не выводить в отчет секцию GROUP HEADER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию GROUP HEADER в выходной файл для текущей позиции источника данных. По умолчанию секция GROUP HEADER НЕ выводится в выходной отчет.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1GroupHeaderCheck(Sender: TObject;

var GroupHeaderCheck: Boolean);

begin

oDB.Prior;

if not oDB.Bof then

begin

OldGroup := Trim(oDB.Fields[4].AsString); //#STATE

oDB.Next;

end

else

begin

OldGroup := 'hhhhhhhhhhhhhhhhhhhhhhh';

end;

if OldGroup <> Trim(oDB.Fields[4].AsString) then GroupHeaderCheck := true;

end;

void __fastcall TForm2::WordRpt1GroupHeaderCheck(TObject *Sender, bool &eof){

oDB->Prior();

if ( !oDB->Bof ){

OldGroup = Trim(oDB->Fields[4]->AsString); //#STATE

oDB->Next();

} else {

OldGroup = AnsiString("hhhhhhhhhhhhhhhhhhhhhhh");

};

if ( OldGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnGroupHeaderDataRequest

Это событие висит на оконном сообщении WM_REP_GROUP_HEADER при LPARAM, установленном в GROUP_HEADER_DATA_REQUEST. Событие приходит, когда копировщик секции GROUP HEADER натыкается на метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1GroupHeaderDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

sStr := Trim(oDB.Fields[4].AsString); //#STATE

end;

void __fastcall TForm2::WordRpt1GroupHeaderDataRequest(Tobject *Sender, int &iP, AnsiString &sStr){

sStr = Trim(oDB->Fields[4]->AsString); //#STATE

}

 

OnMoveNext

Это событие висит на оконном сообщении WM_REP_MOVE при LPARAM, установленном в MOVE_NEXT. Вы можете перехватить данное сообщение для перемещения источника данных на следующую запись.

Вы можете повесить на это событие некоторые другие действия, такие как, например, передвижение Progress Bar для индикации процесса построения отчета.

В качестве параметра передается объект WordRpt и ссылка на логическуювеличину, которую необходимо установить в false или true в зависимости от достижения источником данных конца. По умолчанию считается, что источник данных уже достиг конца.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1MoveNext(Sender: TObject; var bEof: Boolean);

begin

oDB.Next;

if ( ( oDB.Eof ) or ( _End ) )

then

bEof := true

else

bEof := false;

PBar.Position := Pbar.Position + 1;

FT_REC.Caption := IntToStr(PBar.Position);

FT_PROC.Caption := FloatToStr((PBar.Position*100)/oDB.RecordCount);

end;

void __fastcall TForm2::WordRpt1MoveNext(TObject *Sender, bool &eof){

oDB->Next();

if ( ( oDB->Eof ) || ( _End ) ){

eof = true;

} else {

eof = false;

};

PBar->Position = PBar->Position + 1;

FT_REC->Caption = IntToStr(PBar->Position);

FT_PROC->Caption = FloatToStr((PBar->Position*100)/oDB->RecordCount);

}

 

OnMoveTop

Это событие висит на оконном сообщении WM_REP_MOVE при LPARAM, установленном в MOVE_TOP. Вы можете перехватить данное сообщение для перемещения источника данных на первую логическую запись.

Вы можете повесить на это событие некоторые другие действия, такие как, например, инициализацию Progress Bar для индикации процесса построения отчета.

В качестве параметра передается объект WordRpt и ссылка на логическуювеличину, которую необходимо установить в false или true в зависимости от достижения источником данных конца. По умолчанию считается, что источник данных уже достиг конца.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1MoveTop(Sender: TObject; var bEof: Boolean);

begin

oDB.First;

if ( ( oDB.Eof ) or ( _End ) )

then

bEof := true

else

bEof := false;

PBar.Position := 1;

FT_REC.Caption := '';

FT_PROC.Caption := '';

end;

void __fastcall TForm2::WordRpt1MoveTop(TObject *Sender, bool &eof){

oDB->First();

if ( ( oDB->Eof ) || ( _End ) ){

eof = true;

} else {

eof = false;

};

PBar->Position = 1;

FT_REC->Caption = AnsiString("");

FT_PROC->Caption = AnsiString("");

}

OnReportError

Возникает при любой ошибке при формировании отчета.

В качестве параметра передается объект WordRpt, ссылка на объект типа Tmessage, из которого можно выбрать полностью информацию об ошибке, и строка, словесно описывающая ошибку.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1ReportError(Sender: TObject; var Msg: Tmessage;

var sStr: String);

begin

ShowMessage(sStr);

end;

void __fastcall TForm2::WordRpt1ReportError(TObject *Sender, TMessage &Msg, AnsiString &sStr){

ShowMessage(sStr);

};

OnReportFooter

Это событие висит на оконном сообщении WM_REP_FOOTER. Событиеприходит, когда копировщик при копировании секции REPORT FOOTER находит метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1ReportFooter(Sender: Tobject; var iP: Integer;

var sStr: String);

begin

sStr := aParam[iP];

end;

void __fastcall TForm2::WordRpt1ReportFooter(TObject *Sender, int &iP, AnsiString &sStr){

sStr = aParam[iP];

}

 

OnReportHeader

Это событие висит на оконном сообщении WM_REP_HEADER. Событиеприходит, когда копировщик при копировании секции REPORT HEADER находит метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1ReportHeader(Sender: Tobject; var iP: Integer;

var sStr: String);

begin

sStr := aParam[iP];

end;

void __fastcall TForm2::WordRpt1ReportHeader(TObject *Sender, int &iP, AnsiString &sStr){

sStr = aParam[iP];

}

 

OnStartReport

Это событие висит на оконном сообщении WM_REP_START_REPORT. Вы можете перехватить данное сообщение для инициализации своих переменных.

В качестве параметра передается объект WordRpt.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1StartReport(Sender: TObject);

begin

s1 := 0;

s2 := 0;

end;

void __fastcall TForm2::WordRpt1StartReport(TObject *Sender){

s1 = 0;

s2 = 0;

}

OnSubGroupFooterCheck

Это событие висит на оконном сообщении WM_REP_SUBGROUP_FOOTER при LPARAM, установленном в SUBGROUP_FOOTER_CHECK. Вы можете перехватить данное сообщение, если хотите указать выводить или не выводить в отчет секцию SUBGROUP FOOTER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию SUBGROUP FOOTER в выходной файл для текущей позиции источника данных. По умолчанию секция SUBGROUP FOOTER НЕ выводится в выходной отчет.

 

Пример:

 

Для DelphiДля C++ Builder

procedure TForm2.WordRpt1SubGroupFooterCheck(Sender: TObject;

var SubGroupFooterCheck: Boolean);

begin

oDB.Next;

if not oDB.Eof then

begin

OldSubGroup := Trim(oDB.Fields[4].AsString

oDB.Prior;

end

else

begin

OldSubGroup := 'chfdhhtryrye';

end;

if SubOldGroup <> Trim(oDB.Fields[4].AsString) then SubGroupFooterCheck := true;

end;

void __fastcall TForm2::WordRpt1SubGroupFooterCheck(TObject *Sender, bool &eof){

oDB->Next();

if ( !oDB->Eof ){

OldSubGroup = Trim(oDB->Fields[4]->AsString);

oDB->Prior();

} else {

OldSubGroup = AnsiString("chfdhhtryrye");

};

if ( OldSubGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnSubGroupFooterDataRequest

Это событие висит на оконном сообщении WM_REP_SUBGROUP_FOOTER при LPARAM, установленном в SUBGROUP_FOOTER_DATA_REQUEST. Событие приходит, когда копировщиксекции SUBGROUP FOOTER находит метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubGroupFooterDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

if iP = 1 then sStr := Trim(oDB.Fields[4].AsString);

if iP = 2 then

begin

sStr := Trim(FloatToStr(s1));

s1 := 0;

end;

end;

void __fastcall TForm2::WordRpt1SubGroupFooterDataRequest(TObject *Sender, int &iP, AnsiString &sStr){

if ( iP == 1 ) sStr = Trim(oDB->Fields[4]->AsString);

if ( iP == 2 ){

sStr = Trim(FloatToStr(s1));

s1 = 0;

};

}

 

OnSubGroupHeaderCheck

Это событие висит на оконном сообщении WM_REP_SUBGROUP_HEADER при LPARAM, установленном в SUBGROUP_HEADER_CHECK. Вы можете перехватить данное сообщение, если хотите указать выводить или не выводить в отчет секцию SUBGROUP HEADER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию SUBGROUP HEADER в выходной файл для текущей позиции источника данных. По умолчанию секция SUBGROUP HEADER НЕ выводится в выходной отчет.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubGroupHeaderCheck(Sender: TObject;

var SubGroupHeaderCheck: Boolean);

begin

oDB.Next;

if not oDB.Eof then

begin

OldSubGroup := Trim(oDB.Fields[4].AsString

oDB.Prior;

end

else

begin

OldSubGroup := 'chfdhhtryrye';

end;

if SubOldGroup <> Trim(oDB.Fields[4].AsString) then SubGroupHeaderCheck := true;

end;

void __fastcall TForm2::WordRpt1SubGroupHeaderCheck(TObject *Sender, bool &eof){

oDB->Next();

if ( !oDB->Eof ){

OldSubGroup = Trim(oDB->Fields[4]->AsString);

oDB->Prior();

} else {

OldSubGroup = AnsiString("chfdhhtryrye");

};

if ( OldSubGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnSubGroupHeaderDataRequest

Это событие висит на оконном сообщении WM_REP_SUBGROUP_HEADER при LPARAM, установленном в SUBGROUP_HEADER_DATA_REQUEST. Событие приходит, когда копировщик секции SUBGROUP HEADER находит метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubGroupHeaderDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

if iP = 1 then sStr := Trim(oDB.Fields[4].AsString);

if iP = 2 then

begin

sStr := Trim(FloatToStr(s1));

s1 := 0;

end;

end;

void __fastcall TForm2::WordRpt1SubGroupHeaderDataRequest(TObject *Sender, int &iP, AnsiString &sStr){

if ( iP == 1 ) sStr = Trim(oDB->Fields[4]->AsString);

if ( iP == 2 ){

sStr = Trim(FloatToStr(s1));

s1 = 0;

};

}

 

OnSubSubGroupFooterCheck

Это событие висит на оконном сообщении WM_REP_SUBSUBGROUP_FOOTER при LPARAM, установленном в SUBSUBGROUP_FOOTER_CHECK. Вы можете перехватить данноесообщение, если хотите указать выводить или не выводить в отчет секцию SUBSUBGROUP FOOTER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию SUBSUBGROUP FOOTER в выходной файл для текущей позиции источника данных. По умолчанию секция SUBSUBGROUP FOOTER НЕ выводиться в выходной отчет.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubGroupFooterCheck(Sender: TObject;

var SubSubGroupFooterCheck: Boolean);

begin

oDB.Next;

if not oDB.Eof then

begin

OldSubGroup := Trim(oDB.Fields[4].AsString

oDB.Prior;

end

else

begin

OldSubGroup := 'chfdhhtryrye';

end;

if SubOldGroup <> Trim(oDB.Fields[4].AsString) then SubSubGroupFooterCheck := true;

end;

void __fastcall TForm2::WordRpt1SubSubGroupFooterCheck(TObject *Sender, bool &eof){

oDB->Next();

if ( !oDB->Eof ){

OldSubGroup = Trim(oDB->Fields[4]->AsString);

oDB->Prior();

} else {

OldSubGroup = AnsiString("chfdhhtryrye");

};

if ( OldSubGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnSubSubGroupFooterDataRequest

Это событие висит на оконном сообщении WM_REP_SUBSUBGROUP_FOOTER при LPARAM, установленном в SUBSUBGROUP_FOOTER_DATA_REQUEST. Событие приходит, когда копировщик секции SUBSUBGROUP FOOTER находит метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubSubGroupFooterDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

if iP = 1 then sStr := Trim(oDB.Fields[4].AsString);

if iP = 2 then

begin

sStr := Trim(FloatToStr(s1));

s1 := 0;

end;

end;

void __fastcall TForm2::WordRpt1SubSubGroupFooterDataRequest(TObject *Sender, int &iP, AnsiString &sStr){

if ( iP == 1 ) sStr = Trim(oDB->Fields[4]->AsString);

if ( iP == 2 ){

sStr = Trim(FloatToStr(s1));

s1 = 0;

};

}

 

OnSubSubGroupHeaderCheck

Это событие висит на оконном сообщении WM_REP_SUBSUBGROUP_HEADER при LPARAM, установленном в SUBSUBGROUP_HEADER_CHECK. Вы можете перехватить данноесообщение, если хотите указать выводить или не выводить в отчет секцию SUBSUBGROUP HEADER.

В качестве параметра передается объект WordRpt и логическая переменная по ссылке, которую Вы и устанавливаете в зависимости от желания вывести секцию SUBSUBGROUP HEADER в выходной файл для текущей позиции источника данных. По умолчанию секция SUBSUBGROUP HEADER НЕ выводится в выходной отчет.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubSubGroupHeaderCheck(Sender: Tobject;

var SubSubGroupHeaderCheck: Boolean);

begin

oDB.Next;

if not oDB.Eof then

begin

OldSubGroup := Trim(oDB.Fields[4].AsString

oDB.Prior;

end

else

begin

OldSubGroup := 'chfdhhtryrye';

end;

if SubOldGroup <> Trim(oDB.Fields[4].AsString) then SubSubGroupHeaderCheck := true;

end;

void __fastcall TForm2::WordRpt1SubSubGroupHeaderCheck(TObject *Sender, bool &eof){

oDB->Next();

if ( !oDB->Eof ){

OldSubGroup = Trim(oDB->Fields[4]->AsString);

oDB->Prior();

} else {

OldSubGroup = AnsiString("chfdhhtryrye");

};

if ( OldSubGroup != Trim(oDB->Fields[4]->AsString) ) eof = true;

}

OnSubSubGroupHeaderDataRequest

Это событие висит на оконном сообщении WM_REP_SUBSUBGROUP_HEADER при LPARAM, установленном в SUBSUBGROUP_HEADER_DATA_REQUEST. Событие приходит, когда копировщик секции SUBSUBGROUP HEADER натыкается на метку запроса данных. Вы переопределяете событие, когда заполняете отчет из источника данных.

В качестве параметра передается объект WordRpt, целая величина (по ссылке), показывающая номер метки запроса данных, и строка (тоже по ссылке), которую Вы заполняете в зависимости от величины метки из источника данных. Это строка копируется на место метки.

 

Пример:

 

Для Delphi

Для C++ Builder

procedure TForm2.WordRpt1SubSubGroupHeaderDataRequest(Sender: TObject;

var iP: Integer; var sStr: String);

begin

if iP = 1 then sStr := Trim(oDB.Fields[4].AsString);

if iP = 2 then

begin

sStr := Trim(FloatToStr(s1));

s1 := 0;

end;

end;

void __fastcall TForm2::WordRpt1SubSubGroupHeaderDataRequest(TObject *Sender, int &iP, AnsiString &sStr){

if ( iP == 1 ) sStr = Trim(oDB->Fields[4]->AsString);

if ( iP == 2 ){

sStr = Trim(FloatToStr(s1));

s1 = 0;

};

}

 

Сайт создан в системе uCoz