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