//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "PreviewForm.h"
//---------------------------------------------------------------------------
// include this...
#include <vcl\printers.hpp>
//---------------------------------------------------------------------------
// thanks to Scott Proper for finding several errors where the code
// used 1400 instead of the correct 1440.
//---------------------------------------------------------------------------
// standard template library is used for to build a vector (or table) of
// page offsets
//
/*
typedef struct tagTPageOffset {
long int Start;
long int End;
RECT rendRect;
} TPageOffsets;
*/
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TPreviewForm *PreviewForm;
//---------------------------------------------------------------------------
// custom panel onto which we can draw
//
class TPreviewPanel : public TPanel
{
public:
__fastcall virtual TPreviewPanel(Classes::TComponent* AOwner) :
TPanel(AOwner) { };
__fastcall virtual ~TPreviewPanel(void) { };
__fastcall virtual void Paint(void) {
TPanel::Paint();
PreviewForm->DrawRichEdit();
};
__property Canvas;
};
//---------------------------------------------------------------------------
__fastcall TPreviewForm::TPreviewForm(TComponent* Owner)
: TForm(Owner)
{
PreviewPanel = new TPreviewPanel(this);
PreviewPanel->Parent = Panel1;
PreviewPanel->Color = clWhite;
this->WindowState = wsMaximized;
currPage = 0;
}
//---------------------------------------------------------------------------
__fastcall TPreviewForm::~TPreviewForm(void)
{
if (PreviewPanel) delete PreviewPanel;
FPageOffsets.erase(FPageOffsets.begin(), FPageOffsets.end());
}
//---------------------------------------------------------------------------
// size the panel to dimensions that approximate the page scaled
// to fit within the window.
//
void __fastcall TPreviewForm::FormResize(TObject *Sender)
{
// get the printer dimensions.
int wPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALWIDTH);
int hPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALHEIGHT);
// get the client window dimensions.
int wClient = ClientWidth;
int hClient = ClientHeight;
// initially adjust width to match height
wClient = ::MulDiv(ClientHeight, wPage, hPage);
// if that doesn't fit, then do it the other way
if (wClient > ClientWidth) {
wClient = ClientWidth;
hClient = ::MulDiv(ClientWidth, hPage, wPage);
// center the page in the window
PreviewPanel->Top = (ClientHeight - hClient) >> 1; // divide by two
}
else // center the page in the window
PreviewPanel->Left = (ClientWidth - wClient) >> 1; // divide by two
// now set size of panel
PreviewPanel->Width = wClient;
PreviewPanel->Height = hClient;
}
//---------------------------------------------------------------------------
// sample code to demonstrate implementing a print preview window. the code,
// as written, uses hard-coded two-inch margins (as above) and displays only
// the first page. the goal of the example is to demonstrate rendering a
// a page of text, in this case the first page only, to a window. the
// window can be resized by the user and will be rescaled to approximate
// the printed page. to extend this into a useful class, you will need to
// add code and buttons (or other means) to move between pages. refer to the
// printing code in the main form for code that will, with a little insight
// and effort, help you in this effort. in particular, the printing code
// demonstrates how to build a table of offsets corresponding to the printed
// pages.
//
// here's an obvious tip: there is a considerable amount of duplicated code,
// between the printing function in the main form and the code in the
// following routine, that the savy programmer will consolidate into useful
// subroutines.
//
void TPreviewForm::DrawRichEdit(void)
{
//-----------------------------------------------------------------------
// *** same as printing version ***
//-----------------------------------------------------------------------
float leftMargin = StrToFloat(PrintForm->editLeft->Text),
rightMargin = StrToFloat(PrintForm->editRight->Text),
topMargin = StrToFloat(PrintForm->editTop->Text),
bottomMargin = StrToFloat(PrintForm->editBottom->Text);
int wPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALWIDTH);
int hPage = ::GetDeviceCaps(Printer()->Handle, PHYSICALHEIGHT);
int xPPI = ::GetDeviceCaps(Printer()->Handle, LOGPIXELSX);
int yPPI = ::GetDeviceCaps(Printer()->Handle, LOGPIXELSY);
int wTwips = ::MulDiv(wPage, 1440, xPPI);
int hTwips = ::MulDiv(hPage, 1440, yPPI);
RECT pageRect;
pageRect.left = pageRect.top = 0;
pageRect.right = wTwips;
pageRect.bottom = hTwips;
RECT rendRect;
rendRect.left = rendRect.top = 0;
rendRect.right = pageRect.right - 1440 * (leftMargin + rightMargin);
rendRect.bottom = pageRect.bottom - 1440 * (topMargin + bottomMargin);
TPageOffsets po;
po.Start = 0;
//-----------------------------------------------------------------------
// differences from the printing version start here (although, even
// the following has considerable similarity).
//-----------------------------------------------------------------------
// we will need a few DCs before it is over. note also that this
// function is called inside of the TPreviewPanel Paint() handler.
HDC hdcDesktop = ::GetWindowDC(::GetDesktopWindow());
HDC hdcCanvas = dynamic_cast<TPreviewPanel*>(PreviewPanel)->Canvas->Handle;
HDC hdcPrinter = Printer()->Handle;
FPageOffsets.erase(FPageOffsets.begin(), FPageOffsets.end());
// initialize the formatting data.
TFormatRange fr;
fr.hdc = hdcDesktop;
fr.hdcTarget = hdcPrinter;
fr.chrg.cpMin = po.Start;
fr.chrg.cpMax = -1;
// get the size of the text in the control (use EM_GETTEXTLENEX for RE 2.0).
int lastOffset = ::SendMessage(rtfPrint->Handle,
WM_GETTEXTLENGTH, 0, 0);
// clear the formatting buffer.
::SendMessage(rtfPrint->Handle, EM_FORMATRANGE,
(WPARAM) 0, (LPARAM) 0);
// save the canvas DC and prepare to scale.
SaveDC(hdcCanvas);
::SetMapMode(hdcCanvas, MM_TEXT); // set to device units
::SetMapMode(hdcCanvas, MM_ANISOTROPIC); // inherits prior mapping mode
::SetMapMode(hdcPrinter, MM_TEXT); // set to device units
// set window extent to width/height of printer in printer device units.
::SetWindowExtEx(hdcCanvas, pageRect.right, pageRect.bottom, NULL);
// scale window extent to width/height of printer in desktop device units.
int xDesktopPPI = ::GetDeviceCaps(hdcDesktop, LOGPIXELSX);
int yDesktopPPI = ::GetDeviceCaps(hdcDesktop, LOGPIXELSY);
::ScaleWindowExtEx(hdcCanvas, xDesktopPPI, 1440, yDesktopPPI, 1440, NULL);
// set viewport to width/height of PreviewPanel in device units.
::SetViewportExtEx(hdcCanvas, PreviewPanel->ClientWidth,
PreviewPanel->ClientHeight, NULL);
// now, here is one that I really do not get: we have to reduce the
// horizontal range a little for the text (wrap) to exactly match.
// this may be due to two things: (1) the font scaling that Windows
// to make screen fonts appear about the same size at a distance as
// the printed fonts do close up or (2) the adjustment to the rendering
// rectangle that Rich Edit may or may not do to adjust for the non-
// printable portion of the printed page. although (1) strikes me as
// perhaps more likely, the latter is easier to do and seems to work
// in other projects. if you figure this one out, please, please
// let me know. anyway, we will assume that the vertical adjustment
// is also required. oh, and we are working in twips.
int xPrinterOffset = ::MulDiv(::GetDeviceCaps(hdcPrinter, PHYSICALOFFSETX),
1440, xPPI);
int yPrinterOffset = ::MulDiv(::GetDeviceCaps(hdcPrinter, PHYSICALOFFSETY),
1440, yPPI);
rendRect.left += xPrinterOffset >> 1;
rendRect.right -= xPrinterOffset - (xPrinterOffset >> 1);
rendRect.top += yPrinterOffset >> 1;
rendRect.bottom -= yPrinterOffset - (yPrinterOffset >> 1);
// adjust output origin for two inch margin.
int xOffset = ::MulDiv(PreviewPanel->ClientWidth * leftMargin, 1440, pageRect.right);
int yOffset = ::MulDiv(PreviewPanel->ClientHeight * topMargin, 1440, pageRect.bottom);
::SetViewportOrgEx(hdcCanvas, xOffset, yOffset, NULL);
// loop through data to format and build page offset table.
do {
// set up remaining format data.
fr.rc = rendRect; // RE returns something useful here...
fr.rcPage = pageRect;
po.Start = fr.chrg.cpMin;
// format range with render set to false.
fr.chrg.cpMin = ::SendMessage(rtfPrint->Handle, EM_FORMATRANGE,
(WPARAM) 0, (LPARAM) &fr);
// I don't know why I need to do this, but there is an apparent bug in the TRXRichEdit control
// and it doesn't handle the last page properly if there are more than 3 pages of text.
if(fr.chrg.cpMin == po.End)
fr.chrg.cpMin = lastOffset;
po.End = fr.chrg.cpMin - 1;
po.rendRect = fr.rc; // and where is this documented?
if(fr.chrg.cpMin < lastOffset)
FPageOffsets.push_back(po);
} while (fr.chrg.cpMin != -1 && fr.chrg.cpMin < lastOffset);
// at this point, FPageOffsets.size() is the page count which will
// always be at least one. pageCount is not used here since we
// display only the first page.
// int pageCount = FPageOffsets.size();
// set the rendering device to the scaled DC and the target to null.
fr.hdc = hdcCanvas;
fr.hdcTarget = 0;
// set up remaining format data.
fr.rc = FPageOffsets[currPage].rendRect; // documented where?
fr.rcPage = pageRect;
fr.chrg.cpMin = FPageOffsets[currPage].Start;
fr.chrg.cpMax = FPageOffsets[currPage].End;
// format range with render set to true.
fr.chrg.cpMin = ::SendMessage(rtfPrint->Handle, EM_FORMATRANGE,
(WPARAM) 1, (LPARAM) &fr);
// clean up.
::RestoreDC(hdcCanvas, -1);
::ReleaseDC(::GetDesktopWindow(), hdcDesktop);
::SendMessage(rtfPrint->Handle, EM_FORMATRANGE,
(WPARAM) 0, (LPARAM) 0);
UpdatePageNums();
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::FormShow(TObject *Sender)
{
rtfPrint = PrintForm->rtfPrint;
//UpdatePageNums();
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::btnFwdClick(TObject *Sender)
{
if(currPage < (FPageOffsets.size() - 1)){
currPage++;
PreviewPanel->Repaint();
//UpdatePageNums();
}
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::btnBackClick(TObject *Sender)
{
if(currPage > 0){
currPage--;
PreviewPanel->Repaint();
//UpdatePageNums();
}
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::btnEndClick(TObject *Sender)
{
if(currPage != (FPageOffsets.size() - 1)){
currPage = (FPageOffsets.size() - 1);
PreviewPanel->Repaint();
//UpdatePageNums();
}
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::btnBeginClick(TObject *Sender)
{
if(currPage != 0){
currPage = 0;
PreviewPanel->Repaint();
//UpdatePageNums();
}
}
//---------------------------------------------------------------------------
void __fastcall TPreviewForm::btnCloseClick(TObject *Sender)
{
ModalResult = mrCancel;
}
//---------------------------------------------------------------------------
void TPreviewForm::UpdatePageNums()
{
AnsiString strPages = "Page ";
strPages += IntToStr(currPage + 1);
strPages += " of ";
strPages += IntToStr(FPageOffsets.size());
editPages->Text = strPages;
Caption = (AnsiString)"Print Preview " + strPages;
}
void __fastcall TPreviewForm::btnPrintClick(TObject *Sender)
{
ModalResult = mrOk;
}
//---------------------------------------------------------------------------