Blog

How to Print WPF elements to Ticket Printers.

Some years ago, I had the opportunity to develop software for retail industry. Let me say that things looked pretty interesting back then, Windows XP was young and it seemed to provide functionality that we only had in our dreams. But part of the dream did not turned completely true, since I had to cope with ticket printers.

If you had ever worked with ticket printers before, you will probably agree with me that most of the time you have to tweak or do some workaround because the driver for the printer does not provide a specific feature and the customer just doesn’t want to replace dozens of printers. Then there are the printer commands that your application must be aware of, and setting up reports to properly fit, and before you know the list of small tasks related can grow to surprise the inexperienced.

Time passed and it’s been a while since the last time I had a project for the retail industry, so those printer problems seemed past and distant, but you never know where you are going to end. So one day, while working on a smart client application in WPF, the customer had the requirement to print some documents to a portable ticket printer.

So, at the beginning, I thought that all my old problems where back to take revenge. But surprisingly they were not. This time we were talking about WPF, which provides –through .net Framework– excellent support for printing.
 
The challenge was that this time the documents we were sending to the ticket printer had not specific length. The printer was specialized in barcode printing, so it could only print jobs with a set width and height. The easiest way to see a big document was to set the printer paper to maximum height, which, if I remember correctly, was about 30 inches. But this lazy approach had the problem that when you finished printing a small report, you ended up with a long ticket – most of it blank paper – wasted paper.

So, if you look at the code below, you will see methods GetPrintQueue, PrintMultiple and PerformTransform, these are the basic code to print an UIElement like a Xaml page. However, if you bear with me, you will note that we also created method GetPagedDoc, which is the one that does the magic and allow us to set the printer to have a very specific – and small – height for each page and yet be able to print long reports.
GetPagedDoc basically creates a VisualBrush with the content of the UIElement to print and then, it creates a FixedDoc with whatever number of pages required to fit the whole report. It calculates and assigns to each page the part of the report –as a background image – that belongs to it. And there you have a document that can be properly handled by the ticket printer, regardless of report length or printer paper size.
 
As you can see things are not that complicated. No need to mess with the printer driver, and leave alone printer commands. As usual, the less complicated code we create, the better. We may save ourselves from some nightmares or sleepless nights in the near future.
 

 

    /// <summary>

    /// Gets the default Print queue

    /// </summary>

    private PrintQueue GetPrintQueue()

    {

        PrintQueue printQueue = null;

        LocalPrintServer localPrintServer = new LocalPrintServer();

 

        printQueue = localPrintServer.DefaultPrintQueue;

 

        return printQueue;

    }

    /// <summary>

    /// Sends multiple UI Elements to default printer

    /// </summary>

    public void PrintMultiple(List<UIElement> docsToPrint)

    {

        PrintQueue pq = this.ObtenerPrintQueue();

 

        foreach (UIElement doc in docsToPrint)

        {

            double height = 0;

            double width = 0;

//Our UIElements implements our custom IPrintableDocument which

//includes Height and Width properties. Basically these properties

//calculates from all the children UIElements the actual Hieght and

//Width for the element when is rendered and/or printed

            if (docImprimir is IPrintableDocument)

            {

                height = ((IPrintableDocument)doc).Height;

                width = ((IPrintableDocument)doc).Width;

            }

 

            XpsDocumentWriter xdwPrint = PrintQueue.CreateXpsDocumentWriter(pq);

            FixedDocument document = GetPagedDoc(doc, pq, width, height);

            DocumentPaginator paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;

//Writes all the pages to the printer

            xdwPrint.Write(paginator);

        }

    }

    /// <summary>

    /// Scales the element to fit within the paper width and height.

    /// Returns a FixedDocument with as much pages as needed to fit the element

    /// </summary>

    private FixedDocument GetPagedDoc(UIElement element, PrintQueue pq, double width, double height)

    {

        PrintCapabilities pc = pq.GetPrintCapabilities();

           

        Size visibleSize = new Size(width, pc.PageImageableArea.ExtentHeight);

        FixedDocument document = new FixedDocument();

 

        //We need to do this if the element has not been displayed before 

        element.Measure(new Size(width, height));

        element.Arrange(new Rect(new Point(0, 0), element.DesiredSize));

           

        Size elementSize = new Size(width, height);

        //We assume that the element will fith horizontally, see PerformTransform

        double yOffset = 0;

 

  //We iterate while we have not coveret the full report height

        while (yOffset < elementSize.Height)

        {

//We create a brush and asign the UIElement we are printing

            VisualBrush brush = new VisualBrush(element);

            brush.Opacity = 1;

            brush.Stretch = Stretch.None;

            brush.AlignmentX = AlignmentX.Left;

            brush.AlignmentY = AlignmentY.Top;

            brush.ViewboxUnits = BrushMappingMode.Absolute;

            brush.TileMode = TileMode.None;

            brush.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);

           

//We need to create the page content

            PageContent content = new PageContent();

            FixedPage page = new FixedPage();

            ((IAddChild)content).AddChild(page);

              

            document.Pages.Add(content);

            page.Width = (double)pq.UserPrintTicket.PageMediaSize.Width;

            page.Height = (double)pq.UserPrintTicket.PageMediaSize.Height;

               

//here we are setting the canvas with the exact size that will fit

//in the page

            Canvas canvas = new Canvas();

            FixedPage.SetLeft(canvas, pc.PageImageableArea.OriginWidth);

            FixedPage.SetTop(canvas, pc.PageImageableArea.OriginHeight);

            canvas.Width = visibleSize.Width;

            canvas.Height = visibleSize.Height;

            canvas.Background = brush;

            canvas.Opacity = 1;

           

//add the canvas to the page   

            page.Children.Add(canvas);

            PerformTransform(ref page, pq);

 

//and finally we get ready to move to the next part of the report.

            yOffset += visibleSize.Height;

            yOffset -= 1;

        }

        return document;

    }

 

    /// <summary>

    /// Scales the page to fit within the paper width

    /// </summary>

    private void PerformTransform(ref FixedPage fp, PrintQueue pq)

    {

       // Dots per Inch

       const double inch = 96;

 

        // Here we are getting the margins, we are assuming .3 on each side

  // and no vertical margin

        double xMargin = .3 * inch;

        double yMargin = 0 * inch;

        PrintTicket pt = pq.UserPrintTicket;

        Double printableWidth = pt.PageMediaSize.Width.Value;

        Double printableHeight = pt.PageMediaSize.Height.Value;

 

        Double xScale = (printableWidth - xMargin * 2) / printableWidth;

        Double yScale = (printableHeight - yMargin * 2) / printableHeight;

 

         // Scales the page to fit within the paper width

        fp.RenderTransform = new MatrixTransform(xScale, 0, 0, yScale, 0, 0);

    }


 
 

Comments

On 21 Jun 2010 10:34, William Stape said:

Great blog on label printing. I am new to labeling and have recently started doing my own barcode label printing. Glad to find a great post on labels, am doing a bit of research to help me out. Thanks again

Leave a comment

 
 
 
 
CAPTCHA Image Validation