Board index » cppbuilder » A print question for Builder 5
Arlie Winters
![]() CBuilder Developer |
Arlie Winters
![]() CBuilder Developer |
A print question for Builder 52005-12-20 12:50:19 AM cppbuilder51 I have an ascii file that I'm loading into a RichEdit window. To print whats in the window I'm using: RichEdit1->Print("flt_price_est.txt"); Is there a way to print only so many lines (say 25) to the first sheet of paper, and then print the balance of the file on another sheet? I don't need to build a print menu for the user. Thanks Arlie |
JD
![]() CBuilder Developer |
2005-12-20 04:49:31 PM
Re:A print question for Builder 5
Arlie Winters < XXXX@XXXXX.COM >wrote:
Quote
home.att.net/~robertdunn/Yacs.html The first thing that comes to mind is to insert a page break into the TRichEdit using raw RTF coding. Just position the caret to where you want the page break to be and then insert it by sending the control the EM_STREAMIN message. For example (sample code by Gambit): char buffer[10] = {0}; strcpy(buffer, "\\page"); EDITSTREAM es; es.dwCookie = (DWORD)buffer; es.pfnCallback = (EDITSTREAMCALLBACK)EditStreamCallback; SendMessage(RichEdit1->Handle, EM_STREAMIN, SF_RTF | SFF_SELECTION, (LPARAM)&es ); DWORD CALLBACK EditStreamCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG FAR *pcb) { char *buffer = (char*)dwCookie; memcpy(pbBuff, buffer, strlen(buffer)); *pcb = strlen(buffer); return 0; } If you decide that you don't want to use TRichEdit's Print method, then you'll have to manage the printing yourself. Managing simple printing (just getting it to the paper) is easy enough but producing a nice looking document can be quite challanging - especially if you haven't printed much of anything prior. A very simple print function might look something like: int X = 0, Y = 0; Printer()->BeginDoc(); int CharacterHeight = Printer->Canvas->TextHeight( "Wg" ); for( int x = 0; x < RichEdit1->Lines->Count; ++x ) { Printer->Canvas->TextOut( X, Y * CharacterHeight, RichEdit1->Lines->Strings[x] ); } Printer()->EndDoc(); Two huge problems exist with this simple sample. The first is that it fails to perform any checking to see if the line is actually small enough to fit on one printed line. If it does not fit, the trailing letters will be truncated. The sample also fails to check if there is enough room at the bottom of the page to print another line. As in the first case, if there is not enough room, it just doesn't get printed. If you're going to be doing any printing (or working with graphics), you need to be able to work with a TCanvas. It has many properties and methods to make it easy to use and it works the same irrespective of if it's a TPrinter::Canvas or a TPanel::Canvas or a TBitmap::Canvas. All TCanvas's have a finite number of pixels organized in a 2D array. The upper/left pixel is always 0,0 and the width and height of the Canvas is determined by the control that it is associated with. For example, a TBitmap::Canvas is sized according to the TBitmap::Width and Height. For TPrinter, you can determined it's dimensions by reading it's PageWidth and PageHeight properties. So, anything that print that exceeds the Canvas's width or height will be clipped (not printed). This, along with TCanvas::TextWidth and TPrinter::NewPage is enough information so that you should be able to surmize how to fix the simple sample to produce a crude but reliable print function. You'll just have to figure out how you want to break the lines that are too long. Once you get the printed output, you'll notice that the text is not centered on the paper. The biggest issue with managing your own printing centers around the fact every different make and model of printer positions the Canvas on the paper differently. Once in a blue moon it's centered on the paper but it's almost always *not* centered and the area of the paper which does not fall within the Canvas is unprintable. There's also the issue of DPI which varies from printer to printer and if you want to center your text, you have to determine this as well as a few other settings with the printer. Then you do some math to calculate a new working rectangle for the TPrinter::Canvas instead using the Rect defined as (0, 0, Printer()->PageWidth, Printer()->PageHeight). This is the only way (without using a report builder) to get consistant output across all ranges of printers. The information that you need is not part of TPrinter so you have to use the win32 API GetDeviceCaps (look in the win32.hlp file for more details): // Standard user defined margines in inches double LMargine = 1.0; double TMargine = 1.25; double RMargine = 1.0; double BMargine = 1.25; // Get the printer pixels per inch XPPI = GetDeviceCaps( Printer()->Handle, LOGPIXELSX ); YPPI = GetDeviceCaps( Printer()->Handle, LOGPIXELSY ); // Get the printers total pixels XPixels = GetDeviceCaps( Printer()->Handle, PHYSICALWIDTH ); YPixels = GetDeviceCaps( Printer()->Handle, PHYSICALHEIGHT ); // Calculate the margines in printer pixels TRect Margines; Margines.left = ((double)LMargine * XPPI); Margines.top = ((double)TMargine * YPPI); Margines.right = ((double)RMargine * XPPI); Margines.bottom = ((double)BMargine * YPPI); // Determine the unprintable area of the paper TRect Offsets; Offsets.left = GetDeviceCaps( Printer()->Handle, PHYSICALOFFSETX ); Offsets.top = GetDeviceCaps( Printer()->Handle, PHYSICALOFFSETY ); Offsets.right = XPixels - (Printer()->PageWidth + Offsets.left); Offsets.bottom = YPixels - (Printer()->PageHeight + Offsets.top); // Confirm margines are within printable area if( Offsets.left>Margines.left ) Margines.left = Offsets.left; if( Offsets.top>Margines.top ) Margines.top = Offsets.top; if( Offsets.right < Margines.right ) Margines.right = Offsets.right; if( Offsets.bottom < Margines.bottom ) Margines.bottom = Offsets.bottom; At this point, the TRect Margines represents a Rect into the TPrinter::Canvas that is centered on the paper and takes into account the desired print margines so you can incorporate it into the simple sample. For example (assumes all line fit): int X = Margines.left, Y = Margines.top; Printer()->BeginDoc(); int CharacterHeight = Printer->Canvas->TextHeight( "Wg" ); for( int x = 0; x < RichEdit1->Lines->Count; ++x ) { if( (Y + CharacterHeight)>Margines.bottom ) { Printer()->NewPage(); Y = Margines.top; } Printer->Canvas->TextOut( X, Y, RichEdit1->Lines->Strings[x] ); Y += CharacterHeight; } Printer()->EndDoc(); I saved the issue of breaking a line for last because I'm not that familure with the internals of TRichEdit. It easy enough to know if the line will fit by using TextWidth. For example: if( Printer->Canvas->TextWidth(RichEdit1->Lines->Strings[x])>(Margines.right - Margines.left) ) { // it's too long so break it } but I have questions because I know how TMemo works with it's Lines. Even if there is no hard return, TMemo will break the line according to how it needs to be wrapped according to it's display. If TRichEdit does this as well, you're going to get a very messy looking printout because the line breaks will be based on the screen's DPI and the control's width. You're going to have to do some testing to determine exactly how TRichEdit treats hard returns and how it stores lines without them when they are wrapped on the display. Once you have determined that the line needs a break, you'll need a copy of the line so you can manipulate it. The crudest approach would be to start truncating the line a character at a time until it fits. Then you have to continue truncating until you reach a delimiter so that you can break on a whole word. Now you have a portion of the Line (to print as a single line) and you need to use that against another copy of the original line to arive at the remainder of the Line. Since the remainder of the Line may also need a break, this method has to be in a loop that is repeated until the remainder also fits. A more elegant solution is to use the win32 API DrawText. Specifically, look at DT_CALCRECT and DT_WORDBREAK but you won't be able to get away from having to code manually breaking the lines. This is because a line can span 2 pages and you can't see where Windows added the break. However, DrawText will also modify the string (if you tell it to by using DT_MODIFYSTRING) but it will append ellipses to the end. The end result is a modified string that fits but you'll have to truncate the ellipses, append from the original until you hit a delimiter and then resume truncating until you find where to break. The benifit of using DrawText is that manually breaking the line will add alot of overhead because of repeated calls to TextWidth. Using DrawText will allow you to not have to manually break the line unless you detect that the line spans multiple pages. In addition, DrawText will modify the string and get you very close without ever calling TextWidth once. Good luck. ~ JD |
JD
![]() CBuilder Developer |
2005-12-20 05:03:35 PM
Re:A print question for Builder 5
Hans Galema < XXXX@XXXXX.COM >wrote:
Quote
made it first <g>! My clock is correct and I don't know how the time is determined. For example, you posted: Tue, 20 Dec 2005 10:49:26 +0100 and I posted: 20 Dec 2005 01:49:31 -0700 Just the fact that the format differs leads me to believe that it's a server (aka ISP) issue and then there's the time zone thing ... I'm an hour different from Borland's server. Your guess is as good as mine. ~ JD {smallsort} |
Hans Galema
![]() CBuilder Developer |
2005-12-20 05:49:26 PM
Re:A print question for Builder 5
Arlie Winters wrote:
QuoteI have an ascii file that I'm loading into a RichEdit window. To print just print part of the file. Print() will print the whole file. QuoteIs there a way to print only so many lines (say 25) to the first sheet Hans. |
Hans Galema
![]() CBuilder Developer |
2005-12-20 05:51:51 PM
Re:A print question for Builder 5
JD wrote:
Is your clock off one hour ? I did not see your reply when I started to answer the post. Yet the time differs an hour. Hans. |
Hans Galema
![]() CBuilder Developer |
2005-12-20 06:27:14 PM
Re:A print question for Builder 5
JD wrote:
QuoteYou actually posted a couple of seconds before I did but mine QuoteMy clock is correct and I don't know how Quoteand I posted: So Greenwich Mean Time differs one hour. QuoteJust the fact that the format differs leads me to believe that Hans. |
Arlie Winters
![]() CBuilder Developer |
2005-12-20 11:50:59 PM
Re:A print question for Builder 5
Hans;
Thanks for putting so much time into a response. Your last suggestion of transferring into another TRichEdit "the lines that you actually want to print" is most appealing. The "LoadFromFile" grabs it all...how do I grab only a certain number of lines? Thanks Arlie Hans Galema wrote: QuoteArlie Winters wrote: |
Hans Galema
![]() CBuilder Developer |
2005-12-21 01:30:42 AM
Re:A print question for Builder 5
Arlie Winters wrote:
QuoteThanks for putting so much time into a response. Your last suggestion of Loop through ->Lines->Strings[] for as many lines as you want and add to the other with ->Lines->Add (); You suggested that the filecontents is plain text and not rtf. Hans. |
Hans Galema
![]() CBuilder Developer |
2005-12-21 01:33:59 AM
Re:A print question for Builder 5
Hans Galema wrote:
QuoteYou suggested that the filecontents is plain text and not rtf. from the end.). After a Print(), LoadFromFile() again the file and then delete the first 25 lines. Print(). Hans. |
Arlie Winters
![]() CBuilder Developer |
2005-12-24 12:42:20 AM
Re:A print question for Builder 5
Hans;
I understand the concept you're conveying "To print the first 25 lines ->Lines->Delete() all other lines", but I don't know the syntax to delete those other lines. If I could trouble you once more...it would give me something to build upon. Thanks Arlie Hans Galema wrote: QuoteHans Galema wrote: |
JD
![]() CBuilder Developer |
2005-12-24 01:57:52 AM
Re:A print question for Builder 5
Arlie Winters < XXXX@XXXXX.COM >wrote:
Quote
note that Lines is of type TStrings. Click that help jump and the help for using TStrings will come up with all of the methods and properties for manipulating a TStrings object. Just keep in mind that Lines is the name of the TStrings so you would use RichEdit1->Lines->SomeTStringMethod. Also think about what happens if you delete from a given point to the end. For example: for( int x = 27; x < Count; ++x ) delete string[x] That will only delete every other string starting at index 27 to the end because as you delete one string, the remaining strings fill in the gap. The way to do it is to start at the end and delete backwards. ~ JD |
Hans Galema
![]() CBuilder Developer |
2005-12-24 05:34:03 AM
Re:A print question for Builder 5
Arlie Winters wrote:
QuoteI understand the concept you're conveying "To print the first 25 lines There are RichEdit1->Lines->Count lines. So make a loop deleting all unwanted lines starting -as said- at the end. That's all. Hans. |
Arlie Winters
![]() CBuilder Developer |
2005-12-24 09:35:55 PM
Re:A print question for Builder 5
I see....
for( int x = RichEdit1->Lines->Count; x>47; x--) //while x is greater than the last line I want to print RichEdit1->Lines->Delete(x); //remove line x (always the last line in the file) RichEdit1->Print(""); //print all data in the RichEdit window This trashes everything past the 48th line, and allows me to print from [0] thru [48]. Thanks very much Arlie Hans Galema wrote: QuoteArlie Winters wrote: |
Arlie Winters
![]() CBuilder Developer |
2005-12-28 01:19:15 AM
Re:A print question for Builder 5
Ok, I've got the idea of removing lines inside of a RichEdit window. But when I
remove lines starting from the bottom of the window, I can't get the vertical scrollbar back up to the top. I've tried RichEdit1->SelStart = 0; but it does not work. Any help would be appreciated. Arlie Arlie Winters wrote: QuoteI see.... |
Arlie Winters
![]() CBuilder Developer |
2005-12-28 03:35:59 AM
Re:A print question for Builder 5
I can get the scrollbar up by clicking on it, but I'd like the page to return to the
position that it was in when the file was first opened. Arlie Winters wrote: QuoteOk, I've got the idea of removing lines inside of a RichEdit window. But when I |