簡體   English   中英

打印大型WPF用戶控件

[英]Printing large WPF User Controls

我有一個巨大的數據,我想用WPF打印。 我發現WPF提供了一個PrintDialog.PrintVisual方法,用於打印從Visual類派生的任何WPF控件。

PrintVisual只會打印一個頁面,所以我需要縮放控件以適應頁面。 不幸的是,這對我不起作用,因為報告有時足夠長,以至於在縮放以適合頁面時無法輕松閱讀。

WPF提供的另一種打印選項是在FlowDocument創建單獨的視圖。 這可能是打印文檔的最佳方式,但它比我想要的更多工作,更不用說我希望打印的每個控件都必須保留的額外視圖。

我在這個鏈接中得到了另一個解決方案,但對我來說似乎太復雜了。

有沒有更好,更簡單的解決方案呢? 謝謝你的幫助

我假設您的報告顯示在DataGrid或其他可滾動的內容中?

我相信FlowDocument絕對是你最好的選擇,如果你想打印一些外觀,因為缺乏一個更好的詞,專業。 但是如果你想要快速和骯臟的東西,你可以使用RenderTargetBitmap.Render進行一系列操作。 基本過程是:

  1. 創建RenderTargetBitmap
  2. 滾動視圖,以便在一個頁面上顯示要顯示的區域
  3. DataGrid上調用RenderTargetBitmap.Render或包含“大”控件的ScrollViewer
  4. 打印生成的位圖
  5. 重復下一個“頁面”

同樣,不要在“大”控件上調用RenderTargetBitmap.Render 將大型控件包裝在ScrollViewer如果尚未包含)。 那將基本上是你的分頁。

我不知道你是否會對結果感到滿意,但這是我能想到的最簡單的方法。 看起來你每次都手動點擊PrintScreen 不確定這是不是你想要的,但如果你想讓它看起來更好,我認為你需要使用FlowDocument

我使用PrintDialog和DocumentPaginator進行打印。

我所做的是:

  • 選擇打印機(顯示打印對話框或使用系統默認值)
  • 創建頁面(wpf控件的紙張大小)
  • 打印

這是我的測試功能:

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

在窗口的某個位置,您應該使用Viewbox進行臨時布局更新和渲染。

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

而且你可以運行test: PrintTest1(forRender);


這是我的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);
    }
}

這是我的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);
    }
}

你告訴我你要打印已有的控件。
您可以使用我的解決方案打印出來。

例如:
假設您有一個UserCtrl ,它位於ParentBorder 您需要從父控件中刪除它,然后您可以使用它。

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

比你可以准備頁面:

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;

這是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());
}

為什么我在窗口中使用UpdateLayout和Viewbox,而不像其他示例中的人那樣使用Measure and Arrange?

我試試,但我遇到了很多問題。 我使用我自己擁有的控件,我改變了打印樣式,我也導出為PDF 衡量和安排不適合我。 控件必須停靠在窗口中,以便正確進行布局更新和渲染。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM