BorlandTalk.com Forum Index BorlandTalk.com
Borland discussion newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Print the contents of TRichEdit in BDS 2006

 
Post new topic   Reply to topic    BorlandTalk.com Forum Index -> C++ Builder (VCL)
View previous topic :: View next topic  
Author Message
Bud1960
Guest





PostPosted: Fri May 19, 2006 2:14 pm    Post subject: Print the contents of TRichEdit in BDS 2006 Reply with quote



Hello,

I am need to be able to print the contents of a TRichEdit VCL control
using a TPrinterDialog control. I have the report formatted as I need
it printed and I just can't seem to make it work.

I do have to say that BCB 6's examples were either much easier to
access or BDS 2006's are, at best, limited.

Thanks in advance!
Bud
Back to top
Simon RF
Guest





PostPosted: Sat Jun 03, 2006 9:16 pm    Post subject: Re: Print the contents of TRichEdit in BDS 2006 Reply with quote



Bud1960 wrote:

Quote:
Hello,

I am need to be able to print the contents of a TRichEdit VCL control
using a TPrinterDialog control. I have the report formatted as I need
it printed and I just can't seem to make it work.

I do have to say that BCB 6's examples were either much easier to
access or BDS 2006's are, at best, limited.

Thanks in advance!
Bud

I found this, hope it's of help to you, I use TQuickPrint if I have to
do something like that..

Best of luck Simon RF

In Part 1 of this paper, "About TRichEdit::Print()," we saw that the
VCL does not give you very much control - in fact, essentially no
control - over how the
contents of a TRichEdit are printed on a page.

This part will cover printing Rich Edit controls in depth and supply
you with the details needed to print a Rich Edit control on a specific
portion of a page,
calculate the number of pages needed to print an entire document, and
print selected pages of a document. If you skipped Part 1, no problem.
You can start
here if you are already convinced that TRichEdit::Print() is not what
you need.

Part 3 of this paper, "Previewing Rich Edit Controls," will describe
creating a print preview window for Rich Edit controls. If you already
understand using
FORMATRANGE and EM_FORMATRANGE to print Rich Edit controls, you can
skip ahead to Part 3. Otherwise, I suggest you at least scan though
this
paper.

Rich Edit Printing Overview


To start with, Rich Edit controls print themselves or, more accurately,
they "render" or draw themselves. This means that you provide
information about:
The device to be rendered to (the printer or screen),
The device to use for formatting (typically the printer),
The area of the page on which to print, and
The range of text to be formatted.

Then you ask the control to render itself.

The basic process for rendering a Rich Edit control is:
Create and initialize a FORMATRANGE structure with handles for the
rendering and target devices, the size of the page, and the size of the
rendering
area. Initialize the CHARRANGE structure within the FORMATRANGE
structure with values that tell the Rich Edit control to render all of
the text in the
control.
Send an EM_FORMATRANGE message to the control and pass it the
FORMATRANGE structure. The control will render a page and return how
much of the requested range it was able to fit inside the rectangle.
The text offsets that correspond to the rendered range are returned in
the
CHARRANGE structure of the FORMATRANGE structure.
If not finished, adjust the requested range for the text
successfully rendered (or, more accurately, the text not rendered) and
repeat step (2).

The CHARRANGE and FORMATRANGE structures are going to become close
friends, so spend the time now to get to know them well.

The CHARRANGE and FORMATRANGE Structures


The CHARRANGE structure looks like this:

typedef struct _charrange {
LONG cpMin;
LONG cpMax;
} CHARRANGE;


Before starting the rendering loop (steps 2 and 3 above), you typically
set cpMin to 0 and cpMax to -1. While in the rendering loop, you
simply set cpMin to
the value returned by the EM_FORMATRANGE message (below).

The FORMATRANGE structure is declared as:

typedef struct _formatrange {
HDC hdc;
HDC hdcTarget;
RECT rc;
RECT rcPage;
CHARRANGE chrg;
} FORMATRANGE;


The structure's values are:

hdc - The handle to the device context that will be rendered (drawn)
on. Typically a printer or screen device context. This can also be a
user-created device
context if you need to draw on a bitmap in memory.

hdcTarget - Typically the handle to the device context for the printer.
May be set to zero if rendering and target DCs are identical, i.e., if
they are the same
DC. May also be set to zero if the formatting buffer is already
filled.

rc - A rectangle describing the area on the page to render in. The
text will be formatted to fit within the rectangle.

rcPage - A rectangle describing the entire "page" within which "rc" is
located.

chrg - A CHARRANGE structure as described above and used below.

As above, and for the remainder of this discussion, I will occasionally
refer to the hdc and hdcTarget parameters as the rendering and target
DCs, respectively.
Make no mistake - the drawing will always take place on the rendering
DC but will be based on (or look like) the target DC. Stated another
way, text will be
wrapped as if it was rendered to the target DC but will be drawn on the
rendering DC.

The EM_FORMATRANGE Message


The EM_FORMATRANGE message is sent, of course, with the SendMessage
WinAPI call. So, the SendMessage() call to render the Rich Edit
control call
is:


SendMessage(handle, EM_FORMATRANGE, (WPARAM) fRender,
(LPARAM) lpFmt);


The help text says:
fRender - Value specifying whether to render the text. If this
parameter is non-zero, the text is rendered. Otherwise, the text is
just measured.
lpFmt - Pointer to a FORMATRANGE structure containing information
about the output device, or NULL to free information cached by the
control.

fRender is a simple flag - if zero, nothing is drawn on the output
device; if non-zero, the output is actually rendered. Passing zero is
useful to calculate the
pages that would be printed. When you pass zero, the Rich Edit control
measures the size of the rendering rectangle that would be used and
returns this value
in the rc member of the FORMATRANGE structure.

lpFmt is the address of a FORMATRANGE structure as described above. If
this parameter is zero (or NULL), then the formatting buffer will be
cleared.
Any time this parameter is not zero, information about the page
rendered is stored in the formatting buffer.

The return value for the EM_FORMATRANGE message is the beginning offset
for the next page to be rendered.


An Example


Hopefully, an example will make this clear. The code is based on a
project that I have been working on for more than a year now. For this
paper, I have
extracted the pertinent code from the class, simplified it where
possible, and put it into event functions that respond to simple button
clicks. To follow along
with the example:

Create a new project.

Drop a TPanel on the form and set the Alignment property to alRight.
Make it just wide enough for a button or two.

Drop a TRichEdit on the form (not the TPanel) and set the Alignment
property to alClient. Next, and this is rather important, set the
TRichEdit font to a
TrueType font - I suggest that you start with Courier New.

Drop three buttons on the TPanel. Name the buttons PrintBtn,
PreviewBtn, and CloseBtn. Create OnClick event handlers for the
PrintBtn and CloseBtn
buttons. PreviewBtn is covered in part 3 of this paper, so forget
it for now.

Add the following code to the CloseBtn OnClick handler:

Close();

The example demonstrates the following general steps:

General setup and declarations/definitions.

Build a table or list of text ranges and other information that
correspond to each printed page.

Print each page.


The second step is not truly required if you are printing the entire
contents of the TRichEdit control. However, you will probably want to
advise the user of the
total number of pages that will be printed before actually printing or
allow the user to select a range of pages to be printed. To do that,
you will need to
pre-calculate the number of pages. Step 2 is included to demonstrate
this.

One purpose of this paper is to demonstrate how to print a TRichEdit
with arbitrary margins. However, for the purpose of the exercise, we
will assume that
the printer is loaded with paper that is the (US) standard 8.5" by 11"
size. Further, we will hard-code two-inch margins.

The example also assumes that we are printing to the default printer.
This printer is, in VCL-speak, Printer() and is an automatically
instantiated object of the
TPrinter class.

Obviously, a bit of work will be required to change these assumptions.
Other limitations or shortcomings are noted in the following
discussion.

Here we go.... First, add the following to the main form's *.cpp file
after the TForm* Form1 declaration (created by default) to include
printer declarations so
that we can use TPrinter.

#include <vcl\printers.hpp>

After that, add the following so that we can use the standard template
library (STL) vector class.

#include <vector>

Immediately after that, add the following code.

typedef struct tagTPageOffset {
long int Start;
long int End;
RECT rendRect;
} TPageOffsets;
The above structure that will be used later to store the information
that describes the starting and ending offsets and rendering rectangle
for each page that could
be rendered by the TRichEdit control. For now, just take my word for
it.

All of the above said and done, add an OnClick handler to PrintBtn.
All of the following code will be in this OnClick handler.

First, we get the size of a printed page (assuming 8.5" by 11") in
printer device units.

int wPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALWIDTH);
int hPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALHEIGHT);

Next, get the device units per inch for the printer.

int xPPI = ::GetDeviceCaps(Printer()->Handle, LOGPIXELSX);
int yPPI = ::GetDeviceCaps(Printer()->Handle, LOGPIXELSY);

Convert the page size from device units to twips.

int wTwips = ::MulDiv(wPage, 1440, xPPI);
int hTwips = ::MulDiv(hPage, 1440, yPPI);
Save the page size in twips.

RECT pageRect;
pageRect.left = pageRect.top = 0;
pageRect.right = wTwips;
pageRect.bottom = hTwips;
Next, calculate the size of the rendering rectangle in twips. Remember
- two-inch margins are hard-coded.

RECT rendRect;
rendRect.left = rendRect.top = 0;
rendRect.right = pageRect.right - 1440 * 4;
rendRect.bottom = pageRect.bottom - 1440 * 4;
Now we declare the STL vector class that will be used to save the
information about each page. This is used to to save a list of text
offsets that correspond to
printed pages as well as the values for a single page.

std::vector<TPageOffsets> FPageOffsets;

Define a single page and set the starting offset to zero.

TPageOffsets po;
po.Start = 0;
Define and initialize a TFormatRange structure. As described above,
this structure is passed to the TRichEdit with a request to format as
much text as will fit
on a page starting with the chrg.cpMin offset and ending with the
chrg.cpMax. Initially, we tell the Rich Edit control to start at the
beginning (cpMin = 0) and
print as much as possible (cpMax = -1) . We also tell it to render to
the printer.

TFormatRange fr;
fr.hdc = Printer()->Handle;
fr.hdcTarget = Printer()->Handle;
fr.chrg.cpMin = po.Start;
fr.chrg.cpMax = -1;

In order to recognize when the last page is rendered, we need to know
how much text is in the control. (For this example, we will use the
WM_GETTEXTLENGTH message. TRichEdit uses Rich Edit version 1.0 so this
is fine. However, if you are implementing the Rich Edit 2.0 control,
you will
need to use the EM_GETTEXTLENEX message.)

int lastOffset = ::SendMessage(RichEdit1->Handle,
WM_GETTEXTLENGTH, 0, 0);

As a precaution, clear the formatting buffer:

::SendMessage(RichEdit1->Handle, EM_FORMATRANGE, (WPARAM) 0,
(LPARAM) 0);

Printers frequently cannot print at the absolute top-left position on
the page. In other words, there is usually a minimum margin on each
edge of the page.
When rendering to the printer, Rich Edit controls adjust the top-left
corner of the rendering rectangle for the amount of the page that is
unprintable. Since we
are printing with two-inch margins, we are presumably already within
the printable portion of the physical page. We use the
SetViewportOrgEx() Windows
API call to move the origin of the printable page up and left by the
amount of the unprintable margins to undo the control's adjustment.
Notice that we first
save the device context settings with SaveDC().

::SaveDC(fr.hdc);
::SetMapMode(fr.hdc, MM_TEXT);
int xOffset = -::GetDeviceCaps(Printer()->Handle, PHYSICALOFFSETX);
int yOffset = -::GetDeviceCaps(Printer()->Handle, PHYSICALOFFSETY);
xOffset += ::MulDiv(1440 + 1440, xPPI, 1440);
yOffset += ::MulDiv(1440 + 1440, yPPI, 1440);
::SetViewportOrgEx(fr.hdc, xOffset, yOffset, NULL);

Now we build a table of page entries, one entry for each page that
would be printed. Before explaining, here is the code:

do {
fr.rc = rendRect;
fr.rcPage = pageRect;
po.Start = fr.chrg.cpMin;
fr.chrg.cpMin = ::SendMessage(RichEdit1->Handle,
EM_FORMATRANGE,
(WPARAM) 0, (LPARAM) &fr);
po.End = fr.chrg.cpMin - 1;
po.rendRect = fr.rc;
FPageOffsets.push_back(po);
} while (fr.chrg.cpMin != -1 && fr.chrg.cpMin < lastOffset);


There are a number of things to notice in the above code.

First, we are using the EM_FORMATRANGE message to tell the control to
render itself. However, the WPARAM value is set to zero to tell the
control to
simply measure the rectangle which would be rendered, but not to
actually draw anything on the device.

Next, note that we are saving the value returned in the rc member of
the FORMATRANGE structure. There are only glancing references to this
- fr.rc is
modified by the call. The actual size of the rendering rectangle is
returned here. The really odd thing is that the returned rectangle may
actually be larger than
the rendering rectangle passed into the call. I believe that this is
only an issue when the rendering and target device contexts are
different. They are the same
here, but in Part 3 of this paper they will be different.

Finally, the value returned by the SendMessage() call is one character
greater than the size of the text rendered. It is the beginning offset
for the next page.

At this point, FPageOffsets contains one entry for each page that would
have been rendered. You could use the following to determine the
actual number of
pages to be printed:

int pageCount = FPageOffsets.size();
For this exercise, we are printing only the first page so:

pageCount = 1;
Ok. We are ready to print. We are going to print to the default
printer, Printer(). Printer() returns a pointer to a global TPrinter
that is automatically
instantiated by the VCL.

Now there is a small problem with TPrinter. When we used
Printer()->Handle above, we did not get a handle to a device context.
What Printer()->Handle
really returned was a handle to an "information context," or an HIC.
Before drawing on the real printer device context (HDC), we must call
Printer()->BeginDoc(). However, after that call, Printer()->Handle
will actually return a true device context handle. That is, the prior
value returned by
Printer()->Handle is no longer valid. After we call
Printer()->EndDoc(), Printer()->Handle once again returns an HIC, but
it may not be the same HIC
returned before the Printer()->BeginDoc() call.

The moral of this story? Do not save TPrinter::Handle across
TPrinter::BeginDoc()/EndDoc() calls.

That said (and hopefully re-read until understood), we have to clear
the Rich Edit's formatting buffer because the HDC/HIC to which we will
be rendering has
changed. Let's go ahead and restore the HDC's settings, too.

::SendMessage(RichEdit1->Handle, EM_FORMATRANGE,
(WPARAM) 0, (LPARAM) 0);
::RestoreDC(fr.hdc, -1);

Now, we are almost ready to actually print. First, we tell the printer
that we are beginning to print, next we set the rendering and target
devices to the printer,
then we save the printer's HDC settings, and then we adjust the page
origins to account for the unprintable portion of the page.

Printer()->BeginDoc();
fr.hdc = Printer()->Handle;
fr.hdcTarget = Printer()->Handle;
::SaveDC(fr.hdc);
::SetViewportOrgEx(fr.hdc, xOffset, yOffset, NULL);

Ready to print? Ok, here we go....

bool firstPage = true;
int currPage = 0;
do {
if (firstPage) firstPage = false;
else Printer()->NewPage();

[ Bug Report ]

As presented, the second and subsequent pages are aligned to the top,
left corner of the page. Presumably, NewPage() resets the viewport
origin. To correct
this problem, add the following here (it may be deleted above):

::SetViewportOrgEx(fr.hdc, xOffset, yOffset, NULL);
The correction has been applied to the code sample from Part 4 of this
paper. It has not been added to the code samples available at the end
of Part 3 of this
paper.

Thanks to Jan verhave for chasing this one down and sharing it!

[ End Bug Report ]

fr.rc = FPageOffsets[currPage].rendRect;
fr.rcPage = pageRect;
fr.chrg.cpMin = FPageOffsets[currPage].Start;
fr.chrg.cpMax = FPageOffsets[currPage].End;
fr.chrg.cpMin = ::SendMessage(RichEdit1->Handle,
EM_FORMATRANGE,
(WPARAM) 1, (LPARAM) &fr);
} while (++currPage < pageCount);

Here is what happened above: We defined and initialized variables to
track the current page number and set a flag for the first page. Next,
we entered a loop
to print each page (remember that we are printing only one page if you
religiously followed the code). Within the loop, we started a new page
(if not printing
the first page) and set the formatting rectangle and beginning and
ending offsets to the saved values for the current page. Then we
actually printed the page
(SendMessage() with WPARAM not zero).

[ Begin Big Note ]

A diligent netizen, Mark Wigmore, pointed out that I elsewhere implied
that this series of papers would explain how to implement page headers
and/or footers.
In fact, the original paper did not explain that clearly. Here I hope
to remedy that oversight.

If you are not already familiar with drawing text on a TCanvas, now is
the time to look it up in the "Using C++ Builder" help. Spend some
time learning about
the Text* methods. The bottom line is that you can draw on
Printer()->Canvas using any of the TCanvas methods at any time between
the
Printer()->BeginDoc() and Printer()->EndDoc() calls.

The logical place to do this is in the do-while rendering loop since it
makes one pass for each printed page. The only real "trick" is to make
sure that the Rich
Edit rendering rectangle is below/above the header/footer respectively.
Well, the other trick is to understand that Rich Edit controls always
use twips for
measurements whereas TCanvas methods use the logical units for the
current mapping mode for the device context. Above, we set the printer
mapping mode
to MM_TEXT, which sets the logical units to device units. So, here you
will use printer device units (i.e., pixels; if your printer resolution
is 300 vertical pixels
per inch, then you have 300 device units per vertical inch).

Use the WinAPI GetDeviceCaps() function to get the printer pixels per
inch. Make the calls near the top of the rendering function (in this
case, the OnClick
handler). For example:

int printerHorzUnitsPerInch = ::GetDeviceCaps(Printer()->Handle,
LOGPIXELSX);
int printerVertUnitsPerInch = ::GetDeviceCaps(Printer()->Handle,
LOGPIXELSY);

gets the horizontal and vertical pixels per inch for the printer.

To add complexity (I always seem to do that), you may want to have a
first-page header that is different than the second and remaining page
headers. Assume
for the moment that you have set the top of the Rich Edit rendering
rectangle to one inch (1440 twips) and that you wish to have a header
one-half inch from
the top and left edges of the page. Using the above, you might re-code
the do-while loop to start like this:

do {
if (firstPage) {
firstPage = false;
Printer()->Canvas->TextOut(printerHorzUnitsPerInch / 2,

printerVertUnitsPerInch / 2,
"First page");
}
else {
Printer()->Canvas->TextOut(printerHorzUnitsPerInch / 2,

printerVertUnitsPerInch / 2,
"Second and following pages");
Printer()->NewPage();
}

Note that you are not limited to TCanvas::Text* functions. You can use
any TCanvas method to draw anything anywhere on the page. Naturally,
if you draw
something at a position on the page that will subsequently be drawn on
by the Rich Edit control, it will be obscured by the Rich Edit
control's drawing.

Of course, if you implement a page preview function as described in the
third part of this paper, you will need to add functionally similar
code to the preview
rendering method. When doing this, pay attention to the mapping mode.

One final note: The above code is "off-the-cuff" and untested and may
not be syntactically correct. Please let me know about any bugs!

[ End Big Note ]

At this point, we have finished rendering the contents of the Rich Edit
control. Now we restore the printer's HDC settings and tell Windows
that we are
through printing this document.

::RestoreDC(fr.hdc, -1);
Printer()->EndDoc();

Finally, we clear the Rich Edit control's formatting buffer and
delete the saved page table information.

::SendMessage(RichEdit1->Handle, EM_FORMATRANGE, (WPARAM) 0,
(LPARAM) 0);
FPageOffsets.erase(FPageOffsets.begin(), FPageOffsets.end());

That's it.
Back to top
Post new topic   Reply to topic    BorlandTalk.com Forum Index -> C++ Builder (VCL) All times are GMT
Page 1 of 1

 
 


Powered by phpBB © 2001, 2006 phpBB Group
.