Introduction
to
MFC
Printing:
It's
common
knowledge
that
printing
is one
of the
hardest
things
for
properly
implementing
in your
Win32
program.
You have
probably
heard or
even
experienced
how hard
printing
is with
Win32
API. The
good
news is
that we
are
using
MFC
which
greatly
simplifies
this
task.
You get
print
preview,
standartized
dialogs(print
setup,
page
setup),
print
job
interruption
dialog
and OS
management
for
free. If
this
doesn't
look
much to
you just
have a
look at
the
MFC
source
files
involved
in
printing
and
print
preview.
Basically
with
MFC
you only
have to
add
drawing
code and
logic
for
paginating
the
document
if it
consists
of
multiple
pages.
Maybe
after
this you
wonder
what can
this
tutorial
offer to
you. The
problem
is that
MFC
hides
most of
the
functionality
in
members
of
various
classes.
This
tutorial
will
start
ith
basic
control
over the
process
and will
continue
with
more
advanced
modifications
of the
internal
structures
involved
in
printing.
I will
use a
technique
i call
"copy&modify
for your
needs".
For this
you will
need the
MFC
source
code.
You will
find
further
instructions
in the
examples.
Now
proceed
to Step
1.
Creating
the
MFC
Visual
C++
Printing
program:
-
For
this
tutorial
we
will
create
a
simple
program.
Start
Visual
C++
and
use
MFC
AppWizard(exe).
-
In
step
1
select
"Single
document".
Make
sure
the
check
box
is
selected.
-
Skip
step
2
and
in
step
3
you
can
disable
"ActiveX
Controls"
since
we
won't
use
them.
-
In
step
4
set
the
number
of
files
to 0
and
click
on
Advanced
and
delete
the
two
bottom
lines
(File
new
name(short)
and
(long)).
Otherwise
the
program
will
add
its
document
type
in
the
right
click
menu
in
"New->"
and
you
wouldn't
want
this.
Press
"Finish"
and
the
project
is
ready.
Printing
Functions
provided
by
MFC
in
Visual
C++:
The
program
has a
class
CTutorialView
with
some
member
functions.
The ones
involved
in
printing
are
OnPreparePrinting,
OnBeginPrinting,
OnEndPrinting,
OnPrepareDC,
OnDraw
and
OnPrint.
We
have two
options
for
printing:
1. To
use
OnDraw
paint
the
window
and
OnPrint
to print
or paint
in print
preview
mode.
Right
click
the
class
CTutorialView
and
select
"Add
Virtual
Function...".
Find the
function
OnPrint
and
press
"Add and
Edit".
You are
then
taken to
the
function
body and
you
should
see this
code
CView::OnPrint(pDC,
pInfo);
which
you may
safely
delete(or
better
add //
for
commenting
it).
It
is
possible
to make
output
both for
the
display
and
printer
in
OnDraw.
If you
want
this you
should
use
pDC->IsPrinting()
which
returns
TRUE if
printing
and
FALSE if
displaying.
You
don't
need
OnPrint
function.
Now
start
the
program
and
press
the
Print
button
in the
toolbar
or press
Ctrl+P.
You
should
see the
Print
dialog.
Note
that
Page
range is
set to
all and
the text
box next
to
"pages"
shows
1-65535.
This is
the
default
for
MFC
which
causes
it to
print
only one
page.
The page
range
can be
easily
changed
in
OnPreparePrinting
by using
pInfo.
Default
selection
and the
other
settings
are
harder
and
explained
in later
pInfo
contains
all the
data
related
to
printing.
It's
best to
look in
the help
for
"CPrintInfo"
where
you will
find all
the
information
about
member
variables.
For now
just
note
that you
can use
pInfo->SetMinPage
and
pInfo->SetMaxPage
to set
the
range.
Example:
use
pInfo->SetMaxPage(1);
to limit
your
printing
job to
exactly
1 page.
It is
possible
to know
how much
pages
your
application
needs.
If it
prints
out
something
really
small
you can
safely
assume
it needs
1 page.
If you
print
fixed-height
objects
like
lines of
text you
can get
the size
of the
page and
the text
and
calculate
how much
pages
you
need. If
your
program
is
printing
a highly
specific
thing
like a
640X480
bitmap
or four
20X15
inches
charts
you will
be able
to
easily
determine
what you
need.
Example
1: You
know
exactly
how much
pages
you need
BOOL
CTutorialView::OnPreparePrinting(CPrintInfo*
pInfo)
{
pInfo->SetMaxPage(6);
//
or
the
number
you
need
return
DoPreparePrinting(pInfo);
}
Example
2: You
don't
know the
how much
pages
you need
before
you get
the user
seletions
form the
"Print"
or
"Print
Setup"
dialogs.
void
CTutorialView::OnBeginPrinting(CDC*
pDC,
CPrintInfo*
pInfo)
{
int
nPageHeight=pDC->GetDeviceCaps(VERTRES);
int
nDocLength=GetDocument()->DocLength();
int
MaxPage=max(1,
(nDocLength+nPageHeight-1)/nPageHeight);
pInfo->SetMaxPage(nMaxPage);
}
Note: In
OnBeginPrinting
you have
a
pointer
to the
initialized
device
context
and you
can use
it to
get some
important
information
about
the
environment
like the
size of
the
selected
font.
This can
be used
to
determine
how much
can be
printed
on one
page.
GetDeviceCaps
is
explained
later.
For now
think
that
nPageHeight
is the
page
height
in
pixels
and
nDocLength
is the
document
size in
pixels.
You can
easily
modify
the code
so that
nPageHeight
is the
amount
of text
lines
printed
on a
page and
nDocLength
is the
total
amount
of lines
in the
document.
If
you
print
the
entire
document
contents
rather
than
printing
only the
current
page you
will
have to
implement
the
virtual
function
OnPrepareDC
which is
called
before
printing
every
page and
can be
used to
set the
viewport.
Otherwise
your
program
will
print
the
first
page
every
time
OnPrint(or
OnDraw)
is
called.
void
CTutorialView::OnPrepareDC(CDC*
pDC,
CPrintInfo*
pInfo)
{
CView::OnPrepareDC(pDC,
pInfo);
if(pDC->IsPrinting())
{
int
y=(pInfo->m_nCurPage-1)*m_nPageHeight;
pDC->SetViewportOrg(0,
-y);
//
remove
the
minus
sign
if
you
are
printing
in
MM_TEXT
}
}
Now the
printing
code
will
print
only the
appropriate
lines of
text.
The
printing
code
will
look
like
this:
for(int
a=0;a<numStrings;++a)
{
Cpoint
point(0,0);
//
start
point
for
drawing
pDC->TextOut(str[a],
point.x,
point.y);
point.y-=nHeight;
//
if
map
mode
is
MM_TEXT
change
this
to
+=
}
Note:
For big
documents
scrolling
in print
preview
will
work
slow
because
the
entire
document
is
printed.
If this
is a
problem
you
should
use code
like the
one
below.
If
you
don't
know how
much
pages
are
needed
you can
start
printing
and
determine
when to
stop
while
printing.
Example:
You have
an array
of
CString
objects.
OnPrint
could
look
like
this
(this is
only
part of
code and
won't
work by
itself):
// x
and
y
are
some
starting
positions
on x
and
y
axis
//
for
MM_TEXT
the
point
(0,0)
is
the
top
left
corner
of
the
screen
and
coordinates
increase
to
the
right
and
down
//
for
MM_HIMETRIC,
MM_LOMETRIC,
MM_HIENGLISH
and
MM_LOENGLISH
//
the
start
is
the
bottom
left
point
and
coordinates
decrease(become
negative)
upwards
and
increase
to
the
right.
//
for
MM_ISOTROPIC
and
MM_ANISOTROPIC
they
are
user-defined
CPoint(x,y);
//
get
the
current
page
number
(for
first
page
returns
1,
for
second
- 2
and
so
on)
nCurPage=pInfo->m_nCurPage;
//
get
the
index
of
the
first
CString
in
the
array
that
has
to
be
printed
in
this
page
nStartPos=(nCurPage-1)*linesPerPage;
//
calculate
the
index
of
the
last
CString
nEndPos=nStartPos+linesPerPage;
//
fill
a
TEXTMETRIC
struct
with
various
information
about
the
selected
font
TEXTMETRIC
tm;
pDC->GetTextMetrics(&tm);
int
nHeight=tm.tmHeight+tm.tmExternalLeading;
for(int
a=nStartPos;a<nEndPos
&&
a<numStrings;++a)
{
pDC->TextOut(str[a],
point.x,
point.y);
point.y-=nHeight;
//
if
map
mode
is
MM_TEXT
change
this
to
+=
}
if(a>=numStrings)
//
will
stop
printing
if
all
strings
are
printed
pInfo->m_bContinuePrinting=FALSE;
Printing:
There
are
several
ways to
print
your
document
but
first
you need
to know
something
about
mapping
modes.
Printers
have
fixed
physical
measures.
Most
printers
support
at least
600X600
dpi.
This
means a
printer
can
print
600
"pixels
in one
inch"
while a
monitor
has
something
like 10.
So if
you
print in
MM_TEXT
mode
where
one
logical
unit
means
one
pixel
the
display
might
work for
the
screen
but the
printed
images
will be
very
small or
invisible
at all.
So you
should
either
use some
of the
MM_[HI/LO][ENGLISH/METRIC]
map
modes:
MM_HIMETRIC:
Each
logical
unit is
converted
to 0.01
millimeter
MM_LOMETRIC:
Each
logical
unit is
converted
to 0.1
millimeter
MM_HIENGLISH:
Each
logical
unit is
converted
to 0.001
inches
MM_LOENGLISH:
Each
logical
unit is
converted
to 0.01
inches
These
are
usefull
when
printing
charts
and
tables:
MM_TWIPS
is
very
usefull
when
printing
with
text.
Each
logical
unit
is
converted
to
1/20
of a
point.
A
piont
is
the
unit
used
in
font
measuring.
The
size
which
you
select
in
Word
for
example
is
in
points.
Also
the
standard
"Select
font"
dialog
uses
this
measure.
-or-
Get
the
sizes
of
the
sheet
of
paper
and
size
your
output
according
to
it.
Here's
how
to
do
this:
//
GetDeviceCaps
can
give
much
important
information
about
the
display
device
int
horzsize=pDC->GetDeviceCaps(HORZSIZE);
//
gives
the
width
of
the
physical
display
in
millimeters
int
vertsize=pDC->GetDeviceCaps(VERTSIZE);
//
gives
the
height
of
the
physical
display
in
millimeters
int
horzres=pDC->GetDeviceCaps(HORZRES);
//
gives
the
height
of
the
physical
display
in
pixels
int
vertres=pDC->GetDeviceCaps(VERTRES);
//
gives
the
width
of
the
physical
display
in
pixels
int
hdps=horzres/horzsize;
//
calculate
the
horizontal
pixels
per
millimeter
int
vdps=vertres/vertsize;
//
calculate
the
verticalpixels
per
millimeter
//
note
1:
if
the
resolution
of
the
printer
is
600X600,
1200X1200
or
anything
***X***
hdps
will
be
equal
to
vdps
//
note
2:
multiply
hdps
and
vdps
by
2.54
to
receive
the
dpi
//
since
you
didn't
set
the
map
mode
it
is
still
MM_TEXT
//
now
when
calculating
sizes
in
millimeters
multiply
them
by
hdps
or
vdps
and
the
sizes
will
be
correct
CRect
rectDraw=pInfo->m_rectDraw;
//
this
assumes
the
page
is
A$,
the
printer
can
print
without
margins
//
(this
is
not
very
good
to
assume
but
will
work
for
now)
//
and
the
page
is
in
landscape
mode
(297mmx210mm)
CRect
rectOut(rectDraw.left,rectDraw.top,rectDraw.left+297*hdps,rectDraw.top+210*vdps);
//
now
print
only
inside
this
rectangle
...
Advanced
settings
-
MFC
Printing:
Maybe
your
printer
supports
many
types of
paper(A3,
A4, B3
,B4,
Envelope,
Letter,
etc.)
but you
want
your
program
to print
on a
certain
type.
You can
set the
printer
defaults
but
sometimes
the user
may need
the
defaults
or you
can't
set al
the
user's
settings.
In this
case it
is best
to set
the type
of paper
and
orientation
(portrait
or
landscape)
in your
program.
The
users
won't
have to
worry
about
setting
anything.
But this
is not
as
simple
as it
sounds.
The
pInfo
has a
member
m_pPD of
type
CPrintDialog*
which is
a
pointer
to the
printer
settings
dialog.
You can
use it
to make
changes
before
the user
opens
the
dialog
or when
starting
the
print
job. The
key is
m_pPD->m_pd
which is
PRINTDLG
struct(actually
it is a
pseudanim
but it
the same
for our
purposes).
It
contains
the
hDevMode
member
which is
a handle
to a
DEVMODE
data
structure
containing
information
about
the
device
initialization
and
environment
of a
printer.
Through
this
pInfo->m_pPD->m_pd.hDevMode
(The
path to
hDevMode)
handle
you gain
total
control
over the
printing
process
but
since it
is a
handle
you
can't
just set
it to
what you
want.
You have
to lock
the
memory
to it
and
access
it
instead.
But
before
the
DoPreparePrinting
function
the
handle
is not
set so
you
can't
access
the
data. If
you
access
it after
calling
DoPreparePrinting
the
changes
will
take
effect
at the
next
print
job. So
you have
to do
something
else. My
best
solution
was to
take the
code of
DoPreparePrinting
and
modify
it
according
to my
needs.
If
you want
to
control
the
defaults
when the
user
selects
Print
Setup
from
File
menu you
have to
associate
the
message
with
your
function.
Press
Ctrl+W
to open
ClassWizard.
Select
CtutorialApp
for
Class
name and
ID_FILE_PRINT_SETUP
for
Object
ID.
Associate
the
COMMAND
message
with a
function(the
default
is
OnFilePrintSetup).
Now add
this
code
instead
whatever
is
there:
CPrintDialog
pd(TRUE);
if
(GetPrinterDeviceDefaults(&pd.m_pd))
{
LPDEVMODE
dev
=
pd.GetDevMode();
GlobalUnlock(dev);
dev->dmOrientation=DMORIENT_LANDSCAPE;
dev->dmPaperSize=DMPAPER_A4;
}
DoPrintDialog(&pd);
The
default
function
does
only
this:
CPrintDialog
pd(TRUE);
DoPrintDialog(&pd);
The
function
has to
be a
CTutorialApp
member
because
DoPrintDialog
is only
accessible
from the
application
class
(try to
put this
code in
a member
function
of the
View or
MainFrame
and see
the
error
messages).
Sometimes
you will
want to
know if
the user
has
right
clicked
on a
document
and
selected
print
from
there.
There is
a
variable
cmdInfo
in
InitInstance.
It
contains
the
information
about
how is
the
program
started.
Unfortunately
it is
processed
by
ProcessShellCommand
which
you have
to copy
and
paste in
InitInstance.
Have in
mind
that
ProcessShellCommand
returns
a
boolean
value in
several
cases
and you
don?t
want
this to
happen
in
InitInstance.
Just
replace
all the
?return
?? lines
with
this
bResult=?
where
bResult
is a
boolean
variable.
Then
when the
copied
function
ends
just
decide
what to
do with
the
result
you have
in
bResult
(it will
be the
same as
if you
have
called
the
function).
Now back
to
ProcessShellCommand.
It has a
case
block
and one
of the
cases is
this:
case
CCommandLineInfo::FilePrintTo:
case
CCommandLineInfo::FilePrint:
Just
add your
code
before
or after
the
original
depending
on your
needs
It?s
always
best to
run the
code
step by
step in
the
debugger.
Thus you
can see
which
part of
code is
executed
currently
and the
order of
called
functions
ending
with the
current(call
stack).
Example:
You want
to see
what and
when
happens
in
OnPreparePrinting.
The way
to check
is to
set a
breakpoint
at the
beginning
of the
function
body
after
the
opening
curly
bracket
( ?{? ),
start
the
program
in the
debugger
by
pressing
F5(you
will
have to
build in
Win32
Debug
mode.
Then
when the
program
stops in
the
function
use
?Step
Into? to
go to
the
desired
function
and copy
its
source.
Note: If
you add
a
breakpoint
in some
functions
like the
ones
involved
in
printing
the
program
will
break
only if
you
start
printing/open
the
print
setup
dialog.