![]() |
Desktop Development »
Printing »
General
Intermediate
Printing a Form in a report fashion (release 2.2)By sylvain2222Printing a Form in a report fashion |
C#, VB, Windows, .NET 1.1VS.NET2003, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

After many requests and many new features added to the initial version, here
is version 2.0 of FormPrinting. A lot of improvements have been made in this
totally rewritten version. The main one is complete management of controls
growing over many pages. That is multiline TextBox,
ListBox, DataGrid,... can grow depending of their
lines/items. See
below for changes.
Release 2.1 : property added for horizontal alignment.
Release 2.2 : bugs fixed. (1) Extension problem when top position of a control equal bottom position of above control that grow. This problem arrive when docking is used. (2) Little print error in multiline TextBox when text is less than one line.
FormPrinting is useful to produce a report directly from a form containing desired data. There are tools to print grids, trees, but I saw nothing to print a form. It's possible to do a print screen, but this gives a cheap looking report.
This tool prints any form in a manner that looks like a report. It create
pages by using a Graphics object to reproduce data appearing on the
form. Graphics object produced is print via a
PrintDocument or saved to a file as a multi-frame Tif image.
To use it, just call the print() or the
PrintToTifFile() function of the class, like this :
Dim fp As New FormPrinting(Me)
fp.Print() 'Print to printer
fp.PrintToTifFile("myTiffFileName") 'Print to tif file
By passing Me as a parameter of the constructor, the
class uses a recursive function to scan each control of the form. It prints them
in different formats depending on the type of the control. Some controls are not
printed, like buttons. For TabControl, only the selected tab is
printed. For example :

However, you can pass any type of Windows control to the constructor. For
example, you can send only a TapPage or a GroupBox. In
this case FormPrinting will use GroupBox.Text as report title and
will process only controls contained in the GroupBox . No need to
"print" the whole Form.
The class takes care of some control properties like Visible,
enabled or Horizontal alignment. But not all
properties are considered.
The following properties of the FormPrinting class can be set to
customize the result: (You can see here default values)
public bool TextBoxBoxed = false;
// box around TextBox controls
public bool TabControlBoxed = true;
// box around TabControl controls
public bool LabelInBold = true;
// Print all labels in bold
public bool PrintPreview = true;
// enabled Print preview instead of direct printing
public bool DisabledControlsInGray = false;
// Color for disabled controls
public bool PageNumbering = false;
//If true, reserve space at the bottom of
//each page and print page number
public string PageNumberingFormat = "Page {0}";
// String format for page number
public OrientationENum Orientation = OrientationENum.Automatic;
// choose print orientation (Automatic, Protrait or Landscape)
public ControlPrinting DelegatePrintingReportTitle;
// Function that will print report title
public Single TopMargin = 0;
//If 0, use default margin, else use this (1/100 inch)
public Single BottomMargin = 0;
//If 0, use default margin, else use this (1/100 inch)
It's possible to activate printing of customized controls that have a
text property without modifying the code. Just add a part of the
description of the type of the control by calling the function
AddTextBoxLikeControl(). Example:
fp.AddTextBoxLikeControl("DateTimeSlicker")
This a list of addition made in version 2.0. Most of them are discussed in this article :
TextBox multiline added
ListBox added
FlexGrid added (Grid from Component One : code in
comments only [no references] )
DataGrid printing
GetTrace() )
String.Format
Printing functions for each control type, including FormPrinting
report header, are called via a common delegate signature.
FormPrinting provide printing functions for most used controls.
However, you can provide your own personalized printing function to replace
internal one or to add printing of new type of control. To use you own function,
record it using the function AddDelegateToPrintControl like in this
VB sample :
fp.AddDelegateToPrintControl("FlexGrid", _
AddressOf FlexGridPrinting.PrintIt)
This will cause FormPrinting to call PrintIt()
function for controls with Type ending with "FlexGrid".
To replace the internal function used to print the title of the report,
assign yours to the public variable DelegatePrintingReportTitle :
fp.DelegatePrintingReportTitle = AddressOf MyOwnPrintReportTitle
Printing function must correspond to the following signature :
public delegate void ControlPrinting(
System.Windows.Forms.Control c,
ParentControlPrinting typePrint,
MultiPageManagement mp,
Single x, Single y,
ref Single extendedHeight, out bool ScanForChildControls);
In most case, set ScanForChildControls to false and you don't
have to worry about typePrint parameter. These are used for
controls containing children to be printed, like Panel and
TapPage . In their case, printing function are called before and
after printing of children. The reason is to adjust size according to growing
children like ListBox.
TextBox.Height control is 200 and it needs 250 to be printed,
return 50. Returned value must be 0 or more
MultiPageManagement ).
Use it to draw The MultiPageManagement class in FormPrinting take
automatically care of multi page functionality. It print unit in the right page.
Also, this class prevent for an element to be print over a page break.
Drawing must always be done in a print unit. If there is not enough space at
the bottom of the current page to print the unit, this one will be print at the
top of next page. Print units below it must be push down. This is handled by the
FormPrinting extension functionality. The
MultiPageManagement class don't change top or bottom margin to
provide space for a print unit. Instead, it return the height missing to print
unit at the bottom of the page. So this value is simply used by the controls
extension management of FormPrinting to push down controls below
it.
Print of more than one page is pretty tricky with PrintDocument.
Pages are printing one by one. So it's not possible to create and use many pages
at the same time. In other hand, it would be too complicated to
FormPrinting to remember position of all controls on each pages. To
bypass this problem, print engine in FormPrinting print the
document as many time as there is pages to print. At each pass, the
MultiPageManagement class print only the print units contained in
the current page. Pass #1 print page 1, Pass #2 print page 2, and so on. After
each pass, MultiPageManagement class returns a boolean
indicating if another page is needed. When all controls fit in current or
previous pages, job is finish.
This process also save bitmap memory usage when print to a Tif file. Each page use the same bitmap. Frames (pages) are added to the tif file at each pass.
Look at this code snippet demonstrating printing of an item in a
ListBox (lb) using MultiPageManagement class :
extendedHeight = mp.BeginPrintUnit(yItem, lb.ItemHeight);
mp.DrawString(lb.Text, printFont, _Brush, x, yItem,
lb.Width, lb.ItemHeight);
mp.EndPrintUnit();
The MultiPageManagement class also do page numbering.
I'm specially proud of this feature that works very fine. The code I wrote
for this is fairly short and robust at the same time. It take care of any
controls position below, above or side by side with another one. It also
calculate the new height of container controls like TabPage and
Panel.
The Combination of Control expansion, recursive printing and
MultiPageManagement class produce a clean result in almost any
case.
The basic logic is that a control is push down (Y position increased) to keep his vertical distance with the bottom of the nearest control above him.
Here is the only section of code that do all of this. It's in
PrintControls() function.
Note : "Controls" is an array of contained controls sorted by Y position. For
example, it can refer to all controls inside a Panel control.
// ************************************************************
// This loop over child controls calculate position of them.
// Algorithm take care of controls that expand besides and above.
// It keep an arraylist of original and printed (after expansion) bottom
// position of expanded control.
// So control is push down if it was originally below expanded controls
// *************************************************************
for (int i = 0; i < nbCtrl; i++)
{
// Set y position of control depending of extension of controls above him
Single pushDownHeight = 0;
foreach (Element e in extendedYPos)
if (controls[i].Location.Y > e.originalBottom) //completely under it
{
if (e.totalPushDown > pushDownHeight)
pushDownHeight = e.totalPushDown;
}
Single cp = controls[i].Location.Y + pushDownHeight;
Single extendedHeight;
PrintControl(controls[i], mp,
x + controls[i].Location.X, y + cp, out extendedHeight);
if (extendedHeight > 0)
{
//Keep extention with y position
Element e = new Element();
e.originalBottom = controls[i].Location.Y + controls[i].Height;
e.printedBottom = cp + controls[i].Height + extendedHeight;
extendedYPos.Add(e);
}
}
// same computation for bottom of container control. Its bottom line is
// below all child controls. So it is extended the same as the most pushed
// child control.
globalExtendedHeight = 0;
foreach (Element e in extendedYPos)
if (e.totalPushDown > globalExtendedHeight)
globalExtendedHeight = e.totalPushDown;
}
private class Element
{
public Single originalBottom;
public Single printedBottom;
public Single totalPushDown
{get {return printedBottom - originalBottom;} }
}
Some type of control was more difficult to format than other. In this section I explain how I solved some problems.
For multiline TextBox , the problem was to separate text
into lines that fit in the width of the control. This is necessary to print it
line by line, so that page break can be handled properly. I used
Graphics.MeasureString(). This method return the number of
characters that can be print for a font in a specific rectangle. I used a
rectangle corresponding to one line of the TextBox.
For ListBox, I only found one way to obtain the text of items. A
Text property return the text of the selected Item. So I save
selected position, and in the loop I change the selected index to get the text
of each items.
For DataGrid, cells content are private elements. So I get the
DataSource of the control. If a DataTable is found, it
is used to retrieve cells content. For column header caption and width, the
DataGridTableStyle object isn't directly accessible. The trick is
to create an instance of object DataGridTableStyle and link it to
the DataGrid. Then we can access column properties :
DataGridTableStyle myGridTableStyle;
if (dg.TableStyles.Count == 0)
{
myGridTableStyle = new DataGridTableStyle();
dg.TableStyles.Add(myGridTableStyle);
}
string caption = dg.TableStyles[0].GridColumnStyles[i].HeaderText;
Another job to do was juggling with alignment. For TextBox,
there are 3 values in Enum HorizontalAlignment. For Labels, there
are 9 values from ContentAlignment. And with the
graphic object, we use StringAlignment Enum, which
contain values Near, Far and Center.
These values must be converted before they can be used. For example, the
absolute value of Center in StringAlignment is not the
same that in HorizontalAlignment.
Drawing of controls is made via a Graphics object. This is the
tool provided in .NET to draw text, lines, square on an Image device. The class
MultiPageManagement in FormPrinting hold an instance
of a Graphics object revived for each page as a parameter of the
NewPage() function. For each operation, it simply calculate the
position in the current page of the object to draw, and then call the
appropriate drawing function. You can see this in the following sample of
function DrawRectangle() in MultiPageManagement.
Function _ConvertToPage() compute vertical position in the current
page of processed Print Unit . "_G" hold the Graphics object.
public void DrawRectangle(Pen pen, Single x,
Single y, Single w, Single h)
{
if (PrintUnitIsInCurrentPage())
{
Single yPage = _ConvertToPage(y);
_G.DrawRectangle(pen, x, yPage, w, h);
}
}
The Graphics object is linked to an Image device who receive the
painting of text, lines, square, ... Image device refer to a UI used as an
output media. It can be a Form on a screen, a printer page, a bitmap. Depending
if you want to print the report or to save it to a file, the main procedure of
FormPrinting create for each page the corresponding Image device,
attach it to the Graphics object, and pass it to the
MultiPageManagement class.
To save an image, a Bitmap is used. The Graphics
object is created with the Graphics.FromImage() method :
Graphics g = Graphics.FromImage(bitmapAllPages);
To obtain a multiframe image, bitmaps are added together with
Image.SaveAdd() method. To do this we need to set different values
in an Imaging.EncoderParameters.
To print the report, a PrintDocument is used. When the
Print method is called, a PrintPage event is triggered
for each pages. The handler received a printer page object already linked with a
Graphics object. At the end of each page, the handler just have to
set the HasMorePages property to true to continue with a new
page.
Not all properties are considered when printing controls. As an example,
FormPrinting doesn't look for the Font of separate elements inside
controls. ListView and other controls are not implemented.
The Demo Form contain many CheckBox that you can used to try
options. It contains too different kind of controls that can expand. Some
SpinBox let you change the number of items in ListBox
and in the DataGrid. Here is the function of test buttons :
The Demo also demonstrate how you can provide your own print function to the
class. I use this Form to test the FormPrinting class.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 Oct 2004 Editor: Nishant Sivakumar |
Copyright 2003 by sylvain2222 Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |