Поделиться через


Отображение диалогового окна печати (WPF .NET)

Хотите распечатать приложение? Класс можно использовать PrintDialog для открытия стандартного диалогового окна печати Microsoft Windows. Это делается следующим образом.

Важно!

Документация по рабочему столу для .NET 7 и .NET 6 находится в стадии разработки.

Примечание.

Элемент System.Windows.Controls.PrintDialog управления, используемый для WPF и обсуждаемый здесь, не следует путать с компонентом System.Windows.Forms.PrintDialog Windows Forms.

Класс PrintDialog предоставляет единый элемент управления для настройки печати и отправки задания печати. Элемент управления легко использовать и может быть создан с помощью разметки XAML или кода. В следующих примерах создается и отображается PrintDialog экземпляр с помощью кода.

Диалоговое окно печати можно использовать для настройки параметров печати, таких как:

  • Печать только определенного диапазона страниц.
  • Выберите принтеры, установленные на компьютере. Для создания этих типов документов можно использовать параметр средства записи документов Microsoft XPS:
    • Формат XPS (XML Paper Specification)
    • Спецификация open XML Paper (OpenXPS)

В этом примере печатаются все страницы документа XPS. По умолчанию код будет:

  1. Откройте диалоговое окно печати, которое предложит пользователю выбрать принтер и запустить задание печати.
  2. Создайте XpsDocument экземпляр объекта с содержимым документа XPS.
  3. XpsDocument Используйте объект для создания DocumentPaginator объекта, который содержит все страницы документа XPS.
  4. Вызовите метод, передав DocumentPaginator объектPrintDocument, чтобы отправить все страницы на указанный принтер.
/// <summary>
/// Print all pages of an XPS document.
/// Optionally, hide the print dialog window.
/// </summary>
/// <param name="xpsFilePath">Path to source XPS file</param>
/// <param name="hidePrintDialog">Whether to hide the print dialog window (shown by default)</param>
/// <returns>Whether the document printed</returns>
public static bool PrintWholeDocument(string xpsFilePath, bool hidePrintDialog = false)
{
    // Create the print dialog object and set options.
    PrintDialog printDialog = new();

    if (!hidePrintDialog)
    {
        // Display the dialog. This returns true if the user presses the Print button.
        bool? isPrinted = printDialog.ShowDialog();
        if (isPrinted != true)
            return false;
    }

    // Print the whole document.
    try
    {
        // Open the selected document.
        XpsDocument xpsDocument = new(xpsFilePath, FileAccess.Read);

        // Get a fixed document sequence for the selected document.
        FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();

        // Create a paginator for all pages in the selected document.
        DocumentPaginator docPaginator = fixedDocSeq.DocumentPaginator;

        // Print to a new file.
        printDialog.PrintDocument(docPaginator, $"Printing {Path.GetFileName(xpsFilePath)}");

        return true;
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);

        return false;
    }
}
''' <summary>
''' Print all pages of an XPS document.
''' Optionally, print all pages without showing a print dialog window.
''' </summary>
''' <param name="xpsFilePath">Path to source XPS file</param>
''' <param name="hidePrintDialog">Whether to hide the print dialog window (shown by default)</param>
''' <returns>Whether the document printed</returns>
Public Shared Function PrintWholeDocument(xpsFilePath As String, Optional hidePrintDialog As Boolean = False) As Boolean

    ' Create the print dialog object and set options.
    Dim printDialog As New PrintDialog

    If Not hidePrintDialog Then

        ' Display the dialog. This returns true if the user presses the Print button.
        Dim isPrinted As Boolean? = printDialog.ShowDialog()
        If isPrinted <> True Then Return False

    End If

    ' Print the whole document.
    Try

        ' Open the selected document.
        Dim xpsDocument As New XpsDocument(xpsFilePath, FileAccess.Read)

        ' Get a fixed document sequence for the selected document.
        Dim fixedDocSeq As FixedDocumentSequence = xpsDocument.GetFixedDocumentSequence()

        ' Create a paginator for all pages in the selected document.
        Dim docPaginator As DocumentPaginator = fixedDocSeq.DocumentPaginator

        ' Print to a new file.
        printDialog.PrintDocument(docPaginator, $"Printing {Path.GetFileName(xpsFilePath)}")

        Return True

    Catch e As Exception

        MessageBox.Show(e.Message)

        Return False

    End Try

End Function

Иногда требуется распечатать только определенный диапазон страниц в документе XPS. Для этого мы расширим абстрактный DocumentPaginator класс, чтобы добавить поддержку диапазонов страниц. По умолчанию код будет:

  1. Откройте диалоговое окно печати, которое предложит пользователю выбрать принтер, указать диапазон страниц и запустить задание печати.
  2. Создайте XpsDocument экземпляр объекта с содержимым документа XPS.
  3. XpsDocument Используйте объект для создания объекта по умолчаниюDocumentPaginator, в котором хранятся все страницы документа XPS.
  4. Создайте экземпляр расширенного DocumentPaginator класса, поддерживающего диапазоны страниц, передавая объект по умолчанию DocumentPaginator и PageRange структуру, возвращаемую объектом PrintDialog.
  5. PrintDocument Вызовите метод, передав экземпляр расширенного DocumentPaginator класса, чтобы отправить указанный диапазон страниц на указанный принтер.
/// <summary>
/// Print a specific range of pages within an XPS document.
/// </summary>
/// <param name="xpsFilePath">Path to source XPS file</param>
/// <returns>Whether the document printed</returns>
public static bool PrintDocumentPageRange(string xpsFilePath)
{
    // Create the print dialog object and set options.
    PrintDialog printDialog = new()
    {
        UserPageRangeEnabled = true
    };

    // Display the dialog. This returns true if the user presses the Print button.
    bool? isPrinted = printDialog.ShowDialog();
    if (isPrinted != true)
        return false;

    // Print a specific page range within the document.
    try
    {
        // Open the selected document.
        XpsDocument xpsDocument = new(xpsFilePath, FileAccess.Read);

        // Get a fixed document sequence for the selected document.
        FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();

        // Create a paginator for all pages in the selected document.
        DocumentPaginator docPaginator = fixedDocSeq.DocumentPaginator;

        // Check whether a page range was specified in the print dialog.
        if (printDialog.PageRangeSelection == PageRangeSelection.UserPages)
        {
            // Create a document paginator for the specified range of pages.
            docPaginator = new DocPaginator(fixedDocSeq.DocumentPaginator, printDialog.PageRange);
        }

        // Print to a new file.
        printDialog.PrintDocument(docPaginator, $"Printing {Path.GetFileName(xpsFilePath)}");

        return true;
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);

        return false;
    }
}

/// <summary>
/// Extend the abstract DocumentPaginator class to support page range printing. This class is based on the following online resources:
///
/// https://www.thomasclaudiushuber.com/2009/11/24/wpf-printing-how-to-print-a-pagerange-with-wpfs-printdialog-that-means-the-user-can-select-specific-pages-and-only-these-pages-are-printed/
///
/// https://social.msdn.microsoft.com/Forums/vstudio/en-US/9180e260-0791-4f2d-962d-abcb22ba8d09/how-to-print-multiple-page-ranges-with-wpf-printdialog
///
/// https://social.msdn.microsoft.com/Forums/en-US/841e804b-9130-4476-8709-0d2854c11582/exception-quotfixedpage-cannot-contain-another-fixedpagequot-when-printing-to-the-xps-document?forum=wpf
/// </summary>
public class DocPaginator : DocumentPaginator
{
    private readonly DocumentPaginator _documentPaginator;
    private readonly int _startPageIndex;
    private readonly int _endPageIndex;
    private readonly int _pageCount;

    public DocPaginator(DocumentPaginator documentPaginator, PageRange pageRange)
    {
        // Set document paginator.
        _documentPaginator = documentPaginator;

        // Set page indices.
        _startPageIndex = pageRange.PageFrom - 1;
        _endPageIndex = pageRange.PageTo - 1;

        // Validate and set page count.
        if (_startPageIndex >= 0 &&
            _endPageIndex >= 0 &&
            _startPageIndex <= _documentPaginator.PageCount - 1 &&
            _endPageIndex <= _documentPaginator.PageCount - 1 &&
            _startPageIndex <= _endPageIndex)
            _pageCount = _endPageIndex - _startPageIndex + 1;
    }

    public override bool IsPageCountValid => true;

    public override int PageCount => _pageCount;

    public override IDocumentPaginatorSource Source => _documentPaginator.Source;

    public override Size PageSize { get => _documentPaginator.PageSize; set => _documentPaginator.PageSize = value; }

    public override DocumentPage GetPage(int pageNumber)
    {
        DocumentPage documentPage = _documentPaginator.GetPage(_startPageIndex + pageNumber);

        // Workaround for "FixedPageInPage" exception.
        if (documentPage.Visual is FixedPage fixedPage)
        {
            var containerVisual = new ContainerVisual();
            foreach (object child in fixedPage.Children)
            {
                var childClone = (UIElement)child.GetType().GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(child, null);

                FieldInfo parentField = childClone.GetType().GetField("_parent", BindingFlags.Instance | BindingFlags.NonPublic);
                if (parentField != null)
                {
                    parentField.SetValue(childClone, null);
                    containerVisual.Children.Add(childClone);
                }
            }

            return new DocumentPage(containerVisual, documentPage.Size, documentPage.BleedBox, documentPage.ContentBox);
        }

        return documentPage;
    }
}
''' <summary>
''' Print a specific range of pages within an XPS document.
''' </summary>
''' <param name="xpsFilePath">Path to source XPS file</param>
''' <returns>Whether the document printed</returns>
Public Shared Function PrintDocumentPageRange(xpsFilePath As String) As Boolean

    ' Create the print dialog object and set options.
    Dim printDialog As New PrintDialog With {
        .UserPageRangeEnabled = True
    }

    ' Display the dialog. This returns true if the user presses the Print button.
    Dim isPrinted As Boolean? = printDialog.ShowDialog()
    If isPrinted <> True Then Return False

    ' Print a specific page range within the document.
    Try

        ' Open the selected document.
        Dim xpsDocument As New XpsDocument(xpsFilePath, FileAccess.Read)

        ' Get a fixed document sequence for the selected document.
        Dim fixedDocSeq As FixedDocumentSequence = xpsDocument.GetFixedDocumentSequence()

        ' Create a paginator for all pages in the selected document.
        Dim docPaginator As DocumentPaginator = fixedDocSeq.DocumentPaginator

        ' Check whether a page range was specified in the print dialog.
        If printDialog.PageRangeSelection = PageRangeSelection.UserPages Then

            ' Create a document paginator for the specified range of pages.
            docPaginator = New DocPaginator(fixedDocSeq.DocumentPaginator, printDialog.PageRange)

        End If

        ' Print to a new file.
        printDialog.PrintDocument(docPaginator, $"Printing {Path.GetFileName(xpsFilePath)}")

        Return True

    Catch e As Exception

        MessageBox.Show(e.Message)

        Return False

    End Try

End Function

' Extend the abstract DocumentPaginator class to support page range printing.
' This class is based on the following online resources:
' https://www.thomasclaudiushuber.com/2009/11/24/wpf-printing-how-to-print-a-pagerange-with-wpfs-printdialog-
' that-means-the-user-can-select-specific-pages-and-only-these-pages-are-printed/
' https://social.msdn.microsoft.com/Forums/vstudio/en-US/9180e260-0791-4f2d-962d-abcb22ba8d09/how-to-print-
' multiple-page-ranges-with-wpf-printdialog
' https://social.msdn.microsoft.com/Forums/en-US/841e804b-9130-4476-8709-0d2854c11582/exception-quotfixedpage-
' cannot-contain-another-fixedpagequot-when-printing-to-the-xps-document?forum=wpf
Public Class DocPaginator
    Inherits DocumentPaginator

    Private ReadOnly _documentPaginator As DocumentPaginator
    Private ReadOnly _startPageIndex As Integer
    Private ReadOnly _endPageIndex As Integer
    Private ReadOnly _pageCount As Integer

    Public Sub New(documentPaginator As DocumentPaginator, pageRange As PageRange)

        ' Set document paginator.
        _documentPaginator = documentPaginator

        ' Set page indices.
        _startPageIndex = pageRange.PageFrom - 1
        _endPageIndex = pageRange.PageTo - 1

        ' Validate And set page count.
        If _startPageIndex >= 0 AndAlso
            _endPageIndex >= 0 AndAlso
            _startPageIndex <= _documentPaginator.PageCount - 1 AndAlso
            _endPageIndex <= _documentPaginator.PageCount - 1 AndAlso
            _startPageIndex <= _endPageIndex Then
            _pageCount = _endPageIndex - _startPageIndex + 1
        End If

    End Sub

    Public Overrides ReadOnly Property IsPageCountValid As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property PageCount As Integer
        Get
            Return _pageCount
        End Get
    End Property

    Public Overrides ReadOnly Property Source As IDocumentPaginatorSource
        Get
            Return _documentPaginator.Source
        End Get
    End Property

    Public Overrides Property PageSize As Size
        Get
            Return _documentPaginator.PageSize
        End Get
        Set(value As Size)
            _documentPaginator.PageSize = value
        End Set
    End Property

    Public Overrides Function GetPage(pageNumber As Integer) As DocumentPage

        Dim documentPage As DocumentPage = _documentPaginator.GetPage(_startPageIndex + pageNumber)

        ' Workaround for "FixedPageInPage" exception.
        If documentPage.Visual.GetType() Is GetType(FixedPage) Then

            Dim fixedPage As FixedPage = documentPage.Visual
            Dim containerVisual = New ContainerVisual()

            For Each child As Object In fixedPage.Children
                Dim childClone = CType(child.[GetType]().GetMethod("MemberwiseClone", BindingFlags.Instance Or BindingFlags.NonPublic).Invoke(child, Nothing), UIElement)
                Dim parentField As FieldInfo = childClone.[GetType]().GetField("_parent", BindingFlags.Instance Or BindingFlags.NonPublic)

                If parentField IsNot Nothing Then
                    parentField.SetValue(childClone, Nothing)
                    containerVisual.Children.Add(childClone)
                End If
            Next

            Return New DocumentPage(containerVisual, documentPage.Size, documentPage.BleedBox, documentPage.ContentBox)

        End If

        Return documentPage

    End Function

End Class

Совет

Хотя вы можете использовать метод для печати, не открывая диалоговое окно печати, по соображениям производительности, лучше использовать PrintDocumentAddJob метод или один из многих Write методовWriteAsync.XpsDocumentWriter Дополнительные сведения об этом см. в разделе "Печать файла XPS" и "Печать документов".

См. также