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