Home > Mobile >  What is the best approach for printing a UserControl or a Window without blocking the UI?
What is the best approach for printing a UserControl or a Window without blocking the UI?

Time:08-24

I have bound the required data in my UserControl. This UserControl is the Print Layout of what I want to print. Great, now I can print it using PrintVisual.

private async void Print(){
    await Task.Run(() => {
        var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
        PrintDialog pd = new(){
            PrintQueue = new PrintQueue(new PrintServer(), printerName)
        };
        pd.PrintVisual(PrintLayout, "Print Job Description");
    });
}

... 
await Print(); //Call the async method to Print.

The above code will work. However, it will block the UI and I wouldn't say I like it.

I was thinking of converting the layout into an image and then printing it using PrintDocument. But then the quality won't be that good and I will need to handle the size. In my case, whatever the size of the layout should be the output. I am also thinking of converting the layout into XPS file or PDF and then maybe printing it using PrintDocument too but I'm not sure yet since this will require another IO processing and could lead to performance issues.

What could be the best approach here?

CodePudding user response:

Disregard the conversion to XPS or PDF file. Instead, you can transform your UserControl into a FixedDocument and directly print it with PrintDialog.PrintDocument(); But still, any use of PrintDialog will block the UI.

The solution is to create a Thread inside your await/Task and set its apartment state into STA (single-threaded apartment).

Here is a more detailed example.

private async void Print(){
    await Task.Run(()=>{
        // Create a thread within task and set single-threaded apartment state to avoid blocking the UI.
        Thread newThread = new(() =>{
            
            PrintLayout controlToPrint = new(){ //I don't know how you use your UserControl PrintLayout or set it's DataContext but you can do it like this;
                DataContext = YourViewModel
            };

            FixedDocument fixedDocument = new();
            //fixedDocument.DocumentPaginator.PageSize = new Size(YourOutputWith, YourOutputHeight); //you can set the size here, the value should be double.

            FixedPage fixedPage = new();
            fixedPage.Children.Add(controlToPrint); //Add your UserControl into FixedPage
            fixedPage.Width = fixedDocument.DocumentPaginator.PageSize.Width; //Make your FixPage size same as FixedDocument.
            fixedPage.Height = fixedDocument.DocumentPaginator.PageSize.Height; //Make your FixPage size same as FixedDocument.

            PageContent pageContent = new();
            ((IAddChild)pageContent).AddChild(fixedPage); //You can add more if you want.

            fixedDocument.Pages.Add(pageContent); //Finally add the PageContent into your FixedDocument which contain your UserControl

            var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
            PrintDialog pd = new(){
                PrintQueue = new PrintQueue(new PrintServer(), printerName)
            };

            pd.PrintTicket.CopyCount = 1; //set the number of copies
            pd.PrintDocument(fixedDocument.DocumentPaginator, "Print Job Description"); //Print it
        });

        // Set the thread to single-threaded apartment state.
        newThread.SetApartmentState(ApartmentState.STA);

        // Start the thread.
        newThread.Start();

        // Wait for thread completion. Blocks the calling thread,
        // which is a ThreadPool thread.
        newThread.Join();
    });
}

You may also use your original code using the PrintDialog.PrintVisual(), just move it inside a thread with a single-threaded apartment to avoid blocking the UI.

Forget the PrintDialog and STA (Single-Threaded-Apartment). For performance reasons, and much better approach, it is recommended to use either of AddJob or Write/WriteAsync of XpsDocumentWriter.

Here is an example of using WriteAsync of XpsDocumentWriter to avoid blocking the UI on print.

private void Print(){
    
    PrintLayout controlToPrint = new(){ //I don't know how you use your UserControl PrintLayout or set it's DataContext but you can do it like this;
        DataContext = YourViewModel
    };

    FixedDocument fixedDocument = new();
    //fixedDocument.DocumentPaginator.PageSize = new Size(YourOutputWith, YourOutputHeight); //you can set the size here, the value should be double.

    FixedPage fixedPage = new();
    fixedPage.Children.Add(controlToPrint); //Add your UserControl into FixedPage
    fixedPage.Width = fixedDocument.DocumentPaginator.PageSize.Width; //Make your FixPage size same as FixedDocument.
    fixedPage.Height = fixedDocument.DocumentPaginator.PageSize.Height; //Make your FixPage size same as FixedDocument.

    PageContent pageContent = new();
    ((IAddChild)pageContent).AddChild(fixedPage); //You can add more if you want.

    fixedDocument.Pages.Add(pageContent); //Finally add the PageContent into your FixedDocument which contain your UserControl

    var printerName = Properties.Settings.Default.DefaultPrinterName; //The Name of the Printer is stored on the Properties Settings.
    PrintQueue printQueue = new(new PrintServer(), printerName); //create a new PrintQueue with the selected printer
    PrintTicket printTicket = printQueue.DefaultPrintTicket; //get the default PrintTicket, you can use this to define the settings of a print job, for example, setting the number of copies to print.
    printTicket.CopyCount = 1; //Set the number of copies.
    
    XpsDocumentWriter xpsDocumentWriter = PrintQueue.CreateXpsDocumentWriter(printQueue); //create a new XpsDocumentWriter for print queue
    
    void OnPrintCompleted(object sender, WritingCompletedEventArgs e){ //trigger this method once the print is completed.
        Trace.WriteLine("Printing Done.");
        xpsDocumentWriter.WritingCompleted -= OnPrintCompleted; //always unregistered this event when done to avoid a potential memory leak.
    }
    xpsDocumentWriter.WritingCompleted  = OnPrintCompleted; //handled the event when print is completed.
    //xpsDocumentWriter.WritingCancelled  = OnWritingCancelled; //you can also handle the event when print is cancelled. PrintProgress is also possible.
    
    xpsDocumentWriter.WriteAsync(fixedDocument, printTicket); //print async without blocking the UI.
 }

Refer to the documentation of XpsDocumentWriter Class for more information.

  • Related