RSS

WPF Printing Library v2.0 (Article in preview state)

06 Mar

note: this article is a preview version and is subject to change

Structure, description, text-correction will change.
Internal library changes will not likely occur.

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

About these ads
 
31 Comments

Posted by on March 6, 2013 in Printing, WPF

 

Tags: ,

31 responses to “WPF Printing Library v2.0 (Article in preview state)

  1. Tropet Coniconde

    March 29, 2013 at 11:28

    Nice post. Thanks for this

     
  2. Tropet Coniconde

    March 30, 2013 at 02:46

    Can I have the class library of this project? I want to modify the Page size. Or do you have any idea on how can I make it as landscape?

     
    • Michael Mairegger

      March 30, 2013 at 09:32

      Thanks for your interests. There is a PageOrientation property in the PrintProcessor class where you can define whether you want to print in portrait or landscape mode.

       
  3. Tropet Coniconde

    March 30, 2013 at 10:41

    What I need to do is to print a receipt. Is it possible with that? I’ve read about the the ColumnDimensionAttribute and will play with it. Hope that I’m on the right track. Thank you sir for this wonderfull lib.

     
    • Michael Mairegger

      March 30, 2013 at 12:16

      I have updated the PrintDimension section and added an example how to work with ColumnDimensionAttributes. Furthermore I have uploaded a new version of the sample project including a print where the ColumnDimensionAttributes have been applied. The class is called CustomPrintDimension

       
    • Michael Mairegger

      March 30, 2013 at 12:17

      Yes it is possible, I use my library for printing invoices, delivery notes, receipts, etc

       
  4. Tropet Coniconde

    March 30, 2013 at 12:47

    Your printing lib is really flexible. Thank you very much! Please keep posting more tutorials. Appreciate it all.

     
  5. Tropet Coniconde

    April 4, 2013 at 03:32

    How can I change the Height of the line items container?

     
    • Michael Mairegger

      April 4, 2013 at 05:35

      The height is computed according the space the item container needs. But if you want to create an item with a greather height you can define this in the IPrintContent.Content property as follows:

      public class CustomPrintContent : IPrintContent { public Panel Content { get { Panel p = new Panel{ Background = Brusehes.Red}; p.Height = 200; return p } } }

       
  6. Tropet Coniconde

    April 4, 2013 at 04:22

    False alarm, I get it.

     
  7. Tropet Coniconde

    April 10, 2013 at 04:36

    Is there an easy way to print an item as tile? This is for barcode generator which will populate the whole page.

     
    • Michael Mairegger

      April 10, 2013 at 14:33

      How do you genereate the Barcode? I suggest to use a WPF-Barcode-Control which you can add as content to the page.

      ________________________________

       
  8. Tropet Coniconde

    April 10, 2013 at 14:50

    I used a stack panel for the return value to IContent and add a barcode control as its children, however, it will display each item in vertical form. So what I did is to perform some operation on it and I’m not sure if it’s efficient. Anyway, thank you very much. I’ll check that control.

     
    • Michael Mairegger

      April 10, 2013 at 15:20

      The StackPanel control stacks the items horizontal because this is the default. You should use the StackPanel.Orientation = Orientation.Horizontal to stack the contents horizontal.

      ________________________________

       
  9. Tropet Coniconde

    June 6, 2013 at 09:18

    I want to print a receipt, how can I print the header on the first page and footer on the last page? It seems the attributes for Print Processor is not working for me. Please help. Thanks.

     
    • Michael Mairegger

      June 6, 2013 at 20:33

      Can you show me how do you declare the PrintOnPageAttribute? It should work to print the header on the first and the footer on the last. That’s how I do all the time.

       
  10. Tropet Coniconde

    June 10, 2013 at 03:36

    Thank you michael, its ok now. I manipulate it through Linq and I only used the GetTableFunction. Regards with the attributes, I think there’s something wrong with my implementation. I try it with the sample project attached here and it works fine

     
    • Michael Mairegger

      June 10, 2013 at 18:39

      Has it solved your problem by using the the PrintOnPageAttribute?

       
  11. Dimi Ockers

    June 11, 2013 at 00:45

    Michael, can i add an image to use in the header? I tried using BitmapImage and then returning a new image, but it’s blank..

     
    • Michael Mairegger

      June 11, 2013 at 10:30

      Hi, you can use an image wherever you want. You achieve this as follows:

      var image = new Image();
      var bitmapImage = new BitmapImage();
      
      bitmapImage.BeginInit();
      bitmapImage.UriSource = _imageSource;
      bitmapImage.EndInit();
      
      image.Source = bitmapImage;
      panel.Children.Add(image);
      
       
  12. Dimi Ockers

    June 12, 2013 at 09:54

    Thanks for the answer, got it working now. However i have one more question: I see there’s a whitespace above the header, can i remove that somehow?

     
    • Michael Mairegger

      June 12, 2013 at 20:01

      The should’t be any whitespace. Can you provide an example where the white space can be seen?

      Note: Try to set the PrintProcessor.ColorPrintPartsForDebug = true then you get an immediate overview from where to where each part is defined. I have inserted a picture about how this looks in the article

       
  13. Dimi Ockers

    June 12, 2013 at 22:42

    My apologies, later on i found out that by default my receipt printer has 60px header spacing. Got it solved by using Margin. Anyway, thanks for the debug feature ! Didn’t knew that.

     
  14. Brian Enderle

    November 11, 2013 at 17:21

    Do you have the source code for this library available for download, via GitHub, CodeProject, etc.?

     
    • Michael Mairegger

      November 11, 2013 at 18:08

      Until now I have not considered to publish the source code. I think this would be a good possibility to make the library available to more people. Do you have any problems with my library?

       
      • Brian Enderle

        November 11, 2013 at 19:13

        I have just found your library and am trying it out now. But where I work, one of the requirements of using code from a non-commercial source is that we are able to get the source code. This is so we can troubleshoot/debug should the original developer no longer support the product.

         
      • Michael Mairegger

        November 13, 2013 at 17:59

        I think I will publish my code on GitHub in the next couple of days

         
  15. Dimi Ockers

    November 13, 2013 at 10:48

    Michael, I have some issues with rendering of space.

    I override the GetHeader and GetFooter method to “meassure and arrange” the height of both elements, to set the FooterHeight and HeaderHeight properties.

    However: when I want to print something for the first time, my footer won’t get rendered properly.

    I did some investigation and it looks like the Items Panel gets rendered too large on the initial printing.

    Here is the bad one: http://www.cloudpos.be/fff/bad.png

    Here is the good one: http://www.cloudpos.be/fff/good.png

    (Note: look at “Bedankt en tot ziens” below)

    Is there any way i can resolve this problem?

     
    • Michael Mairegger

      November 13, 2013 at 17:58

      When do you set the FooterHeight? At that time when GetFooter is called? Because that might be the issue. I check for every item if it has space. This space is calculated with the FooterHeight that you set. If you change the FooterHeight later you might have problems that the footer is put out of range. I will try if I can address this issue.

       
      • Dimi Ockers

        November 13, 2013 at 18:12

        In Printer.cs i made 2 fields: private double header, footer;

        When I need to print I create a new instance of the Printer class like so:

        Printer p = new Printer(_mcv.CollectionToPrint);
        p.GetHeader();
        p.GetFooter();
        p.PreviewDocument();

        In the GetHeader and GetFooter method’s i calculate their heights:

        sp.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        sp.Arrange(new Rect(sp.DesiredSize));
        sp.UpdateLayout();

        footer = sp.ActualHeight; (Note that “sp” is a StackPanel), and header = sp.actualHeight;

        Because I override these methods, the same code will be run twice.

        For debugging purposes, i MessageBox’ed the ” sp.ActualHeight” property.

        On the first run the “ActualHeight” is smaller than the second run. So the second run has the correct value.

        Why is that ?

         
  16. Brian Enderle

    November 14, 2013 at 21:05

    When setting the margin in the Printer() constructor, it appears the margin between the footer and the bottom of the page is triple that of the rest of the margins (top & sides).

    Here is what I have done (within your sample project):

    public Printer(PrintAppendixes printingAppendix, IEnumerable collToPrint)
    {
    PrintDimension.Margin = new Thickness(50);

    _printingAppendix = printingAppendix;
    _collToPrint = collToPrint;

    }

    Any idea why?

     

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: