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
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 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.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.
|