Click here to Skip to main content
Click here to Skip to main content

Printing a Form in a report fashion (release 2.2)

, 26 Oct 2004
Rate this:
Please Sign up or sign in to vote.
Printing a Form in a report fashion

Version 2.0 released!

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.

Update

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.

Introduction

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.

Using the code

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")

Improvements

This a list of addition made in version 2.0. Most of them are discussed in this article :

  • Version 2.0 is in C# (1.0 was written with VB.NET. Demo Form still in VB.NET)
  • Constructor accept control other than a Form
  • Can provide your own print functions for specific controls and for report title formatting
  • Controls can expand
  • Expandable TextBox multiline added
  • Expandable ListBox added
  • Expandable FlexGrid added (Grid from Component One : code in comments only [no references] )
  • DataGrid printing
  • More comments in source code
  • Report can continue on many pages
  • Can display a trace of controls printed (Call function GetTrace() )
  • Can print page number using your own String.Format
  • Report can be print to a multi-frame Tif file (example: for faxing)
  • Better algorithm to compute extension of container controls with height growing of side by side childs
  • Can set top and bottom margin
  • This article is elaborated more

Providing your own printing function

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.

  • c : control to draw
  • x,y : Top left printing position of control to draw (relative to parent control)
  • extendedHeight : growing height of the control if needed. For example, if a TextBox.Height control is 200 and it needs 250 to be printed, return 50. Returned value must be 0 or more
  • mp : the FormPrinting drawing manager (MultiPageManagement ). Use it to draw

MultiPage management

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.

Control growing

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;} }
}

Behind the scene of control printing

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

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.

Limitations

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.

Using the Demo

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 :

  • "Print Me" : Print the form
  • "Tif me" : Save report in a tif file
  • "Print Tab" : Print only the selected tab, not the whole Form
  • "Trace" : after printing, display a build-in trace of controls printed

The Demo also demonstrate how you can provide your own print function to the class. I use this Form to test the FormPrinting class.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

sylvain2222
Web Developer
Canada Canada
I'm programming for many years now, with many languages. One of my mind is to write to lowest number of source code line. I hate to copy and paste code. A good soft is one which need no comments to be understood.
 
Sylvain

Comments and Discussions

 
QuestionСпасибо за труды Pinmemberfarexg18-Nov-12 21:19 
Спасибо за труды
GeneralMy vote of 5 Pinmembermanoj kumar choubey19-Feb-12 19:50 
QuestionPrintToTifFile only 1 page Pinmemberunfolder0315-Jan-12 6:26 
AnswerRe: PrintToTifFile only 1 page Pinmembersylvain222215-Jan-12 13:29 
Generallistview update [modified] Pinmemberunfolder0314-Jan-12 6:02 
GeneralRe: listview update Pinmembersylvain222215-Jan-12 13:30 
Questiondatagridview and listview print [modified] Pinmemberxxmichaelxxx5-Dec-11 17:12 
AnswerRe: datagridview and listview print Pinmembersylvain22225-Dec-11 17:38 
GeneralRe: datagridview and listview print Pinmemberxxmichaelxxx5-Dec-11 17:58 
GeneralRe: datagridview and listview print PinmemberNGErndt28-Jun-13 9:58 
QuestionPrinting tabpages PinmemberMember 768745221-Nov-11 4:15 
AnswerRe: Printing tabpages Pinmembersylvain222221-Nov-11 12:04 
QuestionHow can we increase print dpi ? PinmemberAlaa Jebran26-Oct-10 0:20 
GeneralNICE PIECE OF ART PinmemberAlaa Jebran26-Oct-10 0:16 
GeneralRe: NICE PIECE OF ART Pinmembersylvain222228-Nov-10 8:34 
GeneralIncomplete text in labels PinmemberBFG3719-Aug-10 13:10 
GeneralRe: Incomplete text in labels PinmemberMCY7-Nov-10 8:00 
GeneralTHANK U FOR UR CODE Pinmemberchinpig5-Aug-10 21:00 
GeneralRe: THANK U FOR UR CODE Pinmembersylvain222228-Nov-10 8:35 
GeneralKindly help.... PinmemberMember 414876729-Oct-09 5:05 
Generalappreciation of this article Pinmembersekharbond25-Aug-09 20:29 
GeneralRe: appreciation of this article Pinmembersylvain222228-Nov-10 8:35 
GeneralHeader in datagrid PinmemberMember 413747711-Jun-09 15:57 
GeneralC or C++ Version Pinmembermlong308-Jan-09 5:42 
QuestionPrinting TableLayoutPanel PinmemberMember 415789222-Nov-08 4:26 
GeneralGraphics Pinmemberppro20-Sep-08 11:03 
GeneralScrollable form PinmemberMember 353809914-May-08 7:14 
GeneralRight To Left PinmemberWaleedH25-Apr-08 1:17 
QuestionHow to retrieve the printer's device context in C# PinmemberFaysal14-Apr-08 3:56 
Generaldisabling Multi Page PinmemberPeter Maslin2-Apr-08 22:27 
QuestionScreen Resolution &gt; 1024*768 PinmemberMember 271312127-Mar-08 9:08 
GeneralC# Pinmemberppro25-Jan-08 11:58 
Questionhow to set text alignment ? PinmemberN.SH12-Oct-07 2:30 
GeneralAbout the printing PinmemberRasheed Abdullah10-Oct-07 1:58 
QuestionDifferent Tabs Printed into Multiple Pages? PinmemberKevin Yap18-Jun-07 20:30 
QuestionCan I use this code in my project? Pinmemberkunal_gandhi5-Jun-07 19:19 
GeneralRe: Can I use this code in my project? Pinmemberxxxukrnet28-Jan-08 3:34 
QuestionRTF PinmemberBasil Ba-aziz8-May-07 3:21 
Questionincomplete text Pinmembercan38518-Apr-07 11:45 
AnswerRe: incomplete text Pinmemberjurc14-Jul-07 9:01 
GeneralRe: incomplete text Pinmemberchinpig5-Aug-10 22:37 
GeneralPrint to PDF PinmemberNatreen24-Mar-07 21:39 
Generalhey datagrid with sql Pinmembermakeafirstmove4-Mar-07 23:50 
GeneralRe: hey datagrid with sql Pinmembersylvain22225-Mar-07 5:27 
NewsA ton more enhancements PinmemberChris Wuestefeld15-Dec-06 8:29 
GeneralRe: A ton more enhancements Pinmembersylvain222217-Dec-06 18:48 
QuestionRolling in new enhancements? PinmemberChris Wuestefeld12-Dec-06 7:25 
AnswerRe: Rolling in new enhancements? Pinmembersylvain222212-Dec-06 9:20 
AnswerRe: Rolling in new enhancements? Pinmembersylvain222212-Dec-06 9:21 
GeneralScrolled portion of components (panels, flowtables...) Pinmemberfransua9-Nov-06 9:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 26 Oct 2004
Article Copyright 2003 by sylvain2222
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid