Using the NCReport API
This chapter shows how to use the report API from a C++ (Qt) application: how to create an NCReport object and how to run reports with it. As described earlier, the NCReport system consists of two parts: the report renderer library and the report designer GUI application. The report engine can, of course, be used without the Designer.
To call NCReport from your application, you first have to integrate NCReport into your project. There are several ways to do so:
-
Add the entire source code to your project and build it together with your application.
-
Use the NCReport engine as a shared library. To use the NCReport library like any other library in your project, you need to declare it in your project file. For more information, see the "Declaring Other Libraries" chapter of the Qt qmake manual.
-
Link the NCReport library statically into your project. For more information, see the "Declaring Other Libraries" chapter of the Qt qmake manual.
Project file settings
Add at least the following lines to your .pro file. The library names follow the naming used by the NCReport build
(NCReport for release and NCReportd for debug builds; on Windows, the major version number 2 is appended). If NCReport
was built with barcode support, the Zint barcode library must also be linked:
NCREPORT_LIBPATH = /home/ncreport/lib
CONFIG(release, debug|release) {
win32|win64: LIBS += -L$$NCREPORT_LIBPATH -lNCReport2 -lzint2
unix:!macx: LIBS += -L$$NCREPORT_LIBPATH -lNCReport -lzint
else:macx: LIBS += -framework NCReport
}
CONFIG(debug, debug|release) {
win32|win64: LIBS += -L$$NCREPORT_LIBPATH -lNCReportd2 -lzintd2
unix:!macx: LIBS += -L$$NCREPORT_LIBPATH -lNCReportd -lzintd
else:macx: LIBS += -framework NCReportd
}
INCLUDEPATH += /home/ncreport/include
...
Initializing the NCReport class
This section covers the fundamental steps that most users take when creating and using the NCReport class. The steps are presented in the suggested order.
Include directives
To include the definitions of the module’s classes, use the following directives:
#include "ncreport.h"
#include "ncreportoutput.h"
#include "ncreportpreviewoutput.h"
#include "ncreportpreviewwindow.h"
Creating the NCReport class
Create the report object just as you would any other QObject-based class:
NCReport *report = new NCReport();
If the object was created earlier and is passed as a parameter to the
method in which you use it, initialize the report by calling the reset() method.
If you create a new object in the current scope, you don’t need to call reset().
report->reset();
reset() also has an overloaded version that lets you choose exactly what is cleared: renderers, parameters, user functions,
string lists, batch reports, and (optionally) custom data sources.
Connecting to an SQL database
An SQL connection is required only when your data source uses an internal database connection. An internal connection is an already existing database connection that is established before running the report. Reports can also use an external connection (one defined in the report itself). Other data source types don’t require a database connection.
This example code shows a typical SQL database connection with error handling:
QSqlDatabase defaultDB = QSqlDatabase::addDatabase("QMYSQL", "myconn" );
if ( !defaultDB.isValid() ) {
QMessageBox::warning( 0, "Report error", QObject::tr("Could not load database driver.") );
delete report;
return;
}
defaultDB.setHostName( "host" );
defaultDB.setDatabaseName( "database" );
defaultDB.setUserName( "user" );
defaultDB.setPassword( "password" );
if ( !defaultDB.open() ) {
QMessageBox::warning( 0, "Report error", QObject::tr("Cannot open database:")+defaultDB.lastError().databaseText() );
return;
}
Setting the report’s source
The report source is the way NCReport obtains the XML report
definition; in other words, it is the source of the report definition data.
In most cases report definitions are opened from a file,
but the NCReportSource::ReportSourceType enumeration also provides the Database, Http, Ftp, Custom, and String source types.
A report definition can also be passed directly as an XML string with the setReportDefXml() function.
In this example, we use a file as the report source:
report->setReportFile( fileName );
This is equivalent to the following code:
report->setReportSourceType( NCReportSource::File );
report->reportSource()->setFileName( fileName );
Adding parameters
If your report uses parameters, you have to add the parameter value(s) to the NCReport object before running the report. To create and add a parameter, call:
report->addParameter( "id", "value" );
where the ID is a QString and the value is a QVariant.
Running the report
Now we are ready to run the report and catch the error message if an error occurs. There are two ways to start the report engine.
Running the report in one step
This is the simplest mode, but it offers fewer
configuration options. The runReportTo…() functions create the appropriate output object internally and run the report.
They do not return a value; check the result with hasError() and lastErrorMsg() afterwards.
// run report to printer
report->runReportToPrinter(1, true, parent);
// run report to PDF file
report->runReportToPdf( "file.pdf" );
// or
report->runReportToPdfWriter( "file.pdf" ); (1)
// run report to SVG files
report->runReportToSVG( "file.svg" );
// run report to preview output
report->runReportToPreview();
// run report to QPrintPreview dialog
report->runReportToQtPreview( parent );
bool error = report->hasError();
QString err = report->lastErrorMsg();
| 1 | The recommended way of creating PDF files. |
Further one-step functions are available for other output formats, for example runReportToImage(), runReportToHtml(),
runReportToExcelHtml(), runReportToPostScript(), and runReportToText().
When runReportToPreview() is used, the preview window is not shown automatically: you also have to create and show an NCReportPreviewWindow
(see "Previewing the report" below). Alternatively, runReportToShowPreview( parent ) runs the report and shows the preview window in one step.
Running the report in customized mode
This mode allows more flexible configuration. First the output object has to be initialized; after that, the report is ready to run.
Initializing the report’s output
The next step is to create and specify the report’s output. As
the rendering target, NCReport uses a class derived from the
NCReportOutput base class. There are predefined classes for the
most commonly used outputs:
-
NCReportPrinterOutput- printer -
NCReportPreviewOutput- NCReport’s own print preview -
NCReportQtPreviewOutput- Qt print preview dialog -
NCReportPdfOutput- PDF file through QPrinter -
NCReportPdfWriterOutput- PDF file through QPdfWriter -
NCReportSvgOutput- SVG file(s) -
NCReportImageOutput- image file(s) -
NCReportHtmlOutputandNCReportExcelHtmlOutput- HTML file and Excel-compatible HTML file -
NCReportTextOutput- template-based text output
For a detailed description of the output types and their options, see Report Outputs.
To define the desired output, use code similar to this:
NCReportOutput *output=0;
if ( rbPreview->isChecked() ) {
output = new NCReportPreviewOutput();
output->setAutoDelete( false );
report->setOutput( output );
} else if ( rbPrinter->isChecked() ) {
output = new NCReportPrinterOutput();
output->setCopies(1);
output->setShowPrintDialog( true );
report->setOutput( output );
} else if ( rbPdf->isChecked() ) {
QString fileName = QFileDialog::getSaveFileName(this, tr("Save PDF File"), "report.pdf", tr("Pdf files (*.pdf)"));
if ( fileName.isEmpty() ) {
delete report;
return;
} else {
output = new NCReportPdfOutput();
output->setFileName( fileName );
report->setOutput( output );
}
}
Running the report
Now we are ready to run the report and catch the error message if an error occurs:
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
report->runReport();
bool error = report->hasError();
QString err = report->lastErrorMsg();
QApplication::restoreOverrideCursor();
Previewing the report
If NCReportPreviewOutput is specified as the report’s output, the preview window is
not shown automatically. After the report engine has finished successfully, we need to create an
NCReportPreviewWindow object for previewing. The following code shows how to do this. It is recommended to
check for errors first, before showing the preview window:
if ( error ) {
QMessageBox::information( 0, "Report error", err );
} else {
if ( rbPreview->isChecked() ) {
//-----------------------------
// PRINT PREVIEW
//-----------------------------
NCReportPreviewWindow *pv = new NCReportPreviewWindow();
pv->setReport( report );
pv->setOutput( (NCReportPreviewOutput*)output );
pv->setWindowModality(Qt::ApplicationModal );
pv->setAttribute( Qt::WA_DeleteOnClose );
pv->exec();
}
}
|
Do not delete the output object after it has been added to the |
|
For the best performance and output quality, do not delete the NCReport object until the preview window is closed, and
add the report object to the preview window with the |
pv->setReport( report );
Once this is done, printing from the preview produces the original printout quality, since it runs the report again instead of printing the lower-quality preview pages.
|
Since version 2.8.4, the preview window can be shown in dialog mode, just like a QDialog. The |
Deleting the report object
After running the report, you may want to delete the report
object. Note that the preview window requires the NCReport object
to exist. If NCReportPreviewWindow::exec() is used, the code following it
is not executed until the preview window is closed.
delete report;
Using other data sources
NCReport allows you to use non-SQL data sources such as QString-based text, a QStringList, an item model, or a custom-defined data source. Depending on the data source type, data may come from a file, a report parameter, or another source. For a general description of data source types, location types, and opening roles, see Data Sources.
QString-based text data source
NCReport allows you to use QString text as the simplest data source. Each text row represents one data record (rows are separated by the line feed character), and the data columns are separated by a specified delimiter character. To identify columns in fields, use the column number as a reference in the report, as follows:
0 for the 1st column,
1 for the 2nd column, etc.,
or
col0 for the 1st column,
col1 for the 2nd column, etc.
To use text data sources, add a Text data source to your report in the Designer. You have the following options:
-
Store static text in the report definition. To do so, select the Static location type in the Designer and add the static text to the edit box, using the specified delimiter character.
-
Use an existing text file. To do so, select the File location type in the Designer and specify the name of the text file you want to use. You must also specify the delimiter character.
-
Pass the text to NCReport as a report parameter. To do so, first add the text as a parameter to NCReport with the
addParameter()function. Then select the Parameter location type in the Designer and specify the ID of the parameter you added. You must also specify the delimiter character.
Example of adding QString text to NCReport as a parameter, using the Tab character as the column delimiter:
NCReport report;
QString data;
data += "1 \tChai \t16.0000\t1\t1540\t0\n";
data += "2 \tChang \t17.0000\t1\t 874\t0\n";
data += "3 \tAniseed Syrup \t 9.0000\t1\t1687\t0\n";
data += "4 \tChef Anton's Cajun Seasoning \t20.0000\t1\t1230\t0\n";
data += "5 \tChef Anton's Gumbo Mixj \t19.0000\t2\t1900\t0\n";
data += "6 \tGrandma's Boysenberry Spread \t21.0000\t2\t 520\t0\n";
data += "7 \tUncle Bob's Organic Dried Pears \t25.0000\t3\t 540\t0\n";
data += "8 \tNorthwoods Cranberry Sauce \t34.0000\t3\t 120\t0\n";
data += "9 \tMishi Kobe Niku \t72.0000\t3\t 130\t0\n";
data += "10 \tIkura \t26.0000\t3\t2247\t0\n";
data += "11 \tQueso Cabrales \t19.0000\t4\t 741\t0\n";
data += "12 \tQueso Manchego La Pastora \t32.0000\t4\t 512\t0\n";
data += "13 \tKonbu \t 5.0000\t4\t1470\t0\n";
data += "14 \tTofu \t21.0000\t4\t 978\t0\n";
data += "15 \tGenen Shouyu \t14.0000\t4\t1005\t0\n";
report.addParameter( "data1", data );
QStringList data source
NCReport also allows you to use a QStringList as a data source. Each list item represents one data record, and the data columns are separated by a specified delimiter character.
To identify columns in fields, use the column number as a reference in the report, as follows:
0 for the 1st column,
1 for the 2nd column, etc.,
or
col0 for the 1st column,
col1 for the 2nd column, etc.
To use a QStringList data source, add a StringList data source
to your report in the Designer. You can only use the Static
location type, since a QStringList can be passed to NCReport in only one way: with the addStringList() function. You have to specify an ID for the list, which identifies it.
Example of using a QStringList as a data source:
NCReport report;
QStringList list;
list << "24|Renate Moulding|Desert Hot Springs,CA|1|2008-01-01";
list << "78|Alfred Muller|Miami Beach, FL|1|2008-01-03";
list << "140|Angela Merkel|Munchen, Germany|1|2008-01-07";
list << "139|Bob Larson|Dallas, TX|0|2008-01-20";
report.addStringList( list, "sl0" );
Item Model data source
The Model/View architecture is a very useful feature of
Qt. NCReport allows you to use a data source
based on QAbstractItemModel. First you have to create your item model.
Each model row represents
one data record. To identify columns in fields, use the column
number as a reference in the report, as follows:
0 for the 1st column,
1 for the 2nd column, etc.,
or
col0 for the 1st column,
col1 for the 2nd column, etc.
To use an Item Model data source, add an Item Model data source to
your report in the Designer. You can only use the Static
location type; other location types are undefined. In your code, add the item
model to NCReport with the addItemModel(…) function. You have to specify an ID that identifies the model,
and the item model data source in the Designer must have the same ID.
Example of using an item model as a data source:
QStandardItemModel *model = new QStandardItemModel( 2, 4 );
QStandardItem *item =0;
// -----------------------------------
item = new QStandardItem();
item->setData( 1, Qt::EditRole );
model->setItem( 0, 0, item);
item = new QStandardItem();
item->setData( "Chai", Qt::EditRole );
model->setItem( 0, 1, item);
item = new QStandardItem();
item->setData( 16.0, Qt::EditRole );
model->setItem( 0, 2, item);
item = new QStandardItem();
item->setData( 1540.0, Qt::EditRole );
model->setItem( 0, 3, item);
// -----------------------------------
item = new QStandardItem();
item->setData( 2, Qt::EditRole );
model->setItem( 1, 0, item);
item = new QStandardItem();
item->setData( "Chef Anton's Cajun Seasoning", Qt::EditRole );
model->setItem( 1, 1, item);
item = new QStandardItem();
item->setData( 20.0, Qt::EditRole );
model->setItem( 1, 2, item);
item = new QStandardItem();
item->setData( 1230.0, Qt::EditRole );
model->setItem( 1, 3, item);
report.addItemModel( model, "model1" );
Custom data sources
NCReport allows you to create your own data source by subclassing the NCReportDataSource abstract class. This way you can
use anything as data for NCReport. You only have to implement the
pure virtual methods of the class. To add your data source object
to NCReport, use the addCustomDataSource() function. The following example (based on the TestDataSource class of the NCReport runner
application) demonstrates how to define and use a custom data source class:
Declaration
#include "ncreportdatasource.h"
#include <QDate>
struct TestData {
int id;
QString name;
QString address;
bool valid;
QDate date;
};
class TestDataSource : public NCReportDataSource
{
Q_OBJECT
public:
TestDataSource( QObject *parent=0 );
~TestDataSource();
void addData( const TestData& data );
bool open();
bool close();
bool first();
bool last();
bool next();
bool previous();
bool hasNext();
bool seek( int index );
int size() const;
bool isValid() const;
QVariant value( const QString& column, bool* ok=0, int nQTDataRole=-1 ) const;
QVariant value( int column, bool* ok=0, int nQTDataRole=-1 ) const;
bool read( NCReportXMLReader* );
bool write( NCReportXMLWriter* );
int columnIndexByName( const QString& columnname ) const;
int columnCount() const;
QString columnName( int column ) const;
private:
QList<TestData> list;
};
Implementation
TestDataSource::TestDataSource(QObject * parent) : NCReportDataSource( parent )
{
setDataSourceType(Custom);
setLocationType(Static);
recno() =0;
}
TestDataSource::~TestDataSource()
{
}
bool TestDataSource::open()
{
if ( list.isEmpty() ) {
error()->setError( tr("No data in TestDataSource data source") );
return false;
}
recno() =0;
setOpened(true);
return true;
}
bool TestDataSource::close()
{
recno() =0;
setOpened(false);
return true;
}
bool TestDataSource::next()
{
recno()++;
if ( recno() >= list.count() ) {
recno()--;
flagEnd() = true;
return false;
}
flagBegin() = false;
return true;
}
bool TestDataSource::previous()
{
if ( recno() <= 0 ) {
recno() = 0;
flagBegin() = true;
return false;
}
recno()--;
return true;
}
bool TestDataSource::first()
{
recno()=0;
return true;
}
bool TestDataSource::last()
{
recno() = list.count()-1;
return true;
}
bool TestDataSource::hasNext()
{
return ( recno() < list.count()-1 );
}
bool TestDataSource::seek( int index )
{
if ( index == -1 )
return true; (1)
if ( index < list.count() ) {
recno() = index;
return true;
}
recno() = list.count()-1;
return false;
}
int TestDataSource::size() const
{
return list.count();
}
bool TestDataSource::isValid() const
{
return true;
}
QVariant TestDataSource::value(const QString & column, bool* ok, int nQTDataRole ) const
{
int col = columnIndexByName(column);
if ( col < 0 ) {
if (ok)
*ok = false;
return QVariant();
}
return value( col, ok, nQTDataRole );
}
QVariant TestDataSource::value( int column, bool*, int ) const
{
QVariant v;
switch (column) {
case 0: v = list.at(recno()).id; break;
case 1: v = list.at(recno()).name; break;
case 2: v = list.at(recno()).address; break;
case 3: v = list.at(recno()).valid; break;
case 4: v = list.at(recno()).date; break;
}
return v;
}
int TestDataSource::columnIndexByName(const QString &columnname) const
{
for ( int i=0; i<columnCount(); i++ ) {
if ( columnname == columnName(i) )
return i;
}
return -1;
}
int TestDataSource::columnCount() const
{
return 5;
}
QString TestDataSource::columnName(int column) const
{
switch (column) {
case 0: return "id";
case 1: return "name";
case 2: return "address";
case 3: return "valid";
case 4: return "date";
}
return QString();
}
bool TestDataSource::read(NCReportXMLReader *)
{
return true;
}
bool TestDataSource::write(NCReportXMLWriter *)
{
return true;
}
void TestDataSource::addData(const TestData & data)
{
list.append( data );
}
| 1 | An index of -1 keeps the current record. |
Using the TestDataSource class
Now let’s use the TestDataSource class.
To use it, add a Custom data source to your
report in the Designer. You can only use the Static location type for this data source. Specify the same data source ID
both in the Designer and in your code, using the setID() function.
NCReport report;
TestDataSource *ds = new TestDataSource();
ds->setID("cds0");
TestData d1;
d1.id = 123;
d1.name = "Alexander Henry";
d1.address = "HOT SPRINGS VILLAGE, AR";
d1.valid = true;
d1.date = QDate(2008,01,10);
ds->addData( d1 );
TestData d2;
d2.id = 157;
d2.name = "Julius Coleman";
d2.address = "Coronado, CA";
d2.valid = false;
d2.date = QDate(2008,01,12);
ds->addData( d2 );
TestData d3;
d3.id = 157;
d3.name = "Peter Moulding";
d3.address = "San francisco, CA";
d3.valid = true;
d3.date = QDate(2008,01,07);
ds->addData( d3 );
report.addCustomDataSource( ds );
Custom items in NCReport
Custom items are powerful members of the report system. The custom item feature enables you to render special, custom-defined content in reports. The typical use case is rendering graphs or similar content.
-
Add a Graph (Custom) item to your report in the Designer and specify the size and location of the object. Specify the class ID of the item. This ID is used for identification.
-
Subclass the
NCReportGraphRendererclass and implement itspaintItem()method. -
Set the ID of your object with the
setID()function. -
Add your renderer object to NCReport with the
addGraphItemRenderer()function.
|
Older versions used the |
For chart rendering, see also Charts and Graphics. To modify report items from code while the report runs, see User Functions.
Here is an example of a custom item class:
Declaration
#include "ncreportgraphrenderer.h"
class TestItemRendering : public NCReportGraphRenderer
{
public:
TestItemRendering();
~TestItemRendering();
void paintItem( QPainter* painter, NCReportOutput* output, const QRectF& rect, const QString& itemdata );
};
Implementation
#include "testitemrendering.h"
#include "ncreportoutput.h"
#include <QPainter>
#include <QColor>
TestItemRendering::TestItemRendering()
{
}
TestItemRendering::~TestItemRendering()
{
}
void TestItemRendering::paintItem(QPainter * painter, NCReportOutput* output, const QRectF & rect, const QString & itemdata)
{
switch ( output->outputType() ) {
case NCReportOutput::Printer:
case NCReportOutput::Pdf:
case NCReportOutput::Preview:
break;
default:
return;
}
const int numcols = 10;
const int cw = qRound(rect.width()/numcols);
painter->setPen( Qt::NoPen );
int ch=0;
QColor color;
color.setAlpha( 128 );
for ( int i=0; i < numcols; ++i ) {
if ( i%3 == 0 ) {
color.setRgb(0xAAAAFF);
ch = qRound(rect.height()*0.8);
} else if ( i%2 == 0 ) {
color.setRgb(0xAAFFAA);
ch = qRound(rect.height()*0.4);
} else {
color.setRgb(0xFFAAAA);
ch = qRound(rect.height()*0.6);
}
painter->setBrush( QBrush(color) );
painter->drawRect( rect.x()+i*cw, rect.y()+qRound(rect.height())-ch , cw, ch );
}
painter->setPen( QPen(Qt::black) );
painter->setBrush( Qt::NoBrush );
painter->drawRect( rect );
painter->setFont( QFont("Arial",8) );
painter->drawText( rect, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap,
QString("GRAPH EXAMPLE: %1").arg(itemdata) );
}
Batch report mode
Batch report mode is a feature that enables running multiple reports
into one output. Batch mode makes it possible to join two or more reports in a specified order
and run them as one report. Page numbering is not continuous: each member report keeps its own page numbering
(the gpageno system variable returns the global page number across the whole batch).
In reports, the reportno and reportcount system variables can be used to determine the current report number and the total number of
reports in the batch. For a more detailed description, see Batch Report Mode.
Using batch mode
Batch mode is enabled as soon as a report is added to the batch with the addReportToBatch(…) function.
The function accepts either a report definition XML string or a report file name (a string ending with .xml or .ncr is treated as a file name).
This example shows how to prepare a batch report from existing report files by loading their content into strings:
#include "ncreportutils.h"
...
report->clearBatch();
QString report1;
NCReportUtils::fileToString( "/home/anywhere/report1.xml", report1 );
report->addReportToBatch( report1 );
QString report2;
NCReportUtils::fileToString( "/home/anywhere/report2.xml", report2 );
report->addReportToBatch( report2 );
QString report3;
NCReportUtils::fileToString( "/home/anywhere/report3.xml", report3 );
report->addReportToBatch( report3 );
The order of the reports in the batch matches the order of the addReportToBatch(…) calls.
Monitoring report progress
While a report runs, the NCReport object emits signals that report the state of the run. You can connect them to a progress
dialog, a status bar or a log. The signals are emitted synchronously from the thread that calls runReport() (or one of the
runReportTo…() functions), so the connected slots run during the report run, before runReport() returns.
Progress signals
- void reportStarts()
-
Emitted by
runReport()after the interactive parameters (seegetParameters()below) have been collected and before the report definition is loaded and processed. It is emitted only once perrunReport()call, also in batch mode. It is not emitted if no output is set or if the user cancels a parameter input dialog. - void reportEnds()
-
Emitted once at the end of
runReport(), after all reports of the run (including all batch members and forced copies) have been processed and the output has been finished. It is also emitted when the run ended with an error or was cancelled. Output errors are copied into the error state of the report only after this signal, so checkhasError()andlastErrorMsg()afterrunReport()returns rather than in a slot connected toreportEnds(). - void dataSourceOpen( const QString& dsID )
-
Emitted just before a data source is opened, for example before the SQL query of an SQL data source is executed. Despite the parameter name, the string is a translatable message in the form
Opening data source: <id>, so it can be shown directly as a label text. - void dataSourceOpened( const QString& dsID, int totalRows )
-
Emitted after a data source has been opened successfully. The string is a message in the form
Data source opened: <id>; totalRows is the size of the data source as reported by the data source itself, or-1if the size is not available (for example with SQL drivers that cannot report the query size). - void dataRowCount( int numRows )
-
Emitted when the processing of a detail section’s data source begins: once after all data sources have been opened, and again each time the engine switches to the data source of the next detail. numRows is the row count of that data source. Use it to set the maximum of a progress bar. The value can be
-1(or0) for data sources that cannot report their row count. - void dataRowProgress( const QString& dsID, int row ), void dataRowProgress( int row )
-
Emitted each time the engine moves to the next record of the current detail’s data source. The first overload also passes the ID of the data source. The row counter restarts at the beginning of every detail.
- void sectionProgress( const QString& sectionID )
-
Emitted every time the engine starts to render a section. The parameter is the ID of the section. This signal is emitted very frequently; connect it only to lightweight slots.
- void pageProgress( int page ), void pageProgress( const QString& text )
-
Emitted when a new page is started. The integer overload passes the global page number (the page number across the whole batch). The string overload passes a ready-made message built from the page progress text, which is
Report is running (page %1)…by default and can be changed withsetPageProgressText( const QString& ); the%1placeholder is replaced with the page number. - void batchReportRunning( int reportNumber )
-
Emitted in batch mode before each member report starts. reportNumber is 1-based. This signal is emitted only for batches built from report definition strings or file names (
addReportToBatch( const QString& ),setReportBatch()); it is not emitted for batches built withaddReportToBatch( NCReport* ).
In double-pass mode the engine processes the report twice. The progress signals are emitted in both passes, and the row values are
scaled accordingly: dataRowCount() passes twice the row count, and during the second pass dataRowProgress() continues from the
row count, so a progress bar fills up exactly once. Page numbers, however, are reported for both passes.
Using a progress dialog
The following example runs a report into a PDF file and shows its progress in a QProgressDialog. The Cancel button of the dialog
stops the report with cancel().
#include <QProgressDialog>
#include <QMessageBox>
#include <QFile>
#include "ncreport.h"
void MainWindow::runOrdersReport()
{
NCReport report;
report.setReportFile( "orders.ncr" );
report.setProcessEvents( true ); (1)
QProgressDialog progress( tr("Running report..."), tr("Cancel"), 0, 0, this );
progress.setWindowModality( Qt::WindowModal );
progress.setMinimumDuration( 500 );
progress.setAutoReset( false ); (2)
progress.setAutoClose( false );
connect( &report, &NCReport::dataSourceOpen, &progress, &QProgressDialog::setLabelText );
connect( &report, &NCReport::dataRowCount, &progress, [&progress](int rows) {
progress.setMaximum( rows > 0 ? rows : 0 ); (3)
progress.setValue( 0 );
});
connect( &report, QOverload<int>::of(&NCReport::dataRowProgress),
&progress, &QProgressDialog::setValue );
connect( &report, QOverload<const QString&>::of(&NCReport::pageProgress),
&progress, &QProgressDialog::setLabelText );
connect( &progress, &QProgressDialog::canceled, &report, &NCReport::cancel );
report.runReportToPdfWriter( "orders.pdf" );
progress.close();
if ( progress.wasCanceled() ) {
QFile::remove( "orders.pdf" ); (4)
} else if ( report.hasError() ) {
QMessageBox::warning( this, tr("Report error"), report.lastErrorMsg() );
}
}
| 1 | Keeps the GUI responsive while the report runs. |
| 2 | The row counter restarts for every detail, so the dialog must not reset itself. |
| 3 | A maximum of 0 shows a busy indicator. |
| 4 | The file contains only the pages rendered before cancelling. |
QOverload requires Qt 5.7 or later. With the old string-based syntax use SIGNAL(dataRowProgress(int)) and
SIGNAL(pageProgress(QString)); this is how the NCReport runner application (src/runner) connects its progress dialog.
Cancelling a report
cancel() is a public slot. It has an effect only while a report is running; outside of a run it does nothing. When it is called,
the engine:
-
stops rendering at the next record (the remaining records are not processed and the report footer is not printed); text document reports (
TextDocumentPrinttype) don’t check for cancellation, -
closes the current page without rendering the page footer,
-
finishes the output normally: the output’s
end()andcleanup()functions still run, so a PDF or other file output is written with the pages rendered so far, a printer output sends those pages to the printer, and a preview output contains them, -
sets the error state of the report:
hasError()returnstrueandlastErrorMsg()returnsReport process cancelled.
The return value of runReport() and the result of reportDone() are not reliable indicators of a cancelled run: both can still
report success. Detect cancellation from your own user interface (as progress.wasCanceled() does in the example above) or from
hasError().
|
Because the output is finished normally, cancelling a printer output still prints the pages rendered so far, and cancelling a file output leaves a partial file behind. Delete the file yourself if you don’t need it. |
Keeping the user interface responsive
The report runs in the calling (usually the GUI) thread. Without further measures the event loop is blocked while the report runs, so windows are not repainted and button clicks, such as the Cancel button of a progress dialog, are not processed.
- void setProcessEvents( bool enable ), bool isProcessEvents() const
-
When enabled, the engine calls
QApplication::processEvents()during the run: before each data source is opened, after the data sources have been opened, at the beginning of every page, and for every detail in text document mode. This lets the application repaint its windows and react to user input, which is required if you want to cancel the run from your user interface. It is disabled by default; the NCReport Designer enables it.Note that
processEvents()is not called for every record, and the engine cannot process events while a long-running SQL query is being executed. A window-modalQProgressDialogadditionally processes events itself whenever its value is set. - void setShowWaitCursor( bool set ), bool showWaitCursor() const
-
When enabled, the engine sets the
Qt::WaitCursoroverride cursor while the report is rendered and restores it afterwards. The cursor is set after the output has been initialized, so it does not affect the print dialog of a printer output. It is disabled by default. Disable it when you show your own progress dialog, since the wait cursor suggests that the application does not accept input. - void setUseWaitCursor( bool )
-
Obsolete. The value is stored, but the engine does not use it. Use
setShowWaitCursor()instead.
|
While events are processed, the user can trigger other actions of the application, for example start another report or close the
window that owns the |
Customization hooks
Besides data sources, item renderers and user functions, the API offers a few hooks to adjust the report definition, the printer and the way the report definition is loaded.
Customizing the printer
- void customizePrinter( QPrinter* printer )
-
This signal is emitted when a printer output is initialized: after the engine has configured the
QPrinterobject from the page options of the report definition (paper size, orientation, full page mode, copies) and before the print dialog is shown. A slot connected to it can change anyQPrintersetting, for example the duplex mode, the paper source or the color mode. Since the print dialog comes afterwards, the user can still override the settings in the dialog.NCReportforwards this signal only for outputs created byrunReportToPrinter()andrunReportToPrinterNoDialog(). If you create anNCReportPrinterOutput(or the derivedNCReportPdfOutput) yourself, connect to the signal of the output object, which has the same signature. When printing from the preview window,NCReportPreviewWindowemits its owncustomizePrinter( QPrinter* )signal.
connect( report, &NCReport::customizePrinter, this, [](QPrinter* printer) {
printer->setDuplex( QPrinter::DuplexLongSide );
printer->setColorMode( QPrinter::GrayScale );
printer->setDocName( "Monthly invoices" );
});
report->runReportToPrinter( 1, true, this );
|
Don’t change the printer resolution in this slot. The engine has already set its internal scaling from the printer resolution when
the signal is emitted. Use |
Modifying the report definition before running
After the report definition has been loaded (parsed into an NCReportDef object) and before rendering starts, the engine offers two
hooks for modifying it. Both are called each time a definition is loaded, that is once per report run and once for every member
report in batch mode.
- void reportDefChange( NCReportDef* reportDef )
-
A signal emitted right after the definition has been loaded and the custom data (see
setCustomData()) has been applied. It is emitted even if loading failed, so checkhasError()in the slot before relying on the content of the definition. - virtual void customizeReportDef( NCReportDef* reportDef )
-
A protected virtual function called after
reportDefChange(), only if the definition was loaded successfully. The default implementation does nothing. SubclassNCReportand reimplement it to modify the definition in code.
Changes made in these hooks affect the current run only, because the definition is reloaded from its source for every run. The page
options are passed to the output after the hooks, so changes of the page settings (reportDef→option()) take effect.
#include "ncreport.h"
#include "ncreportdef.h"
#include "ncreportsection.h"
class MyReport : public NCReport
{
public:
explicit MyReport( QObject* parent = nullptr ) : NCReport( parent ) {}
bool landscape = false;
bool hideReportHeader = false;
protected:
void customizeReportDef( NCReportDef* rdef ) override
{
if ( landscape )
rdef->option().setOrientation( NCReportPrinter::Landscape );
if ( hideReportHeader && rdef->reportHeader() )
rdef->reportHeader()->setShow( false );
}
};
MyReport report;
report.setReportFile( "orders.ncr" );
report.landscape = true;
report.runReportToPdfWriter( "orders.pdf" );
Custom report sources
The report source loads the XML report definition (see NCReportSource::ReportSourceType). To load definitions from a place that
the built-in types don’t cover, for example from an application specific repository or an encrypted archive, subclass
NCReportSource and reimplement its protected virtual loadFromCustomSource( NCReportXMLDefReader& reader ) function (or any of the
other loadFrom…() functions). Pass the XML text to reader.read(); on a parse error call setParseError( reader ), which
sets the error message of the report.
The NCReport object creates its report source object internally. To make it create your class, set a report source factory: a
subclass of NCReportSourceFactory that reimplements virtual NCReportSource* createReportSource().
- void setReportSourceFactory( NCReportSourceFactory* factory ), NCReportSourceFactory* reportSourceFactory() const
-
Sets and returns the factory used to create the report source object. The
NCReportobject does not take ownership of the factory, but it deletes the report source object created by the factory when it is destroyed.
#include "ncreportsource.h"
#include "ncreportsourcefactory.h"
#include "ncreportxmldefreader.h"
class RepositoryReportSource : public NCReportSource
{
protected:
bool loadFromCustomSource( NCReportXMLDefReader& reader ) override
{
const QString xml = Repository::instance()->reportXml( stringReportID() );
bool ok = reader.read( nullptr, xml );
if ( !ok )
setParseError( reader );
return ok;
}
};
class RepositorySourceFactory : public NCReportSourceFactory
{
public:
NCReportSource* createReportSource() override { return new RepositoryReportSource(); }
};
RepositorySourceFactory factory; (1)
NCReport report;
report.setReportSourceFactory( &factory ); (2)
report.setReportSourceType( NCReportSource::Custom );
report.reportSource()->setStringReportID( "invoice" );
report.runReportToPreview();
| 1 | The factory must live as long as the report object uses it. |
| 2 | Set the factory first, before any function that creates the report source (see the warning below). |
|
The report source object is created only once, by the first function that needs it: |
Custom evaluator
- void setEvaluator( NCReportEvaluator* evaluator ), NCReportEvaluator* evaluator()
-
Sets a user defined evaluator. The evaluator is responsible for evaluating fields, variables, parameters, print-when conditions, dynamic items and script expressions. If an evaluator is set, the report engine uses it instead of creating its own
NCReportEvaluatorobject. SubclassNCReportEvaluatorand reimplement its virtual functions (for exampleevaluate(),evaluateScript(),evaluatePrintWhen()orgetDataSourceValue()) to change how expressions are evaluated. TheNCReportobject does not take ownership: the evaluator must exist as long as the report uses it. CallsetEvaluator( nullptr )to return to the default evaluator.
|
For most needs, user functions are a simpler and safer way to add custom logic to a report than a custom evaluator. Reimplement evaluator functions only if you need to change the evaluation itself, and call the base class implementation for everything you don’t handle. |
Other runtime settings
The following functions of the NCReport class change how a report runs or return information about the last run.
- void setCountry( QLocale::Country country )
-
Sets the country used together with the current report language. When a current language is set (with
setCurrentLanguage()or in the report definition) and it is not the default language, the engine sets the defaultQLocaleto that language and this country for the duration of the run and restores the previous default locale afterwards. The locale affects, for example, number and date formatting. Without a current language the country has no effect. The default isQLocale::AnyCountry. There is no getter and no separatesetLocale()function. - void setCustomData( const QString& data ), QString customData() const
-
Sets an arbitrary string that replaces the custom data (
customdatapage option) of the report definition each time the definition is loaded; an empty string leaves the value of the definition unchanged. The engine itself does not interpret the value. Your code can read it from the definition withreportDef()→option().customData(), for example incustomizeReportDef(), in a slot connected toreportDefChange(), in a user function or in a custom data source. - void setZintPath( const QString& dir ), QString zintPath() const
-
Sets and returns the directory of the Zint barcode program. On Windows the default value is detected from the registry (the installation path of
qtZint.exe). The value is stored in the report definition object, but the current rendering engine does not use it: barcodes are rendered by the Zint library linked to NCReport. The function is kept for compatibility. - QPointF lastPagePositionMM() const, void setLastPagePositionMM( const QPointF& position )
-
lastPagePositionMM()returns the paint position, in millimeters from the top-left corner of the page, at the end of the last page of the last run. The vertical coordinate is useful for determining the printed height of endless (continuous paper) outputs. The engine sets the value at the end of every page withsetLastPagePositionMM(); applications normally don’t need to call the setter. - bool getParameters()
-
Asks the user for the values of the parameters whose value is the
{GET}string, with an input dialog for each of them. If a parameter named<id>_DEFAULTexists, its value is used as the default value of the dialog.runReport()calls this function automatically before the report starts. If the user cancels a dialog, the function sets the error messageParameter <id> input is cancelled by the user.and returnsfalse, andrunReport()returnsfalsewithout running the report. Set the parent widget of the dialogs withsetParentWidget(). The function is available only if NCReport was built with the preview module; otherwise it returnstruewithout doing anything. - QVariant getParameter( const QString& id, const QVariant& value, QWidget* parent, const QString& title, const QString& label, bool* ok )
-
Shows a single input dialog for parameter id and, if the user accepts it, adds the entered value as a report parameter. The type of the default value determines the dialog: an integer input for
int, a floating point input fordouble, and a text input otherwise. Returns the entered value and sets ok tofalseif the dialog was cancelled. Not available in builds without desktop support (NCREPORT_NO_DESKTOP).
report->addParameter( "customerId", "{GET}" );
report->addParameter( "customerId_DEFAULT", 1000 ); (1)
report->setParentWidget( this );
report->runReportToPreview();
| 1 | The dialog asks for an integer, with 1000 as the default value.
|
|
|
- int runTimeElapsed() const
-
Returns the time, in milliseconds, spent on rendering in the last run. The measurement starts after the definition has been loaded and the output initialized (so a print dialog is not included) and includes finishing the output. In batch mode the value belongs to the last member report.
- int pageCount() const, bool reportDone() const
-
pageCount()returns the number of pages of the last run.reportDone()returnstrueif the last run finished without a processing failure. Note thatreportDone()does not reflect cancellation; see Monitoring report progress. - ReportType reportType() const
-
Intended to return the type of the report (
NCReport::Report,NCReport::TextDocumentPrintorNCReport::Label). In the current version the underlying value is never updated, so the function always returnsNCReport::Report. To get the actual type, usereportDef()→option().reportType()after the definition has been loaded. - void setAlternateRowBackgroundColor( const QColor& color ), QColor alternateRowBackgroundColor() const
-
Sets the default background color of alternating rows. It is used by detail sections that have alternating row background enabled (
altBackRowModeset to odd or even) and use the default color (useDefaultAltBackRowColor, which is the default). Sections with their own alternating row color are not affected. The default color is light gray, RGB(220, 220, 220).