RSS

Tag Archives: Printing

WPF Printing Library v2.0

This project was moved to GitHub.

Introduction

Please refer to the first version of this library WPF Printing Library v1.0

What can be done until now using this library?

  1. Printing footer/header/recipient on every single page
    1. Decide whether the footer is print only on the last page
    2. Decide whether the header is print on every page, but not on the last page
  2. Printing the page count on every page
  3. Decide what has to be printed: e.g. header and no footer, no page count
  4. Print a new item on a new page if there is not enough page available
  5. Printing a customizable summary of all items at the end of the report

Resolved drawbacks from v1.0

  1. If an item is longer than the available space for printing items you will get an infinite loop.
  2. According standards of WPF, that a control is created only in the main thread, you cannot print in background(I will check that and try to fix it) Not possible
  3. Impossible to print to OneNote and XPS.
  4. Until now not possible to decide whether the footer is printed only on the last page according to that you also cannot decide whether a header is printed only on the first page.
  5. Only compatible with WPF (I will not change that).

Class Library

PrintAppendixes

The PrintAppendixes is a flag-enum that defines the parts that have to be printed. Here you can set up if you want to print only the page footer, page header, footer and header or all other possible combinations of all values. As you certainly know, flag enums can be combined. That means you can select only one value but also more of them by applying it with the binary operator |. PrintAppendixes pa = PrintAppendixes.Header | PrintAppendixes.Footer; This enum is used for the PrintOnPage attribute that has to be added on the PrintProcessor class.

IPrintPartDefinition

I have decided to remove the definition of the print parts that have to be printed from the PrintDimension constructor. The reason for that was, because there you could set the PrintAppendixes only once and this will hold for the whole document. I thought that it would be useful to define for every page whether the PrintAppendix should be print or not. Therefore I created an Attribute hierarchy that enables this. It is pretty cool and simple. Just set an attribute of your choice on your implementation of the PrintProcessor class. You have four types of attributes available:

  • PrintOnPage
  • PrintOnAllPages
  • ExcludeFromPage
  • ExcludeFromAllPages

I think the names are self-explanatory. Example: You want to print the footer only on the last page, but the header on every page except the last one. You achieve this by defining such a PrintProcessor class:

[PrintOnAllPages(PrintAppendixes.Header)]
[ExcludeFromPage(PrintAppendixes. Header, PrintPartDefinition.LastPage)]
[PrintOnPage(PrintAppendixes.Footer, PrintPartDefinition.LastPage)]
public abstract class TestPrintProcessor : PrintProcessor {}

That is all you need to do. Cool isn’t it? But what is happening if I define following attributes:

PrintOnAllPages(PrintAppendixes.Footer)]
[ExcludeFromPage(PrintAppendixes.Footer,1,4,5)]

If you write such a print part definition, that the footer gets only printed on page 2, 3, 6… If for a single page the ExcludeFrom and PrintOn is defined, than this print part will not be printed on this page. Now you might think that this is pretty cool, but it is very static and needs to be defined during coding. That’s right for the attribute aspect. But I have created an additional property PrintDefinition in the PrintProcessor class on which you can call the SetPrintAttribute(IPrintPartDefinition definition) method. Now you can change when to print which print part at any time.

PrintProcessor

The PrintProcessor class is the class of mayor interest. It is an abstract class based on the interface IPrintProcessor and provides all necessary methods and properties for creating a cool print with custom headers, footers etc. In order to do that you should override the corresponding methods (GetFooter, GetHeader,…). Note: If you define a header on page 2 and you do not provide a value for GetHeader() an exception gets raised. Not providing a header means that you do either return null or that you have not overwritten the method GetHeader().

Alternating the rows

An additional feature is to color the rows as you desire. To do that you can set a list of Brushes to the AlternatingRowColors property. But this is not enough: you have to enable the alternating coloring through the property IsAlternatingRowColor. If you just set IsAlternatingRowColor to true but do not set a value to AlternatingRowColors the default values, transparent and light gray, were used.

Break last item on new page

As you continue in printing it might be sometimes the case that you want to print the footer only on the last page. So it can be that all elements have been printed, but the footer does not fit on the last page. So we need a page break only to fit the footer on the last page. So we have the situation that on the last page the footer (and other PrintAppendixes) is printed. In order to avoid such an awkward situation you can set the property BreakLastItemIfLastPageWouldBeEmpty to.

Print result is not what you wanted

A very cool feature, for debugging reasons only is that you can enable coloring the print parts by setting ColorPrintPartsForDebug to true. This will color all the PrintAppendixes in such a way that you can see where space for a too large element is wasted. Demo ColorPrintPartsForDebug

Bulk print

Beginning from version 2.0 you can bulk print. This method takes a list of PrintProcessors that gets printed all at once.

Print direct to printer

Also a new feature for this version is that you can start printing and choose the printer from the print dialog, but also by direct printing to the printer. This is achieved by calling one overload of the Print method. You have two overloads available:

  • PrintDocument(printQueueName)
printer.PrintDocument("PDF-XChange Printer 2012");
  • PrintDocument(printQueueName, PrintServer)

This overload is used to print to a LocalPrintServer and set some settings to it.

PrintDimension

The next class that is of interest is the PrintDimension class. It provides all necessary information to paginate the document.

Here you can control the height that is reserved for each print part if it gets printed on the page. In addition I suggest to add some properties for the column width for the IPrintContent.Content panel. This is very useful because in many times this content panel is in tabular form. Change from v1.0: You cannot set the PrintAppendix in this class anymore. Instead you set it as attribute on the PrintProcessor class. The default contstructor initializes the Margin to {0,0,0,0}

Relative column position

Facts that your page size may vary from print to print you want stretch or shrink the print. In version 1.0 you set for every column in the IPrintContent.Content panel the width for each column. By setting UseRelativeColumnPosition to true you enable that the columns gets shrink a stretched as they have space. To define the width for a column you simply derive from PrintDimension and create some properties with a ColumnDimensionAttribute on it. The constructor takes a double representing the percentage that a column may fill up. As second parameter you can passeither ColumnDimensionType.Absolute or ColumnDimensionType.Relative relative. These two values are for defining whether the double value is an absolute width value for the column or like in the exaple below 50% of the page width. Example:

ColumnWidthAttribute

If you want to achieve columns that has exactly that width, than you write:

public class CustomPrintDimension: PrintDimension
{
    public CustomPrintDimension()
    {
        UseRelativeColumnPosition = true;
    }

    [ColumnDimension(.50,ColumnDimensionType.Relative)]
    public double WidthColumn1 { get; set; }

    [ColumnDimension(.15,ColumnDimensionType.Relative)]
    public double WidthColumn2 { get; set; }

    [ColumnDimension(.35,ColumnDimensionType.Relative)]
    public double WidthColumn3 { get; set; }
}

//usage

public override UIElement GetTable(out double tableHeaderHeight, out Brush borderBrush)
{
    Grid g = new Grid();
    borderBrush = Brushes.Gray;
   //for _printDimension.WidthColumn1 you will a value that repesents 50%
    //of the width of the page according the page orientation an page size.
    g.ColumnDefinitions.Add(new ColumnDefinition
        {Width = new GridLength(_printDimensions.WidthColumn1)});
    g.ColumnDefinitions.Add(new ColumnDefinition
       { Width = new GridLength(_printDimensions.WidthColumn2) });
    g.ColumnDefinitions.Add(new ColumnDefinition
        { Width = new GridLength(_printDimensions.WidthColumn3) });

Note: If you change the PageOrientation in your PrintProcessor class from Portrait, which is the default value, to Landscape you will get bigger values for WidthColumn1,WidthColumn2 and WidthColumn3 because the width of the printable area changed.

IPrintContent

This interface was refactored from ILineItem. It provides a single property only. With that property you can provide the content of the line. Unlike in the previous version you do not have to provide the height of the line. This gets computed automatically. NOTE: If the content is bigger than the remaining space of a page the element gets positioned automatically on a new page no matter if it has space or not.

public class TestPrintContent : IPrintContent
{
    private readonly int _item;
    public TestPrintContent(int item)
    {
        _item = item;
    }
    public Panel Content
    {
        get
        {
            var sp = new Grid();
            var textBlock = new TextBlock
                {Text = _item.ToString(),};
            sp.Children.Add(textBlock);
            return sp;
        }
    }
}

If you want to specify exactly the height of the content you can do this as follows:

public Panel Content
{
    get
    {
        var sp = new Grid();
        sp.Backgroud = Brushes.Red; //only to make the grid visible.
        sp.Height = 150;
        return sp;
    }
}

PrintProcessorBackground

As you might have seen in the PrintProcessor class there is a GetBackgroud() method that provides the background of the document. The class provides only methods for positioning the background on the document and the background element, of that the opacity can be set.

Sample C# project

You can download a complete working “Visual Studio 2012 project” that demonstrates my printing library by using this link: (Click here to download the sample project.). NOTE: please change the file format of the downloading document from .docx to .zip

Advertisements
 
35 Comments

Posted by on March 6, 2013 in Printing, WPF

 

Tags: ,

Printing in WPF

NOTE: I have released a new version of this library. Please follow this link: WPF Printing Library v2.0

Printing? All of my programmer friends hate writing code that does printing. It is quite complicated to write this. Thinking of problems about aligning, font size, pagination is quite fraught.
In WPF printing is a bit easier, you can print everything you see on the screen doing following:

<StackPanel Name="printingStackPanel>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="20"
             VerticalAlignment="Center" Foreground="White"
             HorizontalAlignment="Center" Text="{Binding Path=HsNummer.Benennung}"
             DockPanel.Dock="Right">
    <TextBlock.TextDecorations>
<TextDecoration PenThicknessUnit="FontRecommended">
        <TextDecoration.Pen>
          <Pen Thickness="1.5">
            <Pen.Brush>
<LinearGradientBrush Opacity="0.5" StartPoint="0,0.5" EndPoint="1,0.5">
                <LinearGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0" />
<GradientStop Color="Lime" Offset="0.5" />
<GradientStop Color="White" Offset="1" />
                </LinearGradientBrush.GradientStops>
              </LinearGradientBrush>
            </Pen.Brush>
          </Pen>
        </TextDecoration.Pen>
      </TextDecoration>
    </TextBlock.TextDecorations>
  </TextBlock>
  <Button Content=This is my Button/>
</StackPanel>

Printing this piece of XAML is done following:

private void PrintClick(object sender, RoutedEventArgs e)
{
PrintDialog dialog = new PrintDialog();
    if (dialog.ShowDialog() == true)
    {
dialog.PrintVisual(printingStackPanel "printingStackPanel");
    }
}

So this is the easy way of printing. A slightly more difficult way of printing is, if you want to add on each page a header, footer, some details that are only visible on the first or last page or if you want to add page count numbers, etc.
For this reason I began to implement a printing library that will work only referencing the 3 standard WPF Libraries:

  1. PresentationFramework
  2. PresentationCore
  3. WindowsBase

The main parts used for printing can be coded using plain C# or using WPF. It does not matter what kind is used. In this tutorial I will especially focus on the plain C# method, because this is the one that I mainly use.

First of all, I will answer the question why I have developed such a library!

The reason is, because in the company I work in, I had to develop an administrative program, including printing of invoices and delivery reports. Now you can maybe imagine what the major problem was. The invoice may contain a very large amount of items such that they do not fit on one DIN-A4. Therefore all other items have to be printed on a new page. But what is the problem? Using plain XAML and embedding all items in a ListBox and printing it I risk the problem of truncating an item in the middle. The other problem is that you cannot specify any footer nor any header. Well, the header can printed on the first page, I agree, and the footer on the bottom of the last page, but what if you want to print a footer and/or header on every single page? That is not possible.

So, that is the reason I decided to do what I have done.

What can be done until now using this library?

  1. Printing footer/header/recipient on every single page
  2. Printing the page count on every page
  3. Decide what has to be printed: e.g. header and no footer, no page count
  4. Print a new item on a new page if there is not enough page available
  5. Printing a customizable summary of all items at the end of the report

What are the drawbacks?

  1. If an item is longer than the available space for printing items you will get an infinite loop.
  2. According standards of WPF, that a control is created only in the main thread, you cannot print in background(I will check that and try to fix it)
  3. Impossible to print to OneNote and XPS.
  4. Until now not possible to decide whether the footer is printed only on the last page according to that you also cannot decide whether a header is printed only on the first page. (It’s just on my To-do list)
  5. ONLY WPF COMPATIBLE (I WILL NOT CHANGE THAT)

I want to check and fix these drawbacks in the near future if I have time available.

Class Library

Actually the library consists of 7 classes, only 6 of public interest. The remaining one is the main class that coordinates the whole printing progress and has no public accessors.
The class of your major interest ist the PrintProcessor. But for better understanding how this class will work I will explain the PrintDimension and the enum PrintAppendixes earlier.

PrintAppendixes

The PrintAppendixes is a flag-enum that defines the parts that have to be printed. Here you can set up if you want to print only the page footer, page header, footer and header or all other possible combinations of all values.
As you certainly know, flag enums can be joined. That means you can select only one value but also more of them by applying it with the binary operator |.

<span >e.g.
PrintAppendixes pa = PrintAppendixes.Header | PrintAppendixes.Footer;

In that example you have to set the PrintAppendix in such a way that the printer will print additional to the page data also the header and the footer of the page.

PrintDimension

The next class that is of interest, ist the PrintDimension class. It provides all necessary information to paginate the document.

As you can see this class provides some properties. Not for all of them necessarily a value has to be set. This depends on the PrintAppendix parameter that you pass to it’s constructor. If you pass the value of the example shown above you are forced to set the FooterHeight and the HeaderHeight. This value is used to positioning each element on the page. With that values you can define the height that has to be used for printing the particular data. But if you want to set a value to SummaryHeight you will get an error saying that according to the passed PrintAppendixes you cannot set this value. If you want to set the SummaryHeight you have to set the PrintAppendixes flag to Summary. Additionally to the error catching in setting the values there is an other error catching during the print process if you set a value in the enum and do not provide a corresponding height value for that value. As you can see the PrintDimension class provides three different Construcors. I will shortly show what each of those constructors call.

  • PrintDimension() : this(PrintAppendixes.None)
  • PrintDimension(PrintAppendixes pa) : this(pa,new Thickness(50,25,50,50)
  • PrintDimension(PrintAppendixes pa, Thickness margin)

The parameter of type Thickness is used to specify what is the space that should be free on the page. The value new Thickness(50,25,50,50) set as default should be the optimal for printing on a printer.

PrintAppendixes pa = PrintAppendixes.Header | PrintAppendixes.Footer;
PrintingDimensions pd = new PrintingDimensions(pa);
pd.FooterHeight = 30;
pd.HeaderHeight = 30;
pd.SummaryHeight = 30; //throws following exception: InvalidOperationException("You have to set the SummaryHeight before printing if the PrintAppendixes flag has set to "Summary"."))

PrintProcessor

<span >
Now I will speak about the class of major interest, the PrintProcessor class.

It is an abstract class that provides all the necessary information and data that has to be printed. As you can see in the class diagram on the left hand side all inherited classes have to implement the following methods/properties:

  • GetNextItem()
  • GetTable()
  • PrintDimensions
  • >FileName

All other methods are marked as virtual and can be overwritten if desired. But there is one constraint. You are forced to pass a PrintingDimension to the base class and as you have seen before that you have to set in PrintDimension a valid value for the PrintAppendix and corresponding to that a valid value for each dimension you have to overwrite the corresponding GetMethod. If you will not overwrite each corresponding method to each value set in the PrintAppendix flag enum you will get in that moment an exception when you try to print.

As you can not see in the class diagram all of the GetMethods that are not italic are marked as virtual. As just said you have to overwrite them if you set the corresponding PrintAppendix flag, otherwise the trial of printing will rise an error. As you can se the return type is always UIElement, that means you can specify by yourself the element that has to be returned.

Note that the element which is returned by any of that methods will be printed on the document.

Note: If you set the PrintAppendix flag to PrintAppendix.Header and you will not overwrite the GetHeader() method you cannot print the document. In this case, if the PrintAppendix flag for the footer is not set, do not be surprised if the GetFooter() method is not called. Only those, depending on the flag set, are executed.

Now I will get in the description of the GetNextItem() method. This method is marked as abstract therefore you have to provide it. According to the name of the method this method should provide the item that has to be printed in the body. The return type of that method is of type ILineItem that provides the line itself and the height of the line. The method is called as long as you provide values. In the case when this method return null, the printer considers to have finished printing all line items.

public class MyCustomPrintProcessor : PrintProcessor
{
    private int _no = 0;
public MyCustomPrintProcessor(PrintingDimensions pd) :base(pd)
    {}
    […]
public override UIElement GetFooter()
    {
        return new Label { Content = "This is the footer" };
    }
public override UIElement GetHeader()
    {
        return new Label { Content = "This is the header" };
    }
public override UIElement GetSummary()
    {
return new Label { Content = "This is the summary an will never be called because of the Summary flag is not set in PrintDimensions" };
    }
public override ILineItem GetNextItem()
    {
        if(_no == 10)
            return null;
return new MyCustomLineItem(int ++_no);
    }
    […]
}

ILineItem

The interface ILineItem provides the method GetLine() that returns the content of each single line and the property LineHeight. Until now (January 2011) you have to set the height of the returning line manually, but in further releases of this printing library I will provide the automatic calculation of the item. The LineHeight is used to position the element in the document body. Furthermore it is used to check whether the element has to be printed on a new page or not.

NOTE: until now (January 2011) you will get into troubles if the Height is higher than the total space available for printing.

public MyCustomLineItem : ILineItem
{
private readonly int _i;
public MyCustonLineItem(int i)
    {
        _i = i;
    }
public UIElementGetLine()
    {
return new Label {Content = "This is item #"+i};
    }
public double LineHeight
    {
        get { return 20;}
    }
}

PrintProcessorBackground

As you might have seen in the PrintProcessor class there is a GetBackgroud() method that provides the background of the document. The class provides only methods for positioning the background on the document and the background element, of that the opacity can be set.

Example C# project for printing

You can download a complete working “Visual Studio 2010 solution” that demonstrate my printing library by using this link: MmPrinting20100117.

NOTE: please change the file format of the downloading document from *.docx to* .rar

The class diagram can be downloaded here: Class library.

NOTE: I have released a new version of this library. Please follow this link: WPF Printing Library v2.0

 
11 Comments

Posted by on January 17, 2011 in C-Sharp, Printing, WPF

 

Tags: , ,