TOCBACKFORWARD

Charlie Calvert's C++ Builder Unleashed

- 17 -

Printing: QuickReport and Related Technologies

Overview

In this chapter you will get a look at seven ways to print data:

I am going to explain QuickReport first, since it is by far the most important technique. However, you should be sure to glance down at the other techniques, because they are valuable and can be absorbed fairly quickly.

Printing has traditionally been a complicated subject in Windows. In DOS, it was easier, but still very time-consuming. In BCB, all forms of printing are easy to master, and QuickReport is a subject most programmers can learn in a few hours and master in a few days. After you understand the basics of how to use QuickReport, you should be able to prepare many reports in well under an hour. Even if you were a perfectionist struggling to put together a complicated report, it is unlikely that you would spend more than a few hours on a report.

If you are familiar with ReportSmith or Crystal Reports, you might be inclined to use them first without examining QuickReport. I would, however, spend the time it takes to get up to speed on QuickReport. You might be surprised to find how powerful this tool can be. Certainly, QuickReport is almost preternaturally easy to use, especially when considering how much quicker it makes a traditionally long, complex and frustrating task.

Having said all this, I have to confess that printing is still a task I don't enjoy much. I personally don't have much need for printed reports, because they take up space, become outdated quickly, and are very difficult to track. As a result, I prefer to keep information in electronic form. However, I recognize the importance of printing, and will deal with it in this chapter in considerable depth.

One hint that can help you ease the burden of creating reports is to let SQL statements carry the burden of the work. If you need to create a particular report, write a SQL statement that will generate it as nearly as possible, and then let the statement do double duty as a way of preparing an electronic report, and as a way of preparing a paper-based report. This technique makes the task less onerous to me, and it is usually the quickest way to complete the chore. In this chapter, I examine this technique in my analysis of making a grouped report for the Music program.

QuickReport Basics

QuickReport was originally a third-party tool written in Object Pascal that the VCL team decided to incorporate into the product. This unusual move came about as a result of the high quality of this product, along with its relatively close conformance to the VCL programming paradigm. You will, however, notice a few rough edges because some of the conventions in this product do not exactly match the conventions used elsewhere in the VCL.


NOTE: The rough edges between the VCL and QuickReport will probably be smoothed over in later releases. As a result, you should be prepared for the possibility that your code in this one area might break in future releases of BCB. However, QuickReport is easy to use, and the code associated with it is by definition isolated from the rest of the code in your application. As a result, you should be able to adapt to the change fairly easily.

Because QuickReport is made by a third party, you should look on the Web to see if you can find updates to this product. For instance, Delphi 3 will ship with a version of QuickReport that is much more sophisticated than the version included with BCB 1.0.

The basic idea behind QuickReport is to provide a set of components that are as closely parellel as possible to the tools you use when creating a standard BCB form. For instance, Table 17.1 gives a list of standard visual controls and visual database controls with their QuickReport equivalents.

Table 17.1. Standard VCL controls on the left and QuickReport equivalents on the right.
VCL controls QuickReport controls
TLabel TQRLabel
TDBEdit TQRDBText
TDBLabel TQRDBText
TDBMemo TQRMemo
TShape TQRShape


As you can see, there is no difference between a QuickReport label and a QuickReport edit control. Needless to say, this makes sense when you consider the fact that you can't edit a field in a report!

Each of the data-aware controls has a DataSource and DataField property. The TQRLabel and TQRShape controls do not have a these properties, because they are not data-aware.

Each report that you generate appears on its own form. The act of creating the form is a simple matter of laying out TQRMemo and TQRDBText controls on a series of bands called TQRBand controls.

One visual QuickReport component that does not fit into any predefined category is called TQRSysData. This component can display the current project title, page number, date, time, or record count. The particular function adopted by the component is controlled by its Data property. I will explain how to use the component in more depth later in the chapter.

Each time you create a new QuickReport form, you should also drop down a TQuickReport component and at least one TQRBand component. (Don't confuse the TQuickReport component with the product itself, which is called QuickReport.) The TQuickReport component is a "magic" component that converts a regular form into a QuickReport form. All you need to do is drop the component on the form and set its DataSource property to a TDataSource, just as you would when using a TDBGrid. The TQRBand component is the surface on which you should place the QuickReport visual controls.


NOTE: In most reports you want to print multiple rows, as you would when showing data in a grid. To do this, set the BandType property of a TQRBand report to rbDetail. If you want to have multiple columns in your report, set the Columns property of TQuickReport to the number of columns you want to use. I will explain all this in more depth later in this chapter.

After you finish designing a form, you can run the program and call the Preview method of TQuickReport to view the output. The preview form has a print button on it so you can print the report. If you want, you can skip the preview screen and directly call the Print method of TQuickReport.

The great thing about QuickReport is that it lets you use existing datasets, including calculated fields and lookup fields, that are already part of your project. Rather than concerning yourself with creating custom queries or with setting up sort orders, you can just plug your QuickReport components directly into your existing datasets. If you sort the data in some particular way on a form, that is the same sort order you see when you pop up a QuickReport based on the tables used by that form.

Using TQRBand: Creating Rows and Columns in QuickReport

Most people can figure out how to use QuickReport without help from a book like this. However, there are a few things that people sometimes can't figure out on their own:

All four of these subjects will be covered in this chapter. I've already described how to perform the first two chores earlier, but I will repeat the information here, just so these key bits of information will be as easy as possible to find.

To create multiple rows of data, set the BandType property of a TQRBand report to rbDetail. If you want to have multiple columns in your report, set the Columns property of TQuickReport to the number of columns you want to use. That's all you have to do.

If you dig into the QuickReport controls a bit, you will find the TQRBand component has a BandType property that can be set to one of the values in Table 17.2.

Table 17.2. BandType property values and purposes.
Value Purpose
rbTitle A title printed once at the start of the report.
rbPageHeader Appears at the top of each page.
rbDetail Use this type if you want to repeat many rows of data, or for use with one-to-many reports.
rbSubDetail The detail band in a one-to-many report. This connects the DetailGroup band to the DetailLink component using the QRDetailLink.DetailBand property.
rbPageFooter Creates a footer at the bottom of the page.
rbSummary Creates a summary at the end of the report.
rbGroupHeader Creates group headers for use with QRGroup and QRDetailLink
components.
rbGroupFooter Creates footers for use with QRGroup and QRDetailLink components.
rbColumnHeader Labels each column in a column-based report.
rbOverlay Floats on top of all other text and graphics printed on the page.


The best way to learn about these properties is through experience. In particular, you will find that the BandType property should be set to reflect the types of components being used in a particular context. For instance, the TQRSysData component, which is often used to display the date, time, or page count will generally go on TQRBand components set to rbPageHeader or rbPageFooter. This is only logical, because page count, date, and time information usually appears on the header or footer of a report.

The easiest way to learn how to use TQRBands and their related components is through experience. The next several sections of this chapter give working examples of using these components.

Working with the Sample Programs

In the next few pages I describe the reports from all three major database programs found in this book:

Many people could probably learn how to use QuickReport by running these programs and popping up the various preview pages. After they are familiar with the different styles I use in my preview pages, they can just go back to design mode and study my QuickReport forms to see how I achieved a particular effect. In other words, I expect many people will learn by example.

The text that follows is therefore a bit sketchier than what you find elsewhere in the book. The issue here is simply that this is primarily a mechanical task that takes only a minimal amount of understanding. As a result, I will try to point you in the right direction wherever possible, and then step out of the way so you can experiment on your own computer.

The Address2 Program

I add five reports to the Address2 program from Chapter 13, "Flat-File, Real-World Databases." The first prints only addresses, the second prints only phone numbers, the third prints all the data in the report, and the fourth prints reports by grouping them on the Category field. Later in the chapter, I will also include a section that describes how to print a report from inside of ReportSmith.

Address2: Printing Address

This report has the task of printing out addresses. I do not include phone numbers, just addresses--as if I were making labels. A copy of the finished report as it appears in preview mode is shown in Figure 17.1. Figure 17.2 shows the same report in design mode.

To create the report, start by laying down a TQuickReport component. Connect it to the AddressSource on the program's data module. To do this, you will need to include DMod1 in your project by pulling down the File menu and selecting Include Unit Header.

FIGURE 17.1. The Address Report from the Address2 program.

FIGURE 17.2. The Address Report in design mode.


NOTE: I don't think you can make it through this chapter without at least a minimal understanding of the Address2 program. This program was discussed in depth in Chapter 13. The source for that program, including the reports it uses, are found in the Chap13 directory on the CD that accompanies this book.

If you want to work along with me by creating the forms on your machine as I describe them in this book, open up the Address2 program, and add a new form to it. There is no harm in having two forms in the program that perform the same task; that way, you can easily open up my sample form if you get stuck.

Remember, this material does not cause brain strain. You just need to understand how these components work, and then you will be able to write your own reports.

Now drop down three TQRBand components. Don't place them one on top of the other; lay one down, click on the main form, lay the next one down, and so on. That way, they are positioned one above the other on the main form. The final position of the controls at runtime is determined by the BandType property of each TQRBand. If you want to change the order of the controls on the form, drag them around with the mouse, the same way you move items in the Tab Order list box from the Edit menu. The Align property for these forms can be left at the default position of alTop. Remember, it usually doesn't really matter what order these controls have on your form; what matters is the BandType property.

The BandType for the top QRBand control should be set to rbTitle, the next one should be set to rbDetail, and the last one should be set to rbPageFooter. Drop down the Frame property of the detail band and set its DrawLeft, DrawRight, DrawTop, and DrawBottom properties to True.

The title band should have a TQRLabel component placed on it. Set its alignment to taCenter and set AlignToBand to True. Use the Font property to select a large bold font. Set the Caption property to the title of the report.

Another way to create a title involves using the TQRSysData component. Drop one down on the title band and set its alignment and font as you did the TQRLabel. Instead of changing the Caption property of this component, set its Data property to qrsReportTitle. Click on the TQuickReport component and set its ReportTitle property to the name of the report. For instance, you might set it to Address Report. Now when you run the report, the title will show up automatically at the top of the first page.

On the detail band, drop down four TQRDBText controls and set their DataSource property to the AddressSource from the program's data module and their fields to FirstLast, Address1, Address2, and CityStateZip. The first and last fields are calculated fields. The code for creating the calculated fields looks like this:

void __fastcall TDMod::AddressTableCalcFields(TDataSet *DataSet)


{

  if ((!AddressTableFName->IsNull) || (!AddressTableLName->IsNull))

    AddressTableFirstLast->Value =

      AddressTableFName->Value + " " + AddressTableLName->Value;

  else if (!AddressTableCompany->IsNull)

    
AddressTableFirstLast->Value = AddressTableCompany->Value;

  else

    AddressTableFirstLast->Value = "Blank Record";

  AddressTableCityStateZip->Value = AddressTableCity->Value + " " +

    
AddressTableState->Value + ", " + AddressTableZip->Value;

}

The code for creating the AddressTableFirstLast field was described during the initial discussion of the Address2 program in Chapter 13. The code for creating the AddressTableCityStateZip field involves nothing more than concatenating the three fields, and adding spaces and commas where appropriate. In a professional program, you might want to add more code to eliminate the possibility that a blank set of fields would generate a string that consists of nothing but a comma. I will omit such code here so that you can more easily decipher the code I do include.

To display more than one column in a report, turn to the TQuickReport component and set its Column property to the value you desire, such as 2 or 3.

The final band in the report cover appears as a footer with the current date and time on it, as well as the page number. You can use the TQRSysData component to retrieve this data. Set one component's Data property to qrsDateTime and the other to qrsPageNumber.

If you want to show a print dialog to the user, you can do so in the BeforePrint event of the TQuickReport component:

void __fastcall TAddressReport::QuickReport1BeforePrint(bool &PrintReport)

{

  if (PrintDialog1->Execute())

  {

    PrintReport = 
True;

  }

  else

  {

    PrintReport = False;

  }

}

This code pops up a print dialog, as shown in Figure 17.3. The TPrintDialog component is found on the Dialogs page of the Component Palette. The settings selected by the user in the print dialog are sometimes passed on to the system without intervention on your part. However, if you need to access the settings, you can do so via the fields of the component. You can then pass this information on to QuickReport if necessary.

FIGURE 17.3. Showing a print dialog so the user can select a printer.


NOTE: There is a button on the TPrintDialog control that will pop up a setup dialog for the printer. As a result, there is rarely any reason for you to include a TPrinterSetupDialog in your program. The act of including a TPrintDialog covers both bases automatically.

To show the report to the user, you can set up a menu item on the main form of the program with its caption set to the string "Address Report". This is what the response to a click on this control should look like:

void __fastcall TForm1::PrintAddresses1Click(TObject *Sender)

{

  
AddressReport->QuickReport1->Preview();

}

This assumes the name of the form on which the report is stored is called AddressReport, and the unit containing this form has been included in the main form:

#include "QRAddress1.h"

The Preview screen has a button on it that allows the user to print the report. If you didn't want to show the preview first, you can call PRINT directly:

AddressReport->QuickReport1->Print();

As you can see, creating reports using TQuickReport is trivial in the extreme. This is the kind of operation you can usually roll out in about 30 minutes, or less. If you find things getting complicated, you can usually clean up the mess by writing a query. On the rare occasions when you need more power than QuickReport offers, you should use another printing tool, such as ReportSmith or Crystal Reports. These third-party products ship with VCL links for both high-quality tools.


NOTE: ReportSmith is still officially part of the Borland suite of products, but most of its day-to-day operations are now being handled by a third party. As a result, it does not ship with the new versions of BCB or Delphi, though it did ship with Delphi 1.0 and Delphi 2.0.

Address2: Grouping Data in a Report

Reports often have to be presented in groups. For instance, the Address2 program has a Category field that allows the user to put each record in a separate category, such as Work, Home, Family, Fun, or Entertainment. The user wants to be able to generate reports by groups, as shown in Figure 17.4.

QuickReport makes it easy to create a report of this kind. To get started, drop down three TQRBand components, setting the BandType property from the first to rbGroupHeader, the second to rbDetail, and the third to rbGroupFooter. Also drop down a TQuickReport control and set its DataSource property to the AddressSource control from the program's data module.

You now need to drop down a TQRGroup component and set its DataSource to AddressSource and its DataField to Category. Set the HeaderBand property to the first TQRBand control and the FooterBand property to the third TQRBand control.

FIGURE 17.4. A report from the Address2 program grouped on the Category field.

You can now drop down and hook up some TQRSysData and TQRDBText controls so that you can display information to the user. The final report should look like the screen shot shown in Figure 17.5.

FIGURE 17.5. The GroupReport as it appears at design time.

Troubleshooting Tips

Generating reports with TQuickReport is very simple, but occasionally things will go wrong. Here are some troubleshooting tips:

Reports in the Music Program

The Music program appeared in Chapter 16, "Advanced InterBase Concepts." There are two types of reports in the music program:

1. A one-to-many report that shows all the albums associated with each artist.

2. A group-by report that groups each album under its associated type, such as jazz or rock.

The next few pages cover both types of reports. Again, my text will be a bit abrupt at times. The material here is so simple that the best approach is just to give you a few hints about how to proceed, and then let you hammer out the details by working live with the tools.

One-to-Many Reports

The one-to-many report from the Music program shows how to use the TQRDetailLink component. See Figure 17.6.

FIGURE 17.6. The one-to-many Album report as it appears in print preview mode.

To get started, drop down four TQRBand components. Set the BandType for the first component to rbPageHeader, the second one to rbGroupHeader, the third one to rbSubDetail, and the last one to rbPageFooter. You might find it useful to name each component after its type:

Component name BandType
PageHeaderBand rbPageHeader
GroupHeaderBand rbGroupHeader
SubDetailBand rbSubDetail
PageFooterBand rbPageFooter


The header and footer bands should use TQRSysData components to display standard information such as the report title, page number, and the time and date the report is printed.

Drop down a TQuickReport component and connect it to the ArtistSource on the data module. Now drop down a TQRDetailLink component, which is designed to help you set up one-to-many reports. The TQRDetailLink component should have its DataSource property set to the ArtistSource from the data module and its Master property set to QuickReport1. The DetailBand property should be set to the SubDetailBand and the HeaderBand property to the GroupHeaderBand.

After you have everything in place, drop down TQRDBText controls to display the fields of your data. In particular, place one TQRDBText control on the GroupHeaderBand, set the DataSource property equal to ArtistSource from the program's data module, and set its DataField property equal to its FirstLast calculated field.

On the SubDetailBand, drop down four TQRDBText components and set the DataSource property equal to DMod->AlbumSource. Set the DataField property for these controls to the Album and Rating fields, and to the LoudLookup and MediumLookup calculated fields. The result should look like the image shown in Figure 17.7.

FIGURE 17.7. The one-to-many Album report as it appears at design time.

You can now set up a menu item on the program's main page that will launch the form. When you run the program, the form you have created should make a report like that shown in Figure 17.7.

Using Queries to Help with Grouped Reports

The second report from the Music program groups all the records in the report according to their type. For instance, it groups all the jazz albums together and all the rock albums together.

I've already shown you one grouped report in this chapter. I'm showing you another because this one uses queries to easily resolve what appears to be a rather complicated problem. The difficulty here is that you want the report to group according to the type of album, but to also organize the report so that each album is grouped under a particular artist. This is a bit tricky to do, given the design of the database. To remedy the problem, I created a query that generates most of my report for me:

select Types.Types, Artist.First,

  Artist.Last, Album.Album, Album.Rating, Loudness.Loudness

  from Artist, Album, Types, Loudness

  where Artist.Code = 
Album.GroupCode and

    Types.Code=Album.Types and

    Loudness.Code = Album.Loudness

  group by Types.Types, Artist.Last,

    Artist.First, Album.Album, Album.Rating, Loudness.Loudness

This is a fairly straightforward query that gives me all the records in the table sorted first by record type, and secondly by artist. That way, all the Bob Dylan albums that I consider to be folk records appear together, and all the Dylan albums that I think are rock albums are grouped together. Inside of each type, I group the albums alphabetically. The query also does lookups into the Types and Loudness tables so I can substitute human-readable strings for the numeric codes found in the Albums table.


NOTE: Queries not only have the power to solve complicated problems, but they are also fun to use and can therefore spice up the mundane task of creating a report. They can also be reused in the main body of your program. Often, it makes sense to place queries like this inside a stored procedure.

After creating the query, I need to create a calculated field called FirstLast that gives me a single string containing the first and last names:

void __fastcall TAlbumGroupForm::AlbumQueryCalcFields(TDataSet *DataSet)

{

  AlbumQueryFirstLast->Value =

    
AlbumQueryFIRST->Value + " " + AlbumQueryLAST->Value;

}

There is no need to create lookup fields for the Loudness and Types values because I retrieved the strings associated with these fields from the Loudness and Types tables in the original query.

After writing the query, most of the work I need to do for the report is done. Now I can simply drop down some TQRBand components, a TQuickReport component, and a TQRGroup component, and link them together as explained earlier in the chapter. I perform the grouping on the Types field from the query.

On the rbGroupHeader band I drop down a TQRDBText control that is set equal to the Types field. I then add fields rbDetail band that will display each artist's name, each album's name, and each's loudness and type. The final report looks like the image shown in Figure 17.8, and the design time shape of the form is shown in Figure 17.9.

FIGURE 17.8. The Album Group report as it appears in print preview mode.

FIGURE 17.9. The Album Group report as it appears at design time.

That is all I want to say about QuickReport in this book. If you want additional examples of using QuickReport, you should view the kdAdd program.

Printing Forms

One of the easiest ways to print information in BCB is to simply send the image of a form to a printer. The code for doing this is trivial in the extreme:

void __fastcall TForm1::Print1Click(TObject *Sender)

{

  Form1->Print();

}

It doesn't get any easier than this.

On the CD that accompanies this book, you will find a program called PrintForm that contains a menu item that will print the contents of the current form. This option will not help you print a dataset, but it will print whatever you can see on the current form. For instance, the PrintForm program prints the current form as shown in Figure 17.10, minus the menu, border, and caption areas. The quality of the picture in the printed output can differ depending on a number of factors.

FIGURE 17.10. The main form of the PrintForm application just as the user selects the Print option from the menu.

The Address2 program gives you the option of printing a series of forms. In particular, it iterates through the database, printing one form, on one page, for each record in the database. This is a waste of resources, but it does produce clean-looking, easy-to-read reports. However, the user would have to turn the page each time he or she views a new record. This task is so onerous that it leaves this solution virtually useless for all but a few, unusual tasks.

Here is the code that prints all the forms in the Address2 program:

void __fastcall TForm1::PrintForms1Click(TObject *Sender)

{

  if (ScalingForm->ShowModal() == mrOk)

  {

    switch (ScalingForm->ScalingOptions->ItemIndex)

    {

     case 0: PrintScale = poNone; 
break;

     case 1: PrintScale = poProportional; break;

     case 2: PrintScale = poPrintToFit; break;

    }

    Panel2->Visible = False;

    DBGrid1->Visible = False;

    DMod->AddressTable->First();

    while 
(!DMod->AddressTable->Eof)

    {

      Print();

      DMod->AddressTable->Next();

    }

    DBGrid1->Visible = True;

    Panel2->Visible = True;

  }

}

This code first pops up a custom dialog like the one shown in Figure 17.11. This dialog lets you choose three printing options provided by the TForm object in a property called PrintScale. The print options (poNone, poProportional, poPrintToFit) are declared in Forms.hpp.

If you choose PrintToFit, your form will take up the maximum amount of space it can on the pages you are printing. In short, it will expand to fill the available space but will continue to remain in proportion. That is, it won't be stretched but will expand as far as it can in both the horizontal and vertical directions. When it reaches the limit in either direction, it will stop expanding in both directions, so that the view you have of the form remains proportional.

The code for this routine also makes invisible all the controls on the form that are not needed when printing. For instance, the buttons on the form serve no purpose on the printed form, so I make them invisible. A second solution is to create a custom form featuring only the portions of this view that need to be printed.

The code then iterates through the database, printing each form:

DMod->AddressTable->First();

while 
(!DMod->AddressTable->Eof)

{

  Print();

  DMod->AddressTable->Next();

}


FIGURE 17.11. The Scaling form dialog gives you a chance to choose how the reports will look when printed.

Be careful, because with this kind of code, it's easy to get stuck in an endless loop by forgetting to call Next(). This is a no-brainer error that everyone can see why it is a mistake, but people still tend to make it when they are rushed or tired.

TPrinter: Printing Text, Shapes, and Bitmaps

There are two easy ways to send output directly to the printer from a VCL program. One is to open up the printer as a device and send output to it, and the second is to use the VCL TPrinter object. The program shown in this section uses the latter method.

TPrinter provides a TCanvas object initialized to the DC for the printer. As a result, you can send text or graphics objects to the printer just as easily as you can send them to the screen.

The PrintGDI program found on the CD that accompanies this book shows how to print text, shapes, and bitmaps using the TPrinter object. It consists of three forms, shown in Figures 17.12, 17.13, and 17.14. The source for the program is shown in Listings 17.1. through 17.6.

FIGURE 17.12. The text from a Shakespearean sonnet shown in a form. You can use the program to print the text.

Figure 17.13. The form shown here has a TPaintBox component with some shapes drawn in it. You can print the contents of the paint box.

FIGURE 17.14. This form can display bitmaps and also send their contents to the printer.

Listing 17.1. The header for the main module of the PrintGDI program.

///////////////////////////////////////

// File: Main.h

// Project: PrintText

// Copyright (c) 1997 by Charlie 
Calvert

//

#ifndef MainH

#define MainH

#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\Buttons.hpp>

#include <vcl\ExtCtrls.hpp>


#include <vcl\Menus.hpp>

#include <vcl\Dialogs.hpp>

class TForm1 : public TForm

{

__published:

  TMainMenu *MainMenu1;

  TMenuItem *File1;

  TMenuItem *ShapeForm1;

  TMenuItem *Print1;

  TMenuItem *N1;

  TMenuItem *Exit1;

  
TPrintDialog *PrintDialog1;

  TMemo *Memo1;

  TMenuItem *Open1;

  TOpenDialog *OpenDialog1;

  TMenuItem *N2;

  TMenuItem *BitmapForm1;

  void __fastcall Print1Click(TObject *Sender);

  void __fastcall Open1Click(TObject *Sender); 

  void 
__fastcall ShapeForm1Click(TObject *Sender);

  void __fastcall BitmapForm1Click(TObject *Sender);

private:

  void SendToPrinter();

  void PrintText(TCanvas *Canvas);

public:

  __fastcall TForm1(TComponent* Owner);

};

extern TForm1 *Form1;


#endif



Listing 17.2. The main module for the PrintGDI program.

///////////////////////////////////////

// File: Main.cpp

// Project: 
PrintText

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#include <vcl\printers.hpp>

#pragma hdrstop

#include "Main.h"

#include "PaintBoxPrint.h"

#include "PrintBmp1.h"

#pragma 
resource "*.dfm"

TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

}

void TForm1::PrintText(TCanvas *Canvas)

{

  int i, x;

  AnsiString S("Test String");

  x = Canvas->TextHeight(S);

  for 
(i = 0; i < Memo1->Lines->Count; i++)

  {

    S = Memo1->Lines->Strings[i];

    Canvas->TextOut(1, x * i, S);

  }

}

void TForm1::SendToPrinter()

{

  TPrinter *APrinter = Printer();

  APrinter->BeginDoc();

  
PrintText(APrinter->Canvas);

  APrinter->EndDoc();

}

void __fastcall TForm1::Print1Click(TObject *Sender)

{

  if (PrintDialog1->Execute())

  {

    SendToPrinter();

  }

}

void __fastcall TForm1::Open1Click(TObject *Sender)

{

  if 
(OpenDialog1->Execute())

  {

    Memo1->Lines->LoadFromFile(OpenDialog1->FileName);

void __fastcall TForm1::ShapeForm1Click(TObject *Sender)

{

  PaintBoxForm->ShowModal();

}

void __fastcall TForm1::BitmapForm1Click(TObject 
*Sender)

{

  PrintBitmapForm->ShowModal();

}

Listing 17.3. The header for the PaintBoxPrint module.

///////////////////////////////////////

// File: PaintBoxPrint.h

// Project: PrintText

// Copyright (c) 1997 by Charlie Calvert

//

#ifndef PaintBoxPrintH

#define PaintBoxPrintH

#include <vcl\Classes.hpp>


#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\ExtCtrls.hpp>

#include <vcl\Buttons.hpp>

#include <vcl\Dialogs.hpp>

class TPaintBoxForm : public TForm

{


__published:

  TPaintBox *PaintBox1;

  TBitBtn *PrintPictureBtn;

  TBitBtn *ShowPictureBtn;

  TPrintDialog *PrintDialog1;

  void __fastcall PrintPictureBtnClick(TObject *Sender);

private:

  void __fastcall ShowData(TCanvas *Canvas);

  void 
SendToPrinter();

public:

  __fastcall TPaintBoxForm(TComponent* Owner);

};

extern TPaintBoxForm *PaintBoxForm;

#endif



Listing 17.4. The main module for the PaintBoxPrint module.

///////////////////////////////////////

// File: PaintBoxPrint.cpp

// Project: PrintText

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#include <vcl\printers.hpp>


#pragma hdrstop

#include "PaintBoxPrint.h"

#pragma resource "*.dfm"

TPaintBoxForm *PaintBoxForm;

__fastcall TPaintBoxForm::TPaintBoxForm(TComponent* Owner)

: TForm(Owner)

{

}

void __fastcall TPaintBoxForm::ShowData(TCanvas 
*Canvas)

{

  Canvas->Brush->Color = clBlue;

  Canvas->Pen->Color = clYellow;

  Canvas->Rectangle(0, 0, PaintBox1->Width, PaintBox1->Height);

  Canvas->Font->Color = clYellow;

  Canvas->TextOut(5, 5, "Hi");

  
Canvas->Brush->Color = clPurple;

  Canvas->Ellipse(25, 25, 150, 150);

}

void TPaintBoxForm::SendToPrinter()

{

  if (PrintDialog1->Execute())

  {

    TPrinter *APrinter = Printer();

    APrinter->BeginDoc();

    
ShowData(APrinter->Canvas);

    APrinter->EndDoc();

  }

}

void __fastcall TPaintBoxForm::PrintPictureBtnClick(TObject *Sender)

{

  switch(dynamic_cast<TButton *>(Sender)->Tag)

  {

    case 0:

    {

      SendToPrinter();

      
break;

    }

    case 1:

    {

      ShowData(PaintBox1->Canvas);

      break;

    }

  }

}

Listing 17.5. The header for the PrintBitmap module.

///////////////////////////////////////

// File: PrintBmp.h

// Project: PrintText

// Copyright (c) 1997 by Charlie Calvert

//


#ifndef PrintBmp1H

#define PrintBmp1H

#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\ExtCtrls.hpp>

#include <vcl\Menus.hpp>

#include 
<vcl\Dialogs.hpp>

class TPrintBitmapForm : public TForm

{

__published:

  TImage *Image1;

  TMainMenu *MainMenu1;

  TMenuItem *File1;

  TMenuItem *Open1;

  TMenuItem *Print1;

  TMenuItem *N1;

  TMenuItem *Exit1;

  TOpenDialog 
*OpenDialog1;

  TPrintDialog *PrintDialog1;

  TMenuItem *Options1;

  TMenuItem *Stretch1;

  void __fastcall Open1Click(TObject *Sender);

  void __fastcall Print1Click(TObject *Sender);

  void __fastcall Exit1Click(TObject *Sender);

  void 
__fastcall Stretch1Click(TObject *Sender);

private:

public:

  __fastcall TPrintBitmapForm(TComponent* Owner);

};

extern TPrintBitmapForm *PrintBitmapForm;

#endif

Listing 17.6. The main form for the PrintBitmap module.

///////////////////////////////////////

// File: PrintBmp.cpp

// Project: PrintText

// Copyright (c) 
1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#include <vcl\printers.hpp>

#pragma hdrstop

#include "PrintBmp1.h"

#pragma resource "*.dfm"

TPrintBitmapForm *PrintBitmapForm;

__fastcall 
TPrintBitmapForm::TPrintBitmapForm(TComponent* Owner)

    : TForm(Owner)

{

}

void __fastcall TPrintBitmapForm::Open1Click(TObject *Sender)

{

  if (OpenDialog1->Execute())

  {

    
Image1->Picture->LoadFromFile(OpenDialog1->FileName);

  }

}

void __fastcall TPrintBitmapForm::Print1Click(TObject *Sender)

{

  if (PrintDialog1->Execute())

  {

    TPrinter *APrinter = Printer();

    APrinter->BeginDoc();

    
APrinter->Canvas->Draw(1, 1, Image1->Picture->Bitmap);

    APrinter->EndDoc();

  }

}

void __fastcall TPrintBitmapForm::Exit1Click(TObject *Sender)

{

  Close();

}

void __fastcall TPrintBitmapForm::Stretch1Click(TObject *Sender)


{

  Stretch1->Checked = !Stretch1->Checked;

  Image1->Stretch = Stretch1->Checked;

}

The main form of the program can print text, such as that shown in Figure 17.12, where you can see one of Shakespeare's sonnets. To send this text to the printer, you need to retrieve the printer object from the VCL. This object is declared in the Printers unit, so you must include that unit in your form. You can the write the following code:

void 
TForm1::SendToPrinter()

{

  TPrinter *APrinter = Printer();

  APrinter->BeginDoc();

  PrintText(APrinter->Canvas);

  APrinter->EndDoc();

}

This code first calls the Printer method to retrieve an object of type TPrinter. It then calls the BeginDoc method of the TPrinter object to start a document. When the printing task is done, you should call EndDoc.

The following method handles the actual printing chores:

void TForm1::PrintText(TCanvas *Canvas)

{

  int i, x;

  AnsiString S("Test String");

  x = Canvas->TextHeight(S);

  for (i = 0; i < Memo1->Lines->Count; i++)

  {

    S = 
Memo1->Lines->Strings[i];

    Canvas->TextOut(1, x * i, S);

  }

}

As you can see, this code starts at the beginning of the list of strings and iterates through them all, using the TPrinter Canvas object to print the current information. You can change the font, colors, and other aspects of the canvas in any way you like. Recall that in Chapter 7, "Graphics," I described how to use the Canvas object.

Printing Shapes to the Printer

One of the great advantages of the code shown in the PrintText method is that you can use it with any Canvas object. For instance, I could pass in the Canvas of the main form and then print the output to the main form, rather than to the printer.

The technique described in the last paragraph is what goes on in the form shown in Figure 17.13. This form provides the user with two buttons, one for printing information to a control on the current form and the other for printing information to a printer:

void __fastcall TPaintBoxForm::PrintPictureBtnClick(TObject *Sender)

{

  switch(dynamic_cast<TButton 
*>(Sender)->Tag)

  {

    case 0:

    {

      SendToPrinter();

      break;

    }

    case 1:

    {

      ShowData(PaintBox1->Canvas);

      break;

    }

  }

}

If you opt to send information to the printer, the following code is called:

void TPaintBoxForm::SendToPrinter()

{

  if (PrintDialog1->Execute())

  {

    TPrinter *APrinter = Printer();

    APrinter->BeginDoc();

    
ShowData(APrinter->Canvas);

    APrinter->EndDoc();

  }

}

This code first pops up a TPrintDialog and lets the user set up the printer, and switch into color printing mode, if necessary. A document is started, and the ShowData method is called:

void __fastcall TPaintBoxForm::ShowData(TCanvas *Canvas)

{

  Canvas->Brush->Color = clBlue;

  Canvas->Pen->Color = clYellow;

  Canvas->Rectangle(0, 0, 
PaintBox1->Width, PaintBox1->Height);

  Canvas->Font->Color = clYellow;

  Canvas->TextOut(5, 5, "Hi");

  Canvas->Brush->Color = clPurple;

  Canvas->Ellipse(25, 25, 150, 150);

}

As you can see, this method draws some text and a series of shapes into a canvas. If the canvas you pass to this program is for the main form or for a control placed on the main form, the output will appear there. If the canvas you pass in belongs to the printer, the output will be sent to the printer.


NOTE: One problem that I do not address in this code involves selecting the proper size and proportions for the current printer. As a rule, you will find that text or shapes that look okay on the screen will be far too small when shown on a printer. For hints on how to remedy the situation, view the code in Forms.pas that shows how to implement the poProportional and poPrintToFit options discussed earlier in this chapter. This code is almost all straight Windows API code, and will look virtually identical in C++ and Object Pascal.

Print Bitmaps

The final portion of the PrintGDI program that might be of interest to some users involves printing bitmaps. This sounds like it must be a complicated subject, but the VCL makes it easy.

The key to this process is using a TImage control on the form from which you want to print a bitmap. The following code can be used to load an image into that TImage control.

void __fastcall 
TPrintBitmapForm::Open1Click(TObject *Sender)

{

  if (OpenDialog1->Execute())

  {

    Image1->Picture->LoadFromFile(OpenDialog1->FileName);

  }

}

To print the image, just write the following code:

void __fastcall TPrintBitmapForm::Print1Click(TObject *Sender)

{

  if (PrintDialog1->Execute())

  {

    TPrinter *APrinter = Printer();

    APrinter->BeginDoc();

    APrinter->Canvas->Draw(1, 1, 
Image1->Picture->Bitmap);

    APrinter->EndDoc();

  }

}

This code pops up a TPrintDialog, grabs the printer, and begins a document. You can draw a bitmap on the TPrinter canvas by calling Draw and passing in the bitmap already loaded into the TImage component. The first two parameters passed to draw specify where you want the printing to begin as expressed in X, Y coordinates. The final step is to call EndDoc, which sends a form-feed to the printer.

That is all I'm going to say about the TPrinter class. There are more features of this object that you can explore via the online help or by opening up Printers.hpp. However, the basic facts outlined here should give you most of the information you need to get started with this object.

Printing Records in ReportSmith

ReportSmith is no longer an integrated part of the VCL family of products. However, I will touch on this subject briefly, just to give you a few clues about how to proceed with the tool if you need it. In particular, I will show how to create a report for the Address2 program.


NOTE: In the current shipping version of BCB, there aren't any TReportSmith or TReport components. However, I am sure that the people now making ReportSmith supply this component with the product. In the worst-case scenario, you can just use the Windows API command called WinExec to start ReportSmith and pass as a parameter the name of the report you want to run. There are also ways to pass macros to ReportSmith, and you can talk to the product using DDE.

To start creating a report, bring up ReportSmith using the Explorer and select the type of report you want to make, which is probably a label-based report. Go to the Tables page in the Report Query dialog, choose Add Table, and select ADDRESS.DB. ReportSmith understands BDE aliases, so you can use those to help you select a table.

Next, go to the Report Variables page and create a new variable called Filter. Set its type to String, its title to Filter List, and the prompt to "What filter do you want to use?". Set the entry to Type-in, as shown in Figure 17.15. When you are done, choose Add.

FIGURE 17.15. Creating report variables in ReportSmith.


NOTE: You do not have to choose Type-in as the entry method. In fact, the Address2 program is ideally suited for using the Choose from a Table method. After you select this method, a work space will appear in the bottom-right corner of the Report Variables page that enables you to choose a table and field that contain a list of available entries. In this case, you can choose the CATS.DB table and the Category field. Now when the user wants to run the report, he or she will be prompted with a list of valid categories and can choose the appropriate one, without the likelihood of an error being introduced. The copy of the ReportSmith report that ships with the CD for this book uses this method.

Turn to the Selections page, click on the yellow number 1 in the center of the page, and choose Select SQL selection criteria from the drop-down list. Select the Category field from the DataFields list box on the left and choose x=y from the Comparison Operators in the middle list box. Go back to the left-hand list box and change the combo box at the top so it reads Report Variables rather than Data Fields. Choose Filter and then set this variable in quotes:

`ADDRESSxDB'.'CATEGORY' = `<<Filter>>'

When you are done, the dialog should look like that in Figure 17.16. Now click the OK button at the bottom of the dialog.

FIGURE 17.16. Creating SQL selection criteria in ReportSmith.

The final step in this process is to create derived fields in the Derived Fields page of the Report Query dialog. The first derived field should combine the FName and LName fields, so you might want to call this field FirstLast. After typing in the name, select Add, and the Edit Derived Fields dialog box will appear. Select FName from the left column:

`ADDRESSxDB'.'FName'

Choose Addition from the middle column:

`ADDRESSxDB'.'FName' +

Add a space by writing the string ` `:

`ADDRESSxDB'.'FName' + ` `

Choose Addition again from the middle column:

`ADDRESSxDB'.'FName' + ` ` +

End by adding the LName field. The string you create should look like this:

`ADDRESSxDB'.'FName' + ` ` + `ADDRESSxDB'.'LName'

This statement combines the FName and LName fields so that they produce a single string out of a first and last name:

Kurt Weill

You should then create a second derived field called CityStateZip, which combines the City, State, and Zip fields:

`ADDRESSxDB'.'CITY' + `, ` + `ADDRESSxDB'.'STATE' + ` `  +

  `ADDRESSxDB'.'ZIP'

You have now created the logic behind a report, so choose Done from the bottom of the Report Query dialog. ReportSmith will then pop up a dialog to fill in the report variable you created. In other words, it's time for you to fill in the Filter portion of the following statement:

`ADDRESSxDB'.'CATEGORY' = 
`<<Filter>>'

You can type the word Family, Work, or whatever value you feel will return a reasonably sized dataset.

The Insert Field dialog now appears, and you can enter the fields and derived fields that you have created. The combo box at the top of the dialog enables you to switch back and forth between data fields and derived fields; you should do so when you think it's appropriate. For instance, the first field you select will probably be the derived field called FirstLast, whereas the second will probably be the data field called Address1. When you are done, the report you create should look something like the image shown in Figure 17.17.

FIGURE 17.17. A "live data" label report produced by ReportSmith.

Even the report shown here is probably not totally complete; you might want to rearrange the location of the fields inside each label and change the size and location of each individual label. You will find that you can move individual fields around by simply dragging them with the mouse. The actual decisions you make will be based on your personal tastes and on the type of labels you have in your office or home. When you are performing these tasks, you will probably find it easiest to do so by choosing the File | Page Setup menu option from the ReportSmith main menu.

If you want to change the fonts used in your report, select one of the fields in a label, right-click on the page, and choose Character from the pop-up menu. Other options, such as adjusting borders and field height and inserting pictures are available when you right-click a report.

When you are completely finished preparing a report, choose File | Save to save the report you have created under the name Address, in the same directory where the Address2 program resides.

As I stated previously, this brief tutorial on ReportSmith is not meant to be anything more than a very loosely structured primer. ReportSmith is very easy to use, and most of the actions you will perform are intuitive variations on the simple steps already outlined. However, you will probably also want to study the ReportSmith manuals, online help, and perhaps a third-party book dedicated to this subject.


NOTE: ReportSmith has a fairly sophisticated query builder in the Selections page of the Report Query dialog. On several occasions, I have actually abandoned BCB's query builder and opened up ReportSmith to construct a SQL statement. When I'm done, I just block-copy the SQL code from ReportSmith into the SQL property of my current Delphi TQuery object. This is a major kludge, but under some circumstances you might want to consider it as an option.

ReportSmith is a good tool that has the enormous advantage of being easy to use. In fact, ReportSmith is so easy to use that it turns the task of generating reports into something very similar to playing. As a result, you can quickly generate elegant-looking reports and then get back to the more interesting task of writing code.

Summary

In this chapter you have had an overview of printing in BCB. In particular, you have seen how to use QuickReport, the TPrinter object, and ReportSmith. You should remember that there might be updates available for QuickReport, and that another important third-party tool in the printing world is Seagate's Crystal Reports.

The point to grasp when reading this chapter is that BCB makes printing easy. Whether you are using TPrinter or QuickReport, you can output most information on a printer with only a few short minutes of work. If the printing task begins to look complicated, use a SQL query to get the information you need and then print the output of the query. This is the best course of action in terms of ease of use, and also in terms of creating an easy to maintain program.

As I said in the beginning, most people regard printing as a fairly boring chore. There is little in this chapter likely to change anyone's mind on this subject, but by now you should see that you have the tools necessary to quickly and easily generate powerful reports that users will love. If you take the time to learn how to use these tools, you can generate lots of reports easily. This is a skill you simply must have if you want to work in the client/server database world.

TOCBACKFORWARD

©Copyright, Macmillan Computer Publishing. All rights reserved.