|  | Click Here!

Search the site:
EXPERT SEARCH

| Programming Languages
| Databases
| Security
| Web Services
| Network Services
| Middleware
| Components
| Operating Systems
| User Interfaces
| Groupware & Collaboration
| Content Management
| Productivity Applications
| Hardware
| Fun & Games

EarthWeb sites
Crossnodes
Datamation
Developer.com
DICE
EarthWeb.com
EarthWeb Direct
ERP Hub
Gamelan
GoCertify.com
HTMLGoodies
Intranet Journal
IT Knowledge
IT Library
JavaGoodies
JARS
JavaScripts.com
open source IT
RoadCoders
Y2K Info

| ## 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: Printing data from databases via QuickReport Printing QuickReport data in multiple columns and rows Printing QuickReport data by group Printing QuickReport data in one-to-many relationships Using the `TPrinter` object to print a graphic image of a form exactly as the form appears to the user at runtime Printing via the GDI, so that you can duplicate, fonts, pictures, and shapes drawn through the GDI Using ReportSmith 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:

- How to print rows of data

- How to print columns of data

- How to group data

- How to create a one-to-many report

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 `TQRBand`s 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:

- The reports for the flat file Address2 program illustrate the basic facts about using QuickReport, including grouping.

- The reports for the relational kdAdds and Music program show how to create master detail reports, and how to write custom queries that make it easy to create reports.

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:

- If you see only one record when you are expecting to see many, check to see if you have set the `DataSource` field of the `TQuickReport` control to the correct data set.

- If one of the fields is blank, check its `DataSource` and `DataField` properties.

#### 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.

©Copyright, Macmillan Computer Publishing. All rights reserved.

| Use of this site is subject certain Terms & Conditions. Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.