I want to get progress event when printing using WPF's PrintDialog .

HoneyBee 186 Reputation points
2023-02-07T02:33:23.33+00:00

I have a DocumentViewer with a FixedDocument.

And I use System.Windows.Controls.PrintDialog to print the document.

pd = PrintDialog Object

pd. Print()

or

pd.PrintDocument(DocumentPaginator, "")

It proceeds in two forms.

At this time I want to monitor the progress in PrintQueue .

I want to keep showing the currently ongoing Page out of the entire page on the UI thread.

However, no event can be referenced.

Is there any better way?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,669 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,223 questions
{count} votes

2 answers

Sort by: Most helpful
  1. billy paxton 0 Reputation points
    2023-02-07T15:39:53.14+00:00

    Turn the RSS FEED OFF


  2. Hui Liu-MSFT 38,191 Reputation points Microsoft Vendor
    2023-02-08T08:25:24.2133333+00:00

    The PrintDialog is not using any async API. So printing is synchronous. This means you can't update the UI in realtime.

    As a solution you would have to print the document manually. This allows to use asynchronous APIs. In addition, you get more control over the process (for example you can pick a printer or configure the print job explicitly).

    To allow the user to pick a printer, you would have to create your own custom small dialog.

    A custom DocumentPaginator implementation (RangeDocumentPaginator) is used to support printing page ranges.

    MainWindow.xaml.cs:

    private CancellationTokenSource CancellationTokenSource { get; set; }
    
    private async Task OnPrintButtonClickedAsync(object sender, EventArgs e)
    {  
      DocumentPaginator documentPaginator = ((IDocumentPaginatorSource)this.Document).DocumentPaginator;
      var progressReporter = new Progress<(int PageNumber, int Percentage)>(ReportProgress);
      using var printer = new MyDocumentPrinter();
      this.ProgressBar.Maximum = await printer.GetPageCountAsync(documentPaginator);
    
      this.CancellationTokenSource = new CancellationTokenSource();
      try
      {
        // Print complete document
        await printer.PrintFullDocumentAsync(documentPaginator, progressReporter, this.CancellationTokenSource.Token);
    
        // Print document pages 1 to 10. 
        // The page numbers must be converted to indices 0 to 9.
        // The method takes a 'Range' as argument. 
        // 'Range' is defined as inclusive start index and exclusive end index.
        // Therefore the 'Range' for the pages 1 to 10 is '0..10'.
        await printer.PrintDocumentPageRangeAsync(documentPaginator, 0..10, progressReporter, this.CancellationTokenSource.Token);
      }
      catch (OperationCanceledException)
      {
      }
      finally
      {
        this.CancellationTokenSource.Dispose();
        this.CancellationTokenSource = null;
      }
    }
    
    private void OnCancelButtonClicked(object sender, RoutedEventArgs e) 
      => this.CancellationTokenSource?.Cancel();
        
    private void ReportProgress((int PageCount, int Percentage) progress) 
      => this.ProgressBar.Value = progress.PageCount;
    
    
    
    

    MyDocumentPrinter.cs

    public class MyDocumentPrinter : IDisposable
    {
      private TaskCompletionSource PrintTaskCompletionSource { get; set; }
      private TaskCompletionSource<int> ComputePagesTaskCompletionSource { get; set; }
      private PrintServer PrintServer { get; }
      private IProgress<(int PageNumber, int Percentage)> CurrentProgressReporter { get; set; }
      private bool IsPrintJobPending { get; set; }
      private CancellationToken CancellationToken { get; set; }
    
      public MyDocumentPrinter() => this.PrintServer = new LocalPrintServer();
    
      public Task<int> GetPageCountAsync(DocumentPaginator documentPaginator)
      {
        this.CancellationToken = CancellationToken.None;
        if (!documentPaginator.IsPageCountValid)
        {
          this.ComputePagesTaskCompletionSource = new TaskCompletionSource<int>(TaskCreationOptions.None);
          documentPaginator.ComputePageCountCompleted += OnCountingPagesCompleted;
          documentPaginator.ComputePageCountAsync();
          return this.ComputePagesTaskCompletionSource.Task;
        }
    
        return Task.FromResult(documentPaginator.PageCount);
      }
    
      public async Task PrintFullDocumentAsync(DocumentPaginator documentPaginator, IProgress<(int PageNumber, int Percentage)> progressReporter, CancellationToken cancellationToken)
        => await PrintDocumentPageRangeAsync(documentPaginator, .., progressReporter, cancellationToken);
    
      public Task PrintDocumentPageRangeAsync(DocumentPaginator documentPaginator, Range pageIndexRange, IProgress<(int PageNumber, int Percentage)> progressReporter, CancellationToken cancellationToke)
      {
        this.CancellationToken = cancellationToken;
        this.CancellationToken.ThrowIfCancellationRequested();
    
        this.IsPrintJobPending = true;
        this.CurrentProgressReporter = progressReporter;
        this.PrintTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.None);
        var rangeDocumentPaginator = new MyRangeDocumentPaginator(documentPaginator, pageIndexRange);
        if (!rangeDocumentPaginator.IsPageCountValid)
        {
          this.ComputePagesTaskCompletionSource = new TaskCompletionSource<int>(TaskCreationOptions.None);
          rangeDocumentPaginator.ComputePageCountCompleted += OnCountingPagesCompleted;
          rangeDocumentPaginator.ComputePageCountAsync();
          return this.PrintTaskCompletionSource.Task;
        }
    
        StartPrintJob(rangeDocumentPaginator);
        this.IsPrintJobPending = false;
        return this.PrintTaskCompletionSource.Task;
      }
    
      private void StartPrintJob(DocumentPaginator documentPaginator)
      {
        this.CancellationToken.ThrowIfCancellationRequested();
    
        /* Select the destination printer */
    
        // Optionally show a custom printer picker dialog
        PrintQueue destinationPrinter = GetPrintQueueFromUser(printServer);
    
        // Alternatively use the default printer
        PrintQueue destinationPrinter = printServer.DefaultPrintQueue;
    
        // Alternatively, pick a particular printer explicitly e.g., native PDF printer
        PrintQueue destinationPrinter = this.PrintServer.GetPrintQueue("Microsoft Print to PDF");
    
        /* Start the printing */
    
        // Create a XpsDocumentWriter that writes to the printer queue
        XpsDocumentWriter documentWriter = PrintQueue.CreateXpsDocumentWriter(destinationPrinter);
        documentWriter.WritingProgressChanged += OnPrintProgressChanged;
        documentWriter.WritingCompleted += OnPrintingCompleted;
        documentWriter.WriteAsync(documentPaginator);
      }
    
      private PrintQueue GetPrintQueueFromUser(PrintServer printServer)
      {
        PrintQueueCollection printers = printServer.GetPrintQueues();
    
        // TODO::Implement MyPrinterPickerDialog (extend Window)
        var myPrinterPickerDialog = new MyPrinterPickerDialog(printers);
        myPrinterPickerDialog.ShowDialog();
        return myPrinterPickerDialog.SelectedPrintQueue;
      }
    
      private void OnCountingPagesCompleted(object sender,
          System.ComponentModel.AsyncCompletedEventArgs e)
      {
        var documentPaginator = sender as DocumentPaginator;
        documentPaginator.ComputePageCountCompleted -= OnCountingPagesCompleted;
        if (this.CancellationToken.IsCancellationRequested)
        {
          this.ComputePagesTaskCompletionSource.TrySetCanceled(this.CancellationToken);
          this.CancellationToken.ThrowIfCancellationRequested();
        }
        else
        {
          _ = this.ComputePagesTaskCompletionSource.TrySetResult(documentPaginator.PageCount);
        }
    
        if (this.IsPrintJobPending)
        {
          StartPrintJob(documentPaginator);
        }
      }
    
      private void OnPrintProgressChanged(object sender, WritingProgressChangedEventArgs e)
      {
        var documentPrinter = sender as XpsDocumentWriter;
        this.CurrentProgressReporter.Report((e.Number, e.ProgressPercentage));
        if (this.CancellationToken.IsCancellationRequested)
        {
          documentPrinter.WritingCancelled += OnPrintingCancelled;
          documentPrinter.CancelAsync();
        }
      }
    
      private void OnPrintingCancelled(object sender, WritingCancelledEventArgs e)
      {
        var documentPrinter = sender as XpsDocumentWriter;
        documentPrinter.WritingCancelled -= OnPrintingCancelled;
        this.PrintTaskCompletionSource.TrySetCanceled(this.CancellationToken);
        this.CancellationToken.ThrowIfCancellationRequested();
      }
    
      private void OnPrintingCompleted(object sender, WritingCompletedEventArgs e)
      {
        // TODO::Handle errors by checking event args
    
        documentWriter.WritingCompleted -= OnPrintingCompleted;
        documentWriter.WritingProgressChanged -= OnPrintProgressChanged;
        _ = this.PrintTaskCompletionSource.TrySetResult();
      }
    
      private bool disposedValue;
      protected virtual void Dispose(bool disposing)
      {
        if (!this.disposedValue)
        {
          if (disposing)
          {
            this.PrintServer.Dispose();
          }
    
          // TODO: free unmanaged resources (unmanaged objects) and override finalizer
          // TODO: set large fields to null
          this.disposedValue = true;
        }
      }
    
      // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
      // ~MyDocumentPrinter()
      // {
      //     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
      //     Dispose(disposing: false);
      // }
    
      public void Dispose()
      {
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
      }
    }
    

    MyRangeDocumentPaginator.cs:

    internal class MyRangeDocumentPaginator : DocumentPaginator
    {
      protected MyRangeDocumentPaginator() : base() { }
    
      public MyRangeDocumentPaginator(DocumentPaginator documentPaginator, Range pageNumberRange)
      {
    
    this.DocumentPaginator = documentPaginator;
    this.PageIndexRange = pageNumberRange;
    if (!this.DocumentPaginator.IsPageCountValid)
    {
      this.DocumentPaginator.ComputePageCountCompleted += OnPageCountCompleted;
      this.DocumentPaginator.ComputePageCountAsync();
    }
    else
    {
      ThrowIfPageRangeIsInvalid();
    }
    
      }
    
      private void ThrowIfPageRangeIsInvalid()
      {
    
    if (this.PageIndexRange.Start.Value < 0
      || this.PageIndexRange.End.Value > this.DocumentPaginator.PageCount)
    {
      throw new IndexOutOfRangeException();
    }
      }
    
      private void OnPageCountCompleted(object sender, AsyncCompletedEventArgs e)
    
    => ThrowIfPageRangeIsInvalid();
    
      public override DocumentPage GetPage(int pageIndex)
      {
    
    // XpsDocumentWriter will always start with page index 0.
    // We have to use the start page index as offset
    // to return the correct pages of the range from the underlying document.
    // XpsDocumentWriter will use the PageCount property to know how many pages it can fetch.
    // We have manipulated the PageCount property to return the count of the pages contained within the range.
    int pageOffset = this.PageIndexRange.Start.Value;
    pageIndex += pageOffset;
    return pageIndex < this.PageIndexRange.Start.Value
      || (pageIndex >= this.PageIndexRange.End.Value && !this.IsRangeFullDocument)
        ? DocumentPage.Missing
        : this.DocumentPaginator.GetPage(pageIndex);
    
      }
    
      public override bool IsPageCountValid => this.DocumentPaginator.IsPageCountValid;
    
      public override int PageCount
      {
    
    get
    {
      int range = this.PageIndexRange.End.Value - this.PageIndexRange.Start.Value;
      return this.IsRangeFullDocument
        ? this.DocumentPaginator.PageCount
        : range;
    }
    
      }
    
      public override Size PageSize { get => this.DocumentPaginator.PageSize; set => this.DocumentPaginator.PageSize = value; }
      public override IDocumentPaginatorSource Source => this.DocumentPaginator.Source;
      public Range PageIndexRange { get; set; }
      public bool IsRangeFullDocument => this.PageIndexRange.Equals(Range.All);
      private DocumentPaginator DocumentPaginator { get; }
    }
    
    

    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.