Printing large WPF controls

I have huge data that I want to print using WPF. I found that WPF provides the PrintDialog.PrintVisual method for printing any WPF control obtained from the Visual class.

PrintVisual will only print one page, so I need to scale the control to fit on the page. Unfortunately, this would not work for me, as the report was sometimes long enough so that it could not be read easily when it was scaled to fit on the page.

Another printing option provided by WPF is to create a separate view in FlowDocument . This is probably the best way to print documents, but it was a lot of work than I wanted to put in it, not to mention the extra view that would have to be saved for each control that I wanted to print.

I have another solution in this link , but for me it seems too complicated.

Is there a better and simpler solution for this? Thanks for any help

+5
source share
2 answers

I assume your report is displayed in a DataGrid or something else scrollable?

I believe that FlowDocument definitely your best bet here if you want to print something that looks like due to the lack of a better word, a professional. But if you want something fast and dirty, you can use a series of operations using RenderTargetBitmap.Render . The main process:

  • Create a RenderTargetBitmap
  • Scroll the view so that you have a visible region that you want to print on one page
  • Call RenderTargetBitmap.Render on a DataGrid or ScrollViewer that contains a "large" control
  • Printing the resulting bitmap
  • Repeat for the next "page"

Again, do not call RenderTargetBitmap.Render in the "large" control. Wrap a large control in a ScrollViewer , if not already installed. It will, in fact, be your strapon.

I don’t know if you will be satisfied with the results, but this is the easiest way I can think of. It looks like you press PrintScreen manually each time. Not sure if this is what you want, but if you want it to look better, I think you need to use FlowDocument .

+2
source

I use PrintDialog and DocumentPaginator for printing. A.

What am I doing:

  • select a printer (display the print dialog box or use the default system)
  • create pages (wpf control in paper size)
  • Print

Here is my test function:

 public static void PrintTest1(Viewbox viewboxInWindowForRender) { FrameworkElement[] testContArr = PrepareTestContents(); //========================= PrintManager man = new PrintManager(); // Show print dialog (or select default printer) if (!man.SelectPrinter()) return; man.SetPageMargins(new Thickness(PrintManager.Size1cm * 2)); //========================= List<FrameworkElement> pagesForPrint = new List<FrameworkElement>(); for (int i = 0; i < testContArr.Length; i++) { // Put the page content into the control of the size of paper FrameworkElement whitePage = man.CreatePageWithContentStretched(testContArr[i]); // Temporary put the page into window (need for UpdateLayout) viewboxInWindowForRender.Child = whitePage; // Update and render whitePage. // Measure and Arrange will be used properly. viewboxInWindowForRender.UpdateLayout(); pagesForPrint.Add(whitePage); } viewboxInWindowForRender.Child = null; //========================= // Now you can show print preview to user. // pagesForPrint has all pages. // ... //========================= MyDocumentPaginator paginator = man.CreatePaginator(); paginator.AddPages(pagesForPrint); // Start printing man.Print(paginator, "Printing Test"); } // For testing public static FrameworkElement[] PrepareTestContents() { StackPanel sp1 = new StackPanel(); sp1.Width = PrintManager.PageSizeA4.Width - PrintManager.Size1cm * 2; sp1.Children.Add(PrepareTestBorder("Alice has a cat.")); sp1.Children.Add(PrepareTestBorder("Page number one.")); StackPanel sp2 = new StackPanel(); sp2.Width = sp1.Width / 2; sp2.Children.Add(PrepareTestBorder("Farmer has a dog.")); sp2.Children.Add(PrepareTestBorder("Page number two.")); return new FrameworkElement[] {sp1, sp2 }; } // For testing public static FrameworkElement PrepareTestBorder(string text) { Border b = new Border(); b.BorderBrush = Brushes.Black; b.BorderThickness = new Thickness(1); b.Margin = new Thickness(0, 0, 0, 5); TextBlock t = new TextBlock(); t.Text = text; b.Child = t; return b; } 

Somewhere in the window, you should have a Viewbox for temporarily updating the layout and rendering.

 <Window ...> <Grid> <Viewbox x:Name="forRender" Visibility="Hidden" Width="100" Height="100"/> ... </Grid> </Window> 

And how you can run the test: PrintTest1(forRender);


Here is my PrintManager class:

 public class PrintManager { public static readonly Size PageSizeA4 = new Size(21 * 96 / 2.54, 29.7 * 96 / 2.54); // (793.700787401575, 1122.51968503937) public static readonly double Size1cm = 96 / 2.54; // 37.7952755905512 private PrintDialog _printDialog; public PrintTicket PrintTicket { get; private set; } public PrintCapabilities TicketCapabilities { get; private set; } // Page size selected in print dialog (may not be exactly as paper size) public Size PageSize { get; private set; } public Thickness PageMargins { get; private set; } public Rect PageContentRect { get { return new Rect(PageMargins.Left, PageMargins.Top, PageSize.Width - PageMargins.Left - PageMargins.Right, PageSize.Height - PageMargins.Top - PageMargins.Bottom); } } public PrintManager() { } /// <summary> /// Show print dialog or try use default printer when useDefaultPrinter param set to true. /// <para/> /// Return false on error or when user pushed Cancel. /// </summary> public bool SelectPrinter(bool useDefaultPrinter = false) { if (_printDialog == null) _printDialog = new PrintDialog(); try { if (useDefaultPrinter) _printDialog.PrintQueue = LocalPrintServer.GetDefaultPrintQueue(); // pDialog.PrintQueue == null when default printer is not selected in system if (_printDialog.PrintQueue == null || !useDefaultPrinter) { // Show print dialog if (_printDialog.ShowDialog() != true) return false; } if (_printDialog.PrintQueue == null) throw new Exception("Printer error"); // Get default printer settings //_printDialog.PrintTicket = _printDialog.PrintQueue.DefaultPrintTicket; } catch (Exception ex) { MessageBox.Show(ex.Message); return false; } PrintTicket = _printDialog.PrintTicket; TicketCapabilities = _printDialog.PrintQueue.GetPrintCapabilities(PrintTicket); PageSize = new Size((double)TicketCapabilities.OrientedPageMediaWidth, (double)TicketCapabilities.OrientedPageMediaHeight); SetPageMargins(PageMargins); // Update margins if too small return true; } /// <summary> /// Start printing pages from paginator. /// </summary> public void Print(MyDocumentPaginator paginator, string printTaskDescription) { if (_printDialog == null) return; // Start printing document _printDialog.PrintDocument(paginator, printTaskDescription); } /// <summary> /// Set page margins and return true. /// <para/> /// If new page margins are too small (unprinted area) then set minimum and return false. /// </summary> public bool SetPageMargins(Thickness margins) { PageImageableArea pia = TicketCapabilities.PageImageableArea; PageMargins = new Thickness(Math.Max(margins.Left, pia.OriginWidth), Math.Max(margins.Top, pia.OriginHeight), Math.Max(margins.Right, PageSize.Width - pia.OriginWidth - pia.ExtentWidth), Math.Max(margins.Bottom, PageSize.Height - pia.OriginHeight - pia.ExtentHeight)); return PageMargins == margins; } /// <summary> /// Set pate margins with minimal /// </summary> public void SetMinimalPageMargins() { PageImageableArea pia = TicketCapabilities.PageImageableArea; // Set minimal page margins to bypass the unprinted area. PageMargins = new Thickness(pia.OriginWidth, pia.OriginHeight, (double)TicketCapabilities.OrientedPageMediaWidth - - pia.OriginWidth - pia.ExtentWidth, (double)TicketCapabilities.OrientedPageMediaHeight - pia.OriginHeight - pia.ExtentHeight); } /// <summary> /// Create page control witch pageContent ready to print. /// Content is stretched to the margins. /// </summary> public FrameworkElement CreatePageWithContentStretched(FrameworkElement pageContent) { // Place the content inside the page (without margins) Viewbox pageInner = new Viewbox(); pageInner.VerticalAlignment = VerticalAlignment.Top; // From the upper edge pageInner.Child = pageContent; // Printed control - the page with content Border whitePage = new Border(); whitePage.Width = PageSize.Width; whitePage.Height = PageSize.Height; whitePage.Padding = PageMargins; whitePage.Child = pageInner; return whitePage; } /// <summary> /// Create page control witch pageContent ready to print. /// <para/> /// Content is aligned to the top-center and must have /// a fixed size (max PageSize-PageMargins). /// </summary> public FrameworkElement CreatePageWithContentSpecSize(FrameworkElement contentSpecSize) { // Place the content inside the page Decorator pageInner = new Decorator(); pageInner.HorizontalAlignment = HorizontalAlignment.Center; pageInner.VerticalAlignment = VerticalAlignment.Top; pageInner.Child = contentSpecSize; // Printed control - the page with content Border whitePage = new Border(); whitePage.Width = PageSize.Width; whitePage.Height = PageSize.Height; // We align to the top-center only, because padding will cut controls whitePage.Padding = new Thickness(0, PageMargins.Top, 0, 0); whitePage.Child = pageInner; return whitePage; } /// <summary> /// Create paginator for pages created by CreatePageWithContent(). /// </summary> public MyDocumentPaginator CreatePaginator() { return new MyDocumentPaginator(PageSize); } } 

And here is my MyDocumentPaginator class:

 public class MyDocumentPaginator : DocumentPaginator { private List<FrameworkElement> _pages = new List<FrameworkElement>(); public override bool IsPageCountValid { get { return true; } } public override int PageCount { get { return _pages.Count; } } public override Size PageSize { get; set; } public override IDocumentPaginatorSource Source { get { return null; } } public MyDocumentPaginator(Size pageSize) { PageSize = pageSize; } public override DocumentPage GetPage(int pageNumber) { // Warning: DocumentPage remember only reference to Visual object. // Visual object can not be changed until PrintDialog.PrintDocument() called // or eg XpsDocumentWriter.Write(). // That why I don't create DocumentPage in AddPage method. return new DocumentPage(_pages[pageNumber], PageSize, new Rect(PageSize), new Rect(PageSize)); } public void AddPage(FrameworkElement page) { _pages.Add(page); } public void AddPages(List<FrameworkElement> pages) { _pages.AddRange(pages); } } 

You told me you want to print the controls you already have.
You can print this with my solution.

For instance:
Suppose you have a UserCtrl that is in a ParentBorder . You need to remove it from the parent control, and then you can use it.

 ParentBorder.Child = null; // Or you can use my function RemoveFromParent(UserCtrl); 

How can you prepare the page:

 FrameworkElement whitePage = man.CreatePageWithContentStretched(UserCtrl); viewboxInWindowForRender.Child = whitePage; viewboxInWindowForRender.UpdateLayout(); MyDocumentPaginator paginator = man.CreatePaginator(); paginator.AddPages(whitePage); man.Print(paginator, "Printing UserControl"); // After print you can restore UserCtrl RemoveFromParent(UserCtrl); ParentBorder.Child = UserCtrl; 

Here is the RemoveFromParent function:

 public static void RemoveFromParent(FrameworkElement child) { DependencyObject parent = child.Parent; if (parent == null) return; if (parent is Panel) ((Panel)parent).Children.Remove(child); else if (parent is Decorator) ((Decorator)parent).Child = null; else if (parent is ContentControl) ((ContentControl)parent).Content = null; else if (parent is ContentPresenter) ((ContentPresenter)parent).Content = null; else throw new Exception("RemoveFromParent: Unsupported type " + parent.GetType().ToString()); } 

Why am I using UpdateLayout and Viewbox in a window instead of Measure and Arrange like the people in other examples?

I try, but I had a lot of problems with this. I use the controls that I have, I change the print style, as well as export to PDF . Measure and order do not work for me. The controls must be docked in the window to properly update and render the layout.

0
source

All Articles