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)

  • NCReportHtmlOutput and NCReportExcelHtmlOutput - 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 NCReportPreviewWindow object. The preview window deletes its output object when it is destroyed.

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 setReport() function.

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 NCReportPreviewWindow::exec() function shows the preview window and keeps the application event loop running during the preview. This is useful when you use a locally defined report object, because the report object will not be deleted until the user closes the preview window.

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 NCReportGraphRenderer class and implement its paintItem() 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 NCReportAbstractItemRendering base class and the addItemRenderingClass() function. Both still exist for backward compatibility, but they are obsolete; use NCReportGraphRenderer and addGraphItemRenderer() in new code.

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) );
}

Using the class

NCReport report;

TestItemRendering *irc = new TestItemRendering();
irc->setID("testitem0");
report.addGraphItemRenderer( irc );

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 (see getParameters() below) have been collected and before the report definition is loaded and processed. It is emitted only once per runReport() 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 check hasError() and lastErrorMsg() after runReport() returns rather than in a slot connected to reportEnds().

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 -1 if 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 (or 0) 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 with setPageProgressText( const QString& ); the %1 placeholder 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 with addReportToBatch( 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 (TextDocumentPrint type) don’t check for cancellation,

  • closes the current page without rendering the page footer,

  • finishes the output normally: the output’s end() and cleanup() 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() returns true and lastErrorMsg() returns Report 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-modal QProgressDialog additionally processes events itself whenever its value is set.

void setShowWaitCursor( bool set ), bool showWaitCursor() const

When enabled, the engine sets the Qt::WaitCursor override 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 NCReport object. Disable the relevant actions (or use a modal progress dialog) while the report runs, and don’t delete the NCReport object from a slot that is called during the run.

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 QPrinter object 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 any QPrinter setting, 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.

NCReport forwards this signal only for outputs created by runReportToPrinter() and runReportToPrinterNoDialog(). If you create an NCReportPrinterOutput (or the derived NCReportPdfOutput) yourself, connect to the signal of the output object, which has the same signature. When printing from the preview window, NCReportPreviewWindow emits its own customizePrinter( 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 setPrintResolution() (see Other runtime settings) instead.

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 check hasError() 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. Subclass NCReport and 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 NCReport object 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: setReportFile(), setReportDefXml(), setReportSourceType(), setReportSource() or runReport(). Set the factory before calling any of them; a factory set later is ignored.

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 NCReportEvaluator object. Subclass NCReportEvaluator and reimplement its virtual functions (for example evaluate(), evaluateScript(), evaluatePrintWhen() or getDataSourceValue()) to change how expressions are evaluated. The NCReport object does not take ownership: the evaluator must exist as long as the report uses it. Call setEvaluator( 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 default QLocale to 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 is QLocale::AnyCountry. There is no getter and no separate setLocale() function.

void setCustomData( const QString& data ), QString customData() const

Sets an arbitrary string that replaces the custom data (customdata page 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 with reportDef()→option().customData(), for example in customizeReportDef(), in a slot connected to reportDefChange(), 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 with setLastPagePositionMM(); 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>_DEFAULT exists, 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 message Parameter <id> input is cancelled by the user. and returns false, and runReport() returns false without running the report. Set the parent widget of the dialogs with setParentWidget(). The function is available only if NCReport was built with the preview module; otherwise it returns true without 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 for double, and a text input otherwise. Returns the entered value and sets ok to false if 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.
void setForceCopies( int copies )

Runs the whole report copies times into the same output, one copy after the other, in a single job. Unlike the printer’s copy count, the copies are rendered by the engine, so they also work with file outputs. In the report, the forcecopies system variable returns the number of copies and currentcopy returns the index of the current copy, starting from 0. The page number restarts for every copy. The default is 1.

void setPrintResolution( int dpi ), int printResolution() const

Sets the print resolution of printer and file outputs. The value is stored in the page options of the report definition object (the same option as Resolution in the Designer’s page settings), and the outputs except the preview outputs apply it when the report runs. When runReport() loads the definition, a resolution stored in the definition replaces the value set with this function; if the definition has no resolution (Auto in the Designer), the value set here is kept. printResolution() returns the current value of the option, so after a run it may return the resolution of the last loaded definition. A value of -1 means that the default resolution of the output is used.

void setPrintQuality( NCReportPrinter::PrinterMode mode ), NCReportPrinter::PrinterMode printQuality() const

Sets the QPrinter mode (ScreenResolution, PrinterResolution or HighResolution, the default) used when the one-step functions create a printer, PDF, PostScript or Qt preview output. It has no effect on output objects that you create yourself.

void setPageRanges( const QString& ranges ), QString pageRanges() const

Restricts the output to the given pages. The expression is a comma-separated list of page numbers and ranges, for example 1-3,5,8-10. The expression is passed to the output at every run, including outputs that you create and set with setOutput().

runReport() always passes the NCReport page range expression to the output, and an empty expression clears the page list of the output. A page range set only on the output object with NCReportOutput::setPageRanges() is therefore lost when the report runs. Use NCReport::setPageRanges() instead.

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() returns true if the last run finished without a processing failure. Note that reportDone() does not reflect cancellation; see Monitoring report progress.

ReportType reportType() const

Intended to return the type of the report (NCReport::Report, NCReport::TextDocumentPrint or NCReport::Label). In the current version the underlying value is never updated, so the function always returns NCReport::Report. To get the actual type, use reportDef()→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 (altBackRowMode set 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).