How to get current progress when printing using PrintDialog?

466 views Asked by At

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

However, no event can be referenced.

I've seen these links:

WPF - How to update ProgressBar based on the progress of a method from a different class?

https://learn.microsoft.com/en-us/answers/questions/1165557/wpf-download-file-with-progress-bar

I have the following code now, but I don't know how to do it.

Is there any better way?

    <StackPanel Orientation="Horizontal" >
        <Button HorizontalAlignment="Center" Width="80" Click="PrintButtonClick">print</Button>
        <ProgressBar x:Name="progressbar" Margin="10,0" Maximum="100" Width="140" Height="20" Value="{Binding Progress}"/>
        <TextBox IsReadOnly="True" Width="50" Text="{Binding ProgressValue}"/>
    </StackPanel>
   
    <DocumentViewer Name="viewer"  Grid.Row="1">
        <FixedDocument x:Name="fd">

        </FixedDocument>
    </DocumentViewer>
  
</StackPanel>


using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Xps.Packaging;
using Window = System.Windows.Window;
using System.Windows.Markup;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PrintDialog = System.Windows.Controls.PrintDialog;

namespace DocumentViewerPrintProgressbar
{
    public partial class MainWindow : Window, ICommand, INotifyPropertyChanged
    {
        TextBlock page1Text = new TextBlock();
        public MainWindow()
        {
            InitializeComponent();
            PrintDialog pd = new PrintDialog();


            fd.DocumentPaginator.PageSize = new Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);

            for (int i = 0; i <= 5; i++)
            {
                FixedPage page1 = new FixedPage();
                page1.Width = fd.DocumentPaginator.PageSize.Width;
                page1.Height = fd.DocumentPaginator.PageSize.Height;

                UIElement page1Text = pages();
                page1.Children.Add(page1Text);
                PageContent page1Content = new PageContent();
                ((IAddChild)page1Content).AddChild(page1);
                fd.Pages.Add(page1Content);
            }
        }
        private UIElement pages()
        {
            Canvas pcan = new Canvas();

            TextBlock page1Text = new TextBlock();
            page1Text.TextWrapping = TextWrapping.Wrap;
            for (int i = 1; i < 1200; i++)
            {
                page1Text.Text += i.ToString() + "This is a testssssssssssssssssssssssssssssssssssssssssssss";
            }
            page1Text.FontSize = 40;
            page1Text.Margin = new Thickness(96);

            pcan.Children.Add(page1Text);


            return pcan;
        }



        void PrintButtonClick(object sender, RoutedEventArgs e)
        {
            var dlg = new PrintDialog();

            // Allow the user to select a PageRange
            dlg.UserPageRangeEnabled = true;

            if (dlg.ShowDialog() == true)
            {
                DocumentPaginator paginator =  fd.DocumentPaginator;
                dlg.PrintDocument(paginator, "Just a test");

            }
        }
        public string ProgressValue { get; set; }
        public int Status { get; set; }
        public double Progress { get; set; }
        public void Execute(object parameter)
        {
            Progress = 0;
        }
        
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter) => true;
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        public static bool PrintWholeDocument(string xpsFilePath, bool hidePrintDialog = false)
        {
            PrintDialog printDialog = new PrintDialog();
            if (!hidePrintDialog)
            {
                bool? isPrinted = printDialog.ShowDialog();
                if (isPrinted != true)
                    return false;
            }
            try
            {
                XpsDocument xpsDocument = new XpsDocument(xpsFilePath, FileAccess.Read);
                FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
                DocumentPaginator docPaginator = fixedDocSeq.DocumentPaginator;
                printDialog.PrintDocument(docPaginator, $"Printing {System.IO.Path.GetFileName(xpsFilePath)}");
                return true;
            }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show(e.Message);
                return false;
            }
        }

       
    }
}

Update:

   private void btnPrint_Click(object sender, RoutedEventArgs e)
        {
            this.Topmost = false;
            PrintDialog printDialog = new PrintDialog();
            CancellationTokenSource printServerWatchTaskCancellationTokenSource = new CancellationTokenSource();
            PrintServer printServer = new PrintServer(PrintSystemDesiredAccess.AdministrateServer);
            if (printDialog.ShowDialog() == true)
            {
                Task printServerTask = new Task(async () =>
                {
                    await this.Dispatcher.InvokeAsync(async () =>
                    {
                        PrintQueue _queue = new PrintQueue(printServer, printDialog.PrintQueue.FullName);
                        StringBuilder sb = new StringBuilder();
                        PrintSystemJobInfo job;

                        while (true)
                        {
                            _queue.Refresh();
                            if (_queue.NumberOfJobs > 0)
                            {
                                sb.Clear();
                                job = _queue.GetPrintJobInfoCollection().Where(x => x.Name.Equals("AbleSoft PostDocument2.0")).SingleOrDefault();
                                if (job != null)
                                {
                                    switch (job.JobStatus)
                                    {
                                        case PrintJobStatus.Spooling:
                                            sb.AppendLine($"Spooling");
                                            sb.AppendLine($"{job.NumberOfPages} / {PrintTargetFileInfo.Count}");
                                            break;
                                        case PrintJobStatus.Printing:
                                        case (PrintJobStatus.Printing | PrintJobStatus.Retained):
                                            sb.AppendLine($"Printing");
                                            sb.AppendLine($"{job.NumberOfPagesPrinted} / {PrintTargetFileInfo.Count}");
                                            break;
                                    }
                                    ProgressText.Dispatcher.Invoke(() =>
                                    {
                                        ProgressText.Text = sb.ToString();
                                    });
                                }
                            }
                            await Task.Delay(1);
                        }
                    }, System.Windows.Threading.DispatcherPriority.Background);
                    await Task.Delay(1);
                }, printServerWatchTaskCancellationTokenSource.Token);

                printServerTask.Start();
                printDialog.PrintDocument(documentViewer.Document.DocumentPaginator, "AbleSoft PostDocument2.0");
                printServerWatchTaskCancellationTokenSource.Cancel();
            }
        }
1

There are 1 answers

4
BionicCode On

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 void OnPrintButtonClickedAsync(object sender, EventArgs e)
{  
  DocumentPaginator documentPaginator = ((IDocumentPaginatorSource)this.Document).DocumentPaginator;
  var progressReporter = new Progress<(int PageNumber, int Percentage)>(ReportProgress);
  using var printer = new DocumentPrinter();
  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;

DocumentPrinter.cs

public class DocumentPrinter : 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 DocumentPrinter() => 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 RangeDocumentPaginator(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
  // ~DocumentPrinter()
  // {
  //     // 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);
  }
}

RangeDocumentPaginator.cs

internal class RangeDocumentPaginator : DocumentPaginator
{
  protected RangeDocumentPaginator() : base() { }

  public RangeDocumentPaginator(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; }
}