简体   繁体   English

打印大型WPF用户控件

[英]Printing large WPF User Controls

I have a huge data which I want to print using WPF. 我有一个巨大的数据,我想用WPF打印。 I found that WPF provides a PrintDialog.PrintVisual method for printing any WPF control derived from the Visual class. 我发现WPF提供了一个PrintDialog.PrintVisual方法,用于打印从Visual类派生的任何WPF控件。

PrintVisual will only print a single page so I need to scale the control to fit on the page. PrintVisual只会打印一个页面,所以我需要缩放控件以适应页面。 Unfortunately this would not work for me since the report was sometimes long enough that it could not be read easily when scaled to fit on the page. 不幸的是,这对我不起作用,因为报告有时足够长,以至于在缩放以适合页面时无法轻松阅读。

Another option for printing provided by WPF is to create a separate view in a FlowDocument . WPF提供的另一种打印选项是在FlowDocument创建单独的视图。 This is probably the best way to print documents, but it was more work than I wished to put into it, not to mention the extra view that would have to be maintained for each control I wished to print. 这可能是打印文档的最佳方式,但它比我想要的更多工作,更不用说我希望打印的每个控件都必须保留的额外视图。

I got another solution in this link , but it seems too complex for me. 我在这个链接中得到了另一个解决方案,但对我来说似乎太复杂了。

Is there any better and simple solution for this ? 有没有更好,更简单的解决方案呢? Thanks for any help 谢谢你的帮助

I'm assuming your report is displayed in a DataGrid or something else that is scrollable? 我假设您的报告显示在DataGrid或其他可滚动的内容中?

I believe FlowDocument is definitely your best choice here if you want to print something that looks, for lack of a better word, professional. 我相信FlowDocument绝对是你最好的选择,如果你想打印一些外观,因为缺乏一个更好的词,专业。 But if you want something quick and dirty, you could use a series of operations using RenderTargetBitmap.Render . 但是如果你想要快速和肮脏的东西,你可以使用RenderTargetBitmap.Render进行一系列操作。 The basic process would be: 基本过程是:

  1. Create the RenderTargetBitmap 创建RenderTargetBitmap
  2. Scroll the view such that you have a region visible that you want to print on one page 滚动视图,以便在一个页面上显示要显示的区域
  3. Call RenderTargetBitmap.Render on the DataGrid or the ScrollViewer that's containing the "large" control DataGrid上调用RenderTargetBitmap.Render或包含“大”控件的ScrollViewer
  4. Print the resulting bitmap 打印生成的位图
  5. Repeat for the next "Page" 重复下一个“页面”

Again, don't call RenderTargetBitmap.Render on the "large" control. 同样,不要在“大”控件上调用RenderTargetBitmap.Render Wrap the large control in a ScrollViewer if it isn't already. 将大型控件包装在ScrollViewer如果尚未包含)。 That will essentially be your paginator. 那将基本上是你的分页。

I don't know if you'll be satisfied with the results, but this is the easiest method I can think of. 我不知道你是否会对结果感到满意,但这是我能想到的最简单的方法。 It'll look like you manually hit PrintScreen each time. 看起来你每次都手动点击PrintScreen Not sure if that's what you want, but if you want it to look nicer, I think you need to use FlowDocument . 不确定这是不是你想要的,但如果你想让它看起来更好,我认为你需要使用FlowDocument

I use PrintDialog and DocumentPaginator for printing. 我使用PrintDialog和DocumentPaginator进行打印。

What I do is: 我所做的是:

  • select printer (show print dialog or use the system default) 选择打印机(显示打印对话框或使用系统默认值)
  • create pages (wpf control in the size of paper) 创建页面(wpf控件的纸张大小)
  • 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 Viewbox for temporary layout update and render. 在窗口的某个位置,您应该使用Viewbox进行临时布局更新和渲染。

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

And than you can run test: PrintTest1(forRender); 而且你可以运行test: PrintTest1(forRender);


Here's my PrintManager class: 这是我的PrintManager类:

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's my MyDocumentPaginator class: 这是我的MyDocumentPaginator类:

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 e.g. XpsDocumentWriter.Write().
        // That's 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 that you want to print control that you already have. 你告诉我你要打印已有的控件。
You can print this with my solution. 您可以使用我的解决方案打印出来。

For example: 例如:
Suppose that you have a UserCtrl , which is in ParentBorder . 假设您有一个UserCtrl ,它位于ParentBorder You need to removed it from the parent control, and then you can use it. 您需要从父控件中删除它,然后您可以使用它。

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

Than you can prepare 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's is RemoveFromParent function: 这是RemoveFromParent函数:

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 I use UpdateLayout and Viewbox in window, instead of Measure and Arrange like people in other examples? 为什么我在窗口中使用UpdateLayout和Viewbox,而不像其他示例中的人那样使用Measure and Arrange?

I try, but I had many problems with this. 我试试,但我遇到了很多问题。 I use controls that I alredy have, I changing style for printing, and I also export to PDF . 我使用我自己拥有的控件,我改变了打印样式,我也导出为PDF Measure and Arrange not working for me. 衡量和安排不适合我。 Control have to be docked in window for properly layout update and render. 控件必须停靠在窗口中,以便正确进行布局更新和渲染。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM